From 4d92d061b21227b2d4e8b002385515e22e5f2ceb Mon Sep 17 00:00:00 2001 From: Sam Gammon Date: Tue, 15 Mar 2022 18:14:58 -0700 Subject: [PATCH 1/2] Seal Elide v1 (#877) * Feature: Support for Cloud Spanner This changeset introduces support for Google Cloud Spanner in the Elide model layer and ORM. Basic support for reading, writing, transactions, etc., are all planned. This is pretty easy, obviously, since there is already a Firestore adapter to work from. Fixes and closes sgammon/elide#856. * Tune bazel.rc and Makefile configs * Tweaks to build flow * Disable labs in CI * Promote strict_proto_deps / strict_system_includes * Whoops, fix labs state * Converge IntelliJ flags with Makefile * Add DDL-based Spanner table generator - Generate compliant DDL statements for creating Spanner tables from proto models - Add ability to stub/mock `FieldPointer` objects * Add concept of field size for Spanner columns * Significantly more driver testing - Add `TypeBuffet` to test conversion of different types - Add tests for `SpannerUtil` - Add initial tests for Spanner DDL generator - Uncomment more tests from `GenericPersistenceDriverTests` * Fixes for TypeScript-related tests * Further model adapter testing tweaks - Allow test caching (local only) - Enable other adapter/driver tests * Converge newer deps * Re-enable ancillary `rules_closure` tests - Fix CSS testing bugs - Re-enable style tests - Add suite targets for DOM/style tests * Enable trace logging in model adapter objects during tests * Refactor generic driver tests to allow opt-out pattern - Allow drivers to opt-out of tests they don't yet support - Enable all tests across the board * Enable more logging targets in testing * Add specialized Spanner tests to regular backend suite - Add `SpannerDDLTest` - Add `SpannerUtilTest` * Disallow transport config construction * Implement deletes in Spanner - Implement singular delete method - Enable standard driver `delete()` tests * Implement `FieldMask` enforcement in Spanner - Enforce `FieldMask` settings for fetched records - Drop superfluous error checking * Style and bug fixes from analysis tools * Wrap `InvalidProtocolBufferException` in `IllegalStateException` * Refactor error handling for embedded JSON models * Bugfixes for Spanner DDL generator - Fix bug with `ARRAY` fields missing inner types - Add sensible default for `ENUM` fields implemented as `STRING` * Better testing for DDL statements, fixes for `BYTES` fields - Enable column sizes for `BYTES` fields - Transition long string comparisons to Truth assertions * Hook tests into the new DDL generator - Generate tables for emulated testing - Add method alias to generate DDL statements just from a model instance (with default settings) * Drop branch coverage until logging settings don't bork it * Update Javadoc * Exclude docs/tests from CodeClimate Create CNAME Set theme jekyll-theme-cayman * Add missing package-info in gust/backend/annotations * Update javadoc * Use remote 'sleek' Jekyll theme * Drop empty method comment * Further CodeClimate config tweaks * Fix duplicate exclude_patterns in CodeClimate config * [skip-ci] License header updates * Drop docs from IDE project * Refactor `DatabaseManager` to bind adapter/driver - Bind adapter and driver together, that was the OG point - Add `Closeable`/`AutoCloseable` support to `SpannerManager` - Add full test coverage to `SpannerManager` - Add shared run profile for debuggable Bazel testsuite * Refresh docs * Don't remove docroot when cleaning docs * Disallow internal construction of `SpannerManagerSingleton` * Downgrade to Micronaut v2.5.4 * Feature: 'suspend_debug' when running apps * Support for `TIMESTAMP` and `DATE` types in Spanner - Support generating DDL for temporal column types - Codec support for converting between Spanner `DATE` and `com.google.type.Date` - Codec support for converting between Spanner `TIMESTAMP` and `com.google.protobuf.Timestamp` * Ungate serialization of `TIMESTAMP` and `DATE` types - Drop exceptions - Drop TODOs - Enable serialization of `TIMESTAMP` types from PB timestamps - Enable serialization of `DATE` types from PB common dates * Support properly encoding/decoding ENUMs in Spanner * Fix repeated field error in Spanner codec * Add dedicated manager spawn route with settings control The Spanner manager needs a way to spawn drivers with custom settings. It has a way to now. * Add ability to acquire Spanner client from manager * Add granular configuration to `SpannerManager` - Add ability to control executor - Add ability to control cache - Add ability to control base spanner options - Add ability to control main driver options * Add new Spanner tests to regular suite * Fix bug with key column names * Enforce singular KEY field state * Converge tests with new ID behavior * Resolve same key bug for generated DDL * Add enforcement for ID column types * Fix ID issues with default Spanner field sets * Fix issue with Integer vs. Long decoding in Spanner * Allow public acquisition of InMemoryCache instances * Provide 'resolveColumnName' alias with no settings * Open up 'SpannerStructDeserializer#forModel * Expose better streaming support for calculating column fields - Allow access to underlying field stream - Pair fields to corresponding column names - Maintain external API * Update docs * Fix missing license header * Fix issues with ID presence on returned models - Add tests to enforce ID presence on models returned via adapter fetch interfaces - Fix related bugs on Spanner adapter * [p0] Fix for debugger_support flag issue * Fix debug race conditions for JDK app targets * Better nullchecks for Spanner columns * Upgrades to Java dependencies - Upgrade GAX - Upgrade Micronaut - Upgrade Google Cloud SDK - Upgrade Firestore * Rollback GAX change * Fixes for various analyzer warnings * Implement transactional write/delete support - Set `transactionContext` on mutation options for buffered serialization to an active transaction - Acquire a transaction context via `TransactionManager.begin()` * Implement codec support for `NUMERIC` columns Following [Google's advice][1], I've implemented `NUMERIC` types as proto-strings. - Add deserializer support for `NUMERIC`s - Add serializer support for `NUMERIC`s - Add basic test for schema translation [1]: https://cloud.google.com/spanner/docs/working-with-numerics * Update docs * Implement support for ancillary Spanner annotations - Add `NONNULL` annotation - Add basic support for field expressions (including `STORED`) - Add `commit_update` support * Rebuild docs * Apply fixes for runtime jdeps * Bump BoringSSL/Netty -> '2.0.40.Final' * Netty/Micronaut version bumps * Upgrade rules_closure and protobuf -> 3.17.3 * Re-pin maven deps * Match gRPC's versions for Netty and BoringSSL * Re-upgrade Netty/TCNative * Force shaded version of Netty for gRPC * Add remote Bazel settings profile * Enable control of naming for Java image targets * Wire up container-based dev * General fixes for Bazel within dev containers * Dev container env adjustments * Add missing continuation slash * Add container init and post-attach commands * Add creds to codespaces image (via secrets) * Don't require env for dev container * Add default set of VSC extensions * Fix path access to ibazel/bazelisk in devcontainer init * Further adjustments to devcontainer env * Drop initialize command from devcontainer * Move container signal to env * Drop initialize command * Set container user to 'dev' * Add container-init script * Tweaks for init script safety * Force version v1d for codespaces * Upgrade devcontainer -> v1e * Improvements to container init script * Force container version v1f * Fixes for project-wide RBE settings * Upgrade container -> v1g * Upgrade to latest Codespaces container * Upgrade Bazel -> 4.2.0 * Apply lib overrides for Netty and Guava-JDK5 * Upgrade Bazel -> 4.2.1 * Add Micronaut Management dep * Update Spanner -> 6.13.0 * Upgrade GAX -> 2.5.0 * Add renovate.json * Switch Nonnull to NonNull (Micronaut) in AssetManager * Update Maven pins Co-authored-by: Renovate Bot --- .bazelproject | 8 + .bazelversion | 2 +- .buildkite/pipeline.yml | 44 +- .codeclimate.yml | 17 + .devcontainer/Dockerfile | 12 + .devcontainer/Dockerfile.gitpod | 14 + .devcontainer/devcontainer.json | 31 + .ijwb/.run/Bazel Testsuite.run.xml | 13 + Makefile | 41 +- WORKSPACE | 5 +- defs/build.bzl | 54 +- defs/container.bazelrc | 40 + defs/toolchain/java/repos.bzl | 46 +- defs/toolchain/java/rules.bzl | 40 +- defs/toolchain/style/rules.bzl | 4 +- defs/toolchain/testing/file_test.bzl | 6 +- docs/CNAME | 1 + docs/_config.yml | 2 + docs/java/META-INF/MANIFEST.MF | 3 + docs/java/allclasses-index.html | 1034 ++ docs/java/allclasses.html | 167 + docs/java/allpackages-index.html | 222 + docs/java/constant-values.html | 401 + docs/java/deprecated-list.html | 175 + docs/java/element-list | 10 + docs/java/gust/Core.html | 408 + docs/java/gust/backend/AppController.html | 541 + docs/java/gust/backend/Application.html | 280 + docs/java/gust/backend/ApplicationBoot.html | 407 + ...nfiguration.AssetCachingConfiguration.html | 439 + ...uration.AssetCompressionConfiguration.html | 346 + ...figuration.AssetVarianceConfiguration.html | 384 + ...tion.ContentDistributionConfiguration.html | 327 + ...tion.CrossOriginResourceConfiguration.html | 327 + .../java/gust/backend/AssetConfiguration.html | 509 + .../AssetController.AssetsAwareCspFilter.html | 329 + docs/java/gust/backend/AssetController.html | 324 + docs/java/gust/backend/BaseController.html | 333 + ...onfiguration.ClientHintsConfiguration.html | 365 + ...nfiguration.DynamicETagsConfiguration.html | 308 + ...guration.DynamicVarianceConfiguration.html | 403 + ...figuration.FeaturePolicyConfiguration.html | 327 + ...figuration.XSSProtectionConfiguration.html | 346 + .../backend/DynamicServingConfiguration.html | 566 + docs/java/gust/backend/PageContext.html | 912 + .../java/gust/backend/PageContextManager.html | 2657 +++ docs/java/gust/backend/PageRender.html | 337 + docs/java/gust/backend/TemplateProvider.html | 468 + docs/java/gust/backend/annotations/Js.html | 399 + docs/java/gust/backend/annotations/Page.html | 753 + .../backend/annotations/Style.MediaType.html | 392 + docs/java/gust/backend/annotations/Style.html | 307 + .../backend/annotations/class-use/Js.html | 148 + .../backend/annotations/class-use/Page.html | 148 + .../class-use/Style.MediaType.html | 211 + .../backend/annotations/class-use/Style.html | 148 + .../backend/annotations/package-summary.html | 206 + .../backend/annotations/package-tree.html | 175 + .../gust/backend/annotations/package-use.html | 187 + .../gust/backend/class-use/AppController.html | 148 + .../gust/backend/class-use/Application.html | 148 + .../backend/class-use/ApplicationBoot.html | 148 + ...nfiguration.AssetCachingConfiguration.html | 213 + ...uration.AssetCompressionConfiguration.html | 213 + ...figuration.AssetVarianceConfiguration.html | 213 + ...tion.ContentDistributionConfiguration.html | 213 + ...tion.CrossOriginResourceConfiguration.html | 213 + .../backend/class-use/AssetConfiguration.html | 215 + .../AssetController.AssetsAwareCspFilter.html | 148 + .../backend/class-use/AssetController.html | 148 + .../backend/class-use/BaseController.html | 197 + ...onfiguration.ClientHintsConfiguration.html | 213 + ...nfiguration.DynamicETagsConfiguration.html | 213 + ...guration.DynamicVarianceConfiguration.html | 213 + ...figuration.FeaturePolicyConfiguration.html | 213 + ...figuration.XSSProtectionConfiguration.html | 213 + .../DynamicServingConfiguration.html | 214 + .../gust/backend/class-use/PageContext.html | 291 + .../backend/class-use/PageContextManager.html | 643 + .../gust/backend/class-use/PageRender.html | 222 + .../backend/class-use/TemplateProvider.html | 204 + .../driver/firestore/FirestoreAdapter.html | 665 + .../driver/firestore/FirestoreDriver.html | 562 + .../driver/firestore/FirestoreManager.html | 274 + .../firestore/FirestoreTransportConfig.html | 644 + .../firestore/class-use/FirestoreAdapter.html | 241 + .../firestore/class-use/FirestoreDriver.html | 206 + .../firestore/class-use/FirestoreManager.html | 148 + .../class-use/FirestoreTransportConfig.html | 148 + .../driver/firestore/package-summary.html | 191 + .../driver/firestore/package-tree.html | 166 + .../backend/driver/firestore/package-use.html | 194 + .../driver/inmemory/InMemoryAdapter.html | 490 + .../driver/inmemory/InMemoryCache.html | 491 + .../driver/inmemory/InMemoryDriver.html | 519 + .../driver/inmemory/InMemoryManager.html | 274 + .../inmemory/class-use/InMemoryAdapter.html | 209 + .../inmemory/class-use/InMemoryCache.html | 197 + .../inmemory/class-use/InMemoryDriver.html | 196 + .../inmemory/class-use/InMemoryManager.html | 148 + .../driver/inmemory/package-summary.html | 191 + .../backend/driver/inmemory/package-tree.html | 166 + .../backend/driver/inmemory/package-use.html | 200 + .../driver/spanner/SpannerAdapter.html | 890 + .../backend/driver/spanner/SpannerCodec.html | 516 + .../backend/driver/spanner/SpannerDriver.html | 542 + ...SpannerDriverSettings.DefaultSettings.html | 334 + .../driver/spanner/SpannerDriverSettings.html | 410 + .../spanner/SpannerGeneratedDDL.Builder.html | 615 + .../SpannerGeneratedDDL.InterleaveTarget.html | 348 + .../SpannerGeneratedDDL.PropagatedAction.html | 392 + ...annerGeneratedDDL.RenderableStatement.html | 269 + .../SpannerGeneratedDDL.SortDirection.html | 392 + .../SpannerGeneratedDDL.TableConstraint.html | 319 + .../driver/spanner/SpannerGeneratedDDL.html | 538 + .../spanner/SpannerManager.Builder.html | 489 + ...annerManager.ConfiguredSpannerManager.html | 474 + .../driver/spanner/SpannerManager.html | 411 + .../spanner/SpannerMutationSerializer.html | 322 + .../spanner/SpannerStructDeserializer.html | 355 + .../spanner/SpannerTemporalConverter.html | 355 + .../spanner/SpannerTransportConfig.html | 229 + .../backend/driver/spanner/SpannerUtil.html | 1212 ++ .../spanner/class-use/SpannerAdapter.html | 303 + .../spanner/class-use/SpannerCodec.html | 215 + .../spanner/class-use/SpannerDriver.html | 205 + ...SpannerDriverSettings.DefaultSettings.html | 148 + .../class-use/SpannerDriverSettings.html | 453 + .../SpannerGeneratedDDL.Builder.html | 250 + .../SpannerGeneratedDDL.InterleaveTarget.html | 237 + .../SpannerGeneratedDDL.PropagatedAction.html | 223 + ...annerGeneratedDDL.RenderableStatement.html | 203 + .../SpannerGeneratedDDL.SortDirection.html | 226 + .../SpannerGeneratedDDL.TableConstraint.html | 229 + .../class-use/SpannerGeneratedDDL.html | 196 + .../class-use/SpannerManager.Builder.html | 225 + ...annerManager.ConfiguredSpannerManager.html | 214 + .../spanner/class-use/SpannerManager.html | 197 + .../class-use/SpannerMutationSerializer.html | 148 + .../class-use/SpannerStructDeserializer.html | 197 + .../class-use/SpannerTemporalConverter.html | 148 + .../class-use/SpannerTransportConfig.html | 148 + .../driver/spanner/class-use/SpannerUtil.html | 148 + .../driver/spanner/package-summary.html | 306 + .../backend/driver/spanner/package-tree.html | 200 + .../backend/driver/spanner/package-use.html | 277 + docs/java/gust/backend/model/CacheDriver.html | 420 + .../model/CacheOptions.EvictionMode.html | 447 + .../java/gust/backend/model/CacheOptions.html | 410 + .../model/CollapsedMessage.Operation.html | 314 + .../gust/backend/model/CollapsedMessage.html | 339 + .../backend/model/CollapsedMessageCodec.html | 408 + .../model/CollapsedMessageSerializer.html | 240 + .../gust/backend/model/DatabaseAdapter.html | 362 + .../gust/backend/model/DatabaseDriver.html | 257 + .../gust/backend/model/DatabaseManager.html | 200 + .../gust/backend/model/DeleteOptions.html | 296 + .../java/gust/backend/model/EncodedModel.html | 542 + .../java/gust/backend/model/EncodingMode.html | 388 + .../backend/model/FetchOptions.MaskMode.html | 408 + .../java/gust/backend/model/FetchOptions.html | 396 + .../gust/backend/model/InvalidModelType.html | 328 + .../backend/model/MissingAnnotatedField.html | 329 + .../java/gust/backend/model/ModelAdapter.html | 585 + docs/java/gust/backend/model/ModelCodec.html | 406 + .../backend/model/ModelDeflateException.html | 259 + ...odelDeserializer.DeserializationError.html | 258 + .../gust/backend/model/ModelDeserializer.html | 317 + .../backend/model/ModelInflateException.html | 259 + .../model/ModelMetadata.FieldContainer.html | 378 + .../model/ModelMetadata.FieldPointer.html | 511 + .../gust/backend/model/ModelMetadata.html | 2363 +++ .../ModelSerializer.EnumSerializeMode.html | 393 + .../ModelSerializer.InstantSerializeMode.html | 393 + .../ModelSerializer.SerializationError.html | 258 + .../ModelSerializer.WriteDisposition.html | 409 + .../gust/backend/model/ModelSerializer.html | 334 + .../backend/model/ModelWriteConflict.html | 377 + .../gust/backend/model/ModelWriteFailure.html | 333 + .../gust/backend/model/OperationOptions.html | 383 + .../model/PersistenceDriver.Internals.html | 228 + .../gust/backend/model/PersistenceDriver.html | 1595 ++ .../backend/model/PersistenceException.html | 259 + .../backend/model/PersistenceFailure.html | 420 + .../backend/model/PersistenceManager.html | 199 + .../model/PersistenceOperationFailed.html | 309 + .../gust/backend/model/ProtoModelCodec.html | 466 + .../gust/backend/model/SerializedModel.html | 631 + docs/java/gust/backend/model/Transaction.html | 269 + .../gust/backend/model/UpdateOptions.html | 296 + .../model/WriteOptions.WriteDisposition.html | 408 + .../java/gust/backend/model/WriteOptions.html | 345 + docs/java/gust/backend/model/WriteProxy.html | 399 + .../backend/model/class-use/CacheDriver.html | 380 + .../class-use/CacheOptions.EvictionMode.html | 219 + .../backend/model/class-use/CacheOptions.html | 204 + .../class-use/CollapsedMessage.Operation.html | 211 + .../model/class-use/CollapsedMessage.html | 257 + .../class-use/CollapsedMessageCodec.html | 197 + .../class-use/CollapsedMessageSerializer.html | 148 + .../model/class-use/DatabaseAdapter.html | 256 + .../model/class-use/DatabaseDriver.html | 306 + .../model/class-use/DatabaseManager.html | 233 + .../model/class-use/DeleteOptions.html | 321 + .../backend/model/class-use/EncodedModel.html | 292 + .../backend/model/class-use/EncodingMode.html | 245 + .../class-use/FetchOptions.MaskMode.html | 230 + .../backend/model/class-use/FetchOptions.html | 375 + .../model/class-use/InvalidModelType.html | 297 + .../class-use/MissingAnnotatedField.html | 148 + .../backend/model/class-use/ModelAdapter.html | 289 + .../backend/model/class-use/ModelCodec.html | 345 + .../class-use/ModelDeflateException.html | 232 + ...odelDeserializer.DeserializationError.html | 148 + .../model/class-use/ModelDeserializer.html | 293 + .../class-use/ModelInflateException.html | 232 + .../ModelMetadata.FieldContainer.html | 268 + .../class-use/ModelMetadata.FieldPointer.html | 755 + .../model/class-use/ModelMetadata.html | 148 + .../ModelSerializer.EnumSerializeMode.html | 204 + .../ModelSerializer.InstantSerializeMode.html | 204 + .../ModelSerializer.SerializationError.html | 148 + .../ModelSerializer.WriteDisposition.html | 204 + .../model/class-use/ModelSerializer.html | 290 + .../model/class-use/ModelWriteConflict.html | 148 + .../model/class-use/ModelWriteFailure.html | 196 + .../model/class-use/OperationOptions.html | 225 + .../PersistenceDriver.Internals.html | 148 + .../model/class-use/PersistenceDriver.html | 355 + .../model/class-use/PersistenceException.html | 281 + .../model/class-use/PersistenceFailure.html | 209 + .../model/class-use/PersistenceManager.html | 289 + .../class-use/PersistenceOperationFailed.html | 148 + .../model/class-use/ProtoModelCodec.html | 213 + .../model/class-use/SerializedModel.html | 245 + .../backend/model/class-use/Transaction.html | 148 + .../model/class-use/UpdateOptions.html | 224 + .../WriteOptions.WriteDisposition.html | 241 + .../backend/model/class-use/WriteOptions.html | 353 + .../backend/model/class-use/WriteProxy.html | 207 + .../gust/backend/model/package-summary.html | 483 + .../java/gust/backend/model/package-tree.html | 268 + docs/java/gust/backend/model/package-use.html | 696 + docs/java/gust/backend/package-summary.html | 317 + docs/java/gust/backend/package-tree.html | 200 + docs/java/gust/backend/package-use.html | 286 + .../runtime/AssetManager.ManagedAsset.html | 402 + .../AssetManager.ManagedAssetContent.html | 541 + .../runtime/AssetManager.ModuleType.html | 392 + .../gust/backend/runtime/AssetManager.html | 421 + docs/java/gust/backend/runtime/Logging.html | 278 + ...blisher.CompletableFutureSubscription.html | 320 + ...tiveFuture.CompletableFuturePublisher.html | 1218 ++ ...ublisher.ListenableFutureSubscription.html | 320 + ...ctiveFuture.ListenableFuturePublisher.html | 318 + ...ctiveFuture.PublisherListenableFuture.html | 424 + .../gust/backend/runtime/ReactiveFuture.html | 978 ++ .../class-use/AssetManager.ManagedAsset.html | 211 + .../AssetManager.ManagedAssetContent.html | 216 + .../class-use/AssetManager.ModuleType.html | 209 + .../runtime/class-use/AssetManager.html | 196 + .../backend/runtime/class-use/Logging.html | 148 + ...blisher.CompletableFutureSubscription.html | 148 + ...tiveFuture.CompletableFuturePublisher.html | 148 + ...ublisher.ListenableFutureSubscription.html | 148 + ...ctiveFuture.ListenableFuturePublisher.html | 148 + ...ctiveFuture.PublisherListenableFuture.html | 148 + .../runtime/class-use/ReactiveFuture.html | 680 + .../gust/backend/runtime/package-summary.html | 235 + .../gust/backend/runtime/package-tree.html | 186 + .../gust/backend/runtime/package-use.html | 311 + .../backend/transport/GoogleAPIChannel.html | 272 + .../gust/backend/transport/GoogleService.html | 460 + .../transport/GoogleTransportConfig.html | 349 + .../transport/GoogleTransportManager.html | 458 + .../transport/GrpcTransportConfig.html | 481 + .../transport/GrpcTransportCredentials.html | 299 + .../transport/PooledTransportConfig.html | 278 + .../backend/transport/TransportConfig.html | 307 + .../transport/TransportCredentials.html | 267 + .../backend/transport/TransportException.html | 255 + .../backend/transport/TransportManager.html | 367 + .../transport/class-use/GoogleAPIChannel.html | 244 + .../transport/class-use/GoogleService.html | 228 + .../class-use/GoogleTransportConfig.html | 194 + .../class-use/GoogleTransportManager.html | 148 + .../class-use/GrpcTransportConfig.html | 227 + .../class-use/GrpcTransportCredentials.html | 234 + .../class-use/PooledTransportConfig.html | 234 + .../transport/class-use/TransportConfig.html | 241 + .../class-use/TransportCredentials.html | 241 + .../class-use/TransportException.html | 203 + .../transport/class-use/TransportManager.html | 196 + .../backend/transport/package-summary.html | 279 + .../gust/backend/transport/package-tree.html | 240 + .../gust/backend/transport/package-use.html | 316 + docs/java/gust/class-use/Core.html | 148 + docs/java/gust/package-summary.html | 175 + docs/java/gust/package-tree.html | 163 + docs/java/gust/package-use.html | 148 + docs/java/gust/util/Hex.html | 306 + docs/java/gust/util/InstantFactory.html | 320 + .../gust/util/MessageDifferencer.Builder.html | 538 + ...ageDifferencer.DefaultFieldComparator.html | 367 + ...ncer.FieldComparator.ComparisonResult.html | 434 + .../MessageDifferencer.FieldComparator.html | 315 + .../MessageDifferencer.FloatComparison.html | 392 + .../MessageDifferencer.IgnoreCriteria.html | 279 + .../MessageDifferencer.MapKeyComparator.html | 274 + ...ageDifferencer.MessageFieldComparison.html | 395 + ...geDifferencer.RepeatedFieldComparison.html | 394 + .../util/MessageDifferencer.ReportType.html | 455 + .../util/MessageDifferencer.Reporter.html | 280 + .../gust/util/MessageDifferencer.Scope.html | 394 + .../MessageDifferencer.SpecificField.html | 377 + ...rencer.StreamReporter.StreamException.html | 258 + .../MessageDifferencer.StreamReporter.html | 394 + .../MessageDifferencer.UnknownDescriptor.html | 335 + .../MessageDifferencer.UnknownFieldType.html | 476 + docs/java/gust/util/MessageDifferencer.html | 622 + docs/java/gust/util/Pair.html | 334 + docs/java/gust/util/class-use/Hex.html | 148 + .../gust/util/class-use/InstantFactory.html | 148 + .../class-use/MessageDifferencer.Builder.html | 285 + ...ageDifferencer.DefaultFieldComparator.html | 148 + ...ncer.FieldComparator.ComparisonResult.html | 233 + .../MessageDifferencer.FieldComparator.html | 214 + .../MessageDifferencer.FloatComparison.html | 235 + .../MessageDifferencer.IgnoreCriteria.html | 194 + .../MessageDifferencer.MapKeyComparator.html | 195 + ...ageDifferencer.MessageFieldComparison.html | 222 + ...geDifferencer.RepeatedFieldComparison.html | 222 + .../MessageDifferencer.ReportType.html | 232 + .../MessageDifferencer.Reporter.html | 228 + .../class-use/MessageDifferencer.Scope.html | 222 + .../MessageDifferencer.SpecificField.html | 250 + ...rencer.StreamReporter.StreamException.html | 148 + .../MessageDifferencer.StreamReporter.html | 148 + .../MessageDifferencer.UnknownDescriptor.html | 196 + .../MessageDifferencer.UnknownFieldType.html | 211 + .../util/class-use/MessageDifferencer.html | 217 + docs/java/gust/util/class-use/Pair.html | 228 + docs/java/gust/util/package-summary.html | 330 + docs/java/gust/util/package-tree.html | 213 + docs/java/gust/util/package-use.html | 304 + docs/java/help-doc.html | 280 + docs/java/index-all.html | 4123 +++++ docs/java/index.html | 219 + docs/java/jquery/external/jquery/jquery.js | 10872 ++++++++++++ .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 0 -> 335 bytes .../images/ui-bg_glass_65_dadada_1x400.png | Bin 0 -> 262 bytes .../images/ui-bg_glass_75_dadada_1x400.png | Bin 0 -> 262 bytes .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 0 -> 262 bytes .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin 0 -> 332 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 0 -> 280 bytes .../jquery/images/ui-icons_222222_256x240.png | Bin 0 -> 6922 bytes .../jquery/images/ui-icons_2e83ff_256x240.png | Bin 0 -> 4549 bytes .../jquery/images/ui-icons_454545_256x240.png | Bin 0 -> 6992 bytes .../jquery/images/ui-icons_888888_256x240.png | Bin 0 -> 6999 bytes .../jquery/images/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4549 bytes docs/java/jquery/jquery-3.5.1.js | 10872 ++++++++++++ docs/java/jquery/jquery-ui.css | 582 + docs/java/jquery/jquery-ui.js | 2659 +++ docs/java/jquery/jquery-ui.min.css | 7 + docs/java/jquery/jquery-ui.min.js | 6 + docs/java/jquery/jquery-ui.structure.css | 156 + docs/java/jquery/jquery-ui.structure.min.css | 5 + docs/java/member-search-index.js | 1 + docs/java/member-search-index.zip | Bin 0 -> 11546 bytes docs/java/overview-summary.html | 23 + docs/java/overview-tree.html | 423 + docs/java/package-search-index.js | 1 + docs/java/package-search-index.zip | Bin 0 -> 299 bytes docs/java/resources/glass.png | Bin 0 -> 499 bytes docs/java/resources/x.png | Bin 0 -> 394 bytes docs/java/script.js | 149 + docs/java/search.js | 326 + docs/java/serialized-form.html | 432 + docs/java/src-html/gust/Core.html | 187 + .../src-html/gust/backend/AppController.html | 421 + .../src-html/gust/backend/Application.html | 117 + .../gust/backend/ApplicationBoot.html | 166 + ...nfiguration.AssetCachingConfiguration.html | 285 + ...uration.AssetCompressionConfiguration.html | 285 + ...figuration.AssetVarianceConfiguration.html | 285 + ...tion.ContentDistributionConfiguration.html | 285 + ...tion.CrossOriginResourceConfiguration.html | 285 + .../gust/backend/AssetConfiguration.html | 285 + .../AssetController.AssetsAwareCspFilter.html | 721 + .../gust/backend/AssetController.html | 721 + .../src-html/gust/backend/BaseController.html | 111 + ...onfiguration.ClientHintsConfiguration.html | 291 + ...nfiguration.DynamicETagsConfiguration.html | 291 + ...guration.DynamicVarianceConfiguration.html | 291 + ...figuration.FeaturePolicyConfiguration.html | 291 + ...figuration.XSSProtectionConfiguration.html | 291 + .../backend/DynamicServingConfiguration.html | 291 + .../src-html/gust/backend/PageContext.html | 496 + .../gust/backend/PageContextManager.html | 1959 +++ .../src-html/gust/backend/PageRender.html | 114 + .../gust/backend/TemplateProvider.html | 195 + .../src-html/gust/backend/annotations/Js.html | 138 + .../gust/backend/annotations/Page.html | 194 + .../backend/annotations/Style.MediaType.html | 127 + .../gust/backend/annotations/Style.html | 127 + .../driver/firestore/FirestoreAdapter.html | 350 + .../driver/firestore/FirestoreDriver.html | 580 + .../driver/firestore/FirestoreManager.html | 98 + .../firestore/FirestoreTransportConfig.html | 199 + .../driver/inmemory/InMemoryAdapter.html | 211 + .../driver/inmemory/InMemoryCache.html | 200 + .../driver/inmemory/InMemoryDriver.html | 337 + .../driver/inmemory/InMemoryManager.html | 96 + .../driver/spanner/SpannerAdapter.html | 526 + .../backend/driver/spanner/SpannerCodec.html | 236 + .../backend/driver/spanner/SpannerDriver.html | 560 + ...SpannerDriverSettings.DefaultSettings.html | 145 + .../driver/spanner/SpannerDriverSettings.html | 145 + .../spanner/SpannerGeneratedDDL.Builder.html | 859 + .../SpannerGeneratedDDL.InterleaveTarget.html | 859 + .../SpannerGeneratedDDL.PropagatedAction.html | 859 + ...annerGeneratedDDL.RenderableStatement.html | 859 + .../SpannerGeneratedDDL.SortDirection.html | 859 + .../SpannerGeneratedDDL.TableConstraint.html | 859 + .../driver/spanner/SpannerGeneratedDDL.html | 859 + .../spanner/SpannerManager.Builder.html | 533 + ...annerManager.ConfiguredSpannerManager.html | 533 + .../driver/spanner/SpannerManager.html | 533 + .../spanner/SpannerMutationSerializer.html | 495 + .../spanner/SpannerStructDeserializer.html | 599 + .../spanner/SpannerTemporalConverter.html | 153 + .../spanner/SpannerTransportConfig.html | 98 + .../backend/driver/spanner/SpannerUtil.html | 890 + .../gust/backend/model/CacheDriver.html | 176 + .../model/CacheOptions.EvictionMode.html | 168 + .../gust/backend/model/CacheOptions.html | 168 + .../model/CollapsedMessage.Operation.html | 298 + .../gust/backend/model/CollapsedMessage.html | 298 + .../backend/model/CollapsedMessageCodec.html | 195 + .../model/CollapsedMessageSerializer.html | 98 + .../gust/backend/model/DatabaseAdapter.html | 116 + .../gust/backend/model/DatabaseDriver.html | 97 + .../gust/backend/model/DatabaseManager.html | 96 + .../gust/backend/model/DeleteOptions.html | 94 + .../gust/backend/model/EncodedModel.html | 319 + .../gust/backend/model/EncodingMode.html | 97 + .../backend/model/FetchOptions.MaskMode.html | 130 + .../gust/backend/model/FetchOptions.html | 130 + .../gust/backend/model/InvalidModelType.html | 156 + .../backend/model/MissingAnnotatedField.html | 132 + .../gust/backend/model/ModelAdapter.html | 275 + .../gust/backend/model/ModelCodec.html | 170 + .../backend/model/ModelDeflateException.html | 113 + ...odelDeserializer.DeserializationError.html | 131 + .../gust/backend/model/ModelDeserializer.html | 131 + .../backend/model/ModelInflateException.html | 113 + .../model/ModelMetadata.FieldContainer.html | 1838 ++ .../model/ModelMetadata.FieldPointer.html | 1838 ++ .../gust/backend/model/ModelMetadata.html | 1838 ++ .../ModelSerializer.EnumSerializeMode.html | 164 + .../ModelSerializer.InstantSerializeMode.html | 164 + .../ModelSerializer.SerializationError.html | 164 + .../ModelSerializer.WriteDisposition.html | 164 + .../gust/backend/model/ModelSerializer.html | 164 + .../backend/model/ModelWriteConflict.html | 122 + .../gust/backend/model/ModelWriteFailure.html | 169 + .../gust/backend/model/OperationOptions.html | 136 + .../model/PersistenceDriver.Internals.html | 993 ++ .../gust/backend/model/PersistenceDriver.html | 993 ++ .../backend/model/PersistenceException.html | 126 + .../backend/model/PersistenceFailure.html | 113 + .../backend/model/PersistenceManager.html | 96 + .../model/PersistenceOperationFailed.html | 141 + .../gust/backend/model/ProtoModelCodec.html | 325 + .../gust/backend/model/SerializedModel.html | 208 + .../gust/backend/model/Transaction.html | 94 + .../gust/backend/model/UpdateOptions.html | 94 + .../model/WriteOptions.WriteDisposition.html | 114 + .../gust/backend/model/WriteOptions.html | 114 + .../gust/backend/model/WriteProxy.html | 156 + .../runtime/AssetManager.ManagedAsset.html | 670 + .../AssetManager.ManagedAssetContent.html | 670 + .../runtime/AssetManager.ModuleType.html | 670 + .../gust/backend/runtime/AssetManager.html | 670 + .../gust/backend/runtime/Logging.html | 105 + ...blisher.CompletableFutureSubscription.html | 1226 ++ ...tiveFuture.CompletableFuturePublisher.html | 1226 ++ ...ublisher.ListenableFutureSubscription.html | 1226 ++ ...ctiveFuture.ListenableFuturePublisher.html | 1226 ++ ...ctiveFuture.PublisherListenableFuture.html | 1226 ++ .../gust/backend/runtime/ReactiveFuture.html | 1226 ++ .../backend/transport/GoogleAPIChannel.html | 126 + .../gust/backend/transport/GoogleService.html | 142 + .../transport/GoogleTransportConfig.html | 131 + .../transport/GoogleTransportManager.html | 529 + .../transport/GrpcTransportConfig.html | 146 + .../transport/GrpcTransportCredentials.html | 114 + .../transport/PooledTransportConfig.html | 98 + .../backend/transport/TransportConfig.html | 106 + .../transport/TransportCredentials.html | 103 + .../backend/transport/TransportException.html | 116 + .../backend/transport/TransportManager.html | 119 + docs/java/src-html/gust/util/Hex.html | 125 + .../src-html/gust/util/InstantFactory.html | 97 + .../gust/util/MessageDifferencer.Builder.html | 1678 ++ ...ageDifferencer.DefaultFieldComparator.html | 1678 ++ ...ncer.FieldComparator.ComparisonResult.html | 1678 ++ .../MessageDifferencer.FieldComparator.html | 1678 ++ .../MessageDifferencer.FloatComparison.html | 1678 ++ .../MessageDifferencer.IgnoreCriteria.html | 1678 ++ .../MessageDifferencer.MapKeyComparator.html | 1678 ++ ...ageDifferencer.MessageFieldComparison.html | 1678 ++ ...geDifferencer.RepeatedFieldComparison.html | 1678 ++ .../util/MessageDifferencer.ReportType.html | 1678 ++ .../util/MessageDifferencer.Reporter.html | 1678 ++ .../gust/util/MessageDifferencer.Scope.html | 1678 ++ .../MessageDifferencer.SpecificField.html | 1678 ++ ...rencer.StreamReporter.StreamException.html | 1678 ++ .../MessageDifferencer.StreamReporter.html | 1678 ++ .../MessageDifferencer.UnknownDescriptor.html | 1678 ++ .../MessageDifferencer.UnknownFieldType.html | 1678 ++ .../gust/util/MessageDifferencer.html | 1678 ++ docs/java/src-html/gust/util/Pair.html | 129 + docs/java/stylesheet.css | 906 + docs/java/type-search-index.js | 1 + docs/java/type-search-index.zip | Bin 0 -> 1484 bytes gust/core/datamodel.proto | 34 + images/base/alpine/Dockerfile | 2 +- images/base/node/Dockerfile | 2 +- java/gust/backend/annotations/BUILD.bazel | 6 + .../backend/annotations/package-info.java | 3 + java/gust/backend/driver/BUILD.bazel | 1 + .../gust/backend/driver/firestore/BUILD.bazel | 1 + .../driver/firestore/FirestoreManager.java | 4 +- .../driver/inmemory/InMemoryCache.java | 2 +- .../driver/inmemory/InMemoryManager.java | 2 +- java/gust/backend/driver/spanner/BUILD.bazel | 231 + .../driver/spanner/SpannerAdapter.java | 452 + .../backend/driver/spanner/SpannerCodec.java | 162 + .../backend/driver/spanner/SpannerDriver.java | 486 + .../driver/spanner/SpannerDriverSettings.java | 71 + .../driver/spanner/SpannerGeneratedDDL.java | 785 + .../driver/spanner/SpannerManager.java | 459 + .../spanner/SpannerMutationSerializer.java | 421 + .../spanner/SpannerStructDeserializer.java | 525 + .../spanner/SpannerTemporalConverter.java | 79 + .../spanner/SpannerTransportConfig.java | 24 + .../backend/driver/spanner/SpannerUtil.java | 816 + .../backend/driver/spanner/package-info.java | 15 + java/gust/backend/model/BUILD.bazel | 1 + .../backend/model/CollapsedMessageCodec.java | 7 +- java/gust/backend/model/DatabaseManager.java | 5 +- java/gust/backend/model/FetchOptions.java | 5 - java/gust/backend/model/ModelAdapter.java | 1 - java/gust/backend/model/ModelCodec.java | 8 + java/gust/backend/model/ModelMetadata.java | 182 +- java/gust/backend/model/ObjectModelCodec.kt | 5 +- java/gust/backend/model/OperationOptions.java | 5 + .../backend/model/PersistenceManager.java | 1 + java/gust/backend/model/ProtoModelCodec.java | 6 + java/gust/backend/runtime/AssetManager.java | 124 +- .../gust/backend/transport/GoogleService.java | 5 +- javatests/BUILD.bazel | 5 + .../firestore/FirestoreAdapterTest.java | 14 + .../gust/backend/driver/spanner/BUILD.bazel | 139 + .../driver/spanner/SpannerAdapterTest.java | 288 + .../driver/spanner/SpannerDDLTest.java | 152 + .../driver/spanner/SpannerManagerTest.java | 223 + .../spanner/SpannerTemporalConverterTest.java | 63 + .../driver/spanner/SpannerUtilTest.java | 351 + javatests/gust/backend/model/BUILD.bazel | 6 + .../model/GenericPersistenceDriverTest.java | 52 +- javatests/gust/backend/model/person.proto | 86 +- javatests/logback.xml | 25 +- maven_install.json | 13831 +++++++++------- package.json | 1 + renovate.json | 5 + tests/BUILD.bazel | 11 + tests/dom/BUILD.bazel | 8 + tests/style/BUILD.bazel | 27 +- tools/bazel.rc | 148 +- tools/bundler/BUILD.bazel | 1 + tools/container-init.sh | 46 + yarn.lock | 7 + 584 files changed, 232113 insertions(+), 6174 deletions(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/Dockerfile.gitpod create mode 100644 .devcontainer/devcontainer.json create mode 100644 .ijwb/.run/Bazel Testsuite.run.xml create mode 100644 defs/container.bazelrc create mode 100644 docs/CNAME create mode 100644 docs/_config.yml create mode 100644 docs/java/META-INF/MANIFEST.MF create mode 100644 docs/java/allclasses-index.html create mode 100644 docs/java/allclasses.html create mode 100644 docs/java/allpackages-index.html create mode 100644 docs/java/constant-values.html create mode 100644 docs/java/deprecated-list.html create mode 100644 docs/java/element-list create mode 100644 docs/java/gust/Core.html create mode 100644 docs/java/gust/backend/AppController.html create mode 100644 docs/java/gust/backend/Application.html create mode 100644 docs/java/gust/backend/ApplicationBoot.html create mode 100644 docs/java/gust/backend/AssetConfiguration.AssetCachingConfiguration.html create mode 100644 docs/java/gust/backend/AssetConfiguration.AssetCompressionConfiguration.html create mode 100644 docs/java/gust/backend/AssetConfiguration.AssetVarianceConfiguration.html create mode 100644 docs/java/gust/backend/AssetConfiguration.ContentDistributionConfiguration.html create mode 100644 docs/java/gust/backend/AssetConfiguration.CrossOriginResourceConfiguration.html create mode 100644 docs/java/gust/backend/AssetConfiguration.html create mode 100644 docs/java/gust/backend/AssetController.AssetsAwareCspFilter.html create mode 100644 docs/java/gust/backend/AssetController.html create mode 100644 docs/java/gust/backend/BaseController.html create mode 100644 docs/java/gust/backend/DynamicServingConfiguration.ClientHintsConfiguration.html create mode 100644 docs/java/gust/backend/DynamicServingConfiguration.DynamicETagsConfiguration.html create mode 100644 docs/java/gust/backend/DynamicServingConfiguration.DynamicVarianceConfiguration.html create mode 100644 docs/java/gust/backend/DynamicServingConfiguration.FeaturePolicyConfiguration.html create mode 100644 docs/java/gust/backend/DynamicServingConfiguration.XSSProtectionConfiguration.html create mode 100644 docs/java/gust/backend/DynamicServingConfiguration.html create mode 100644 docs/java/gust/backend/PageContext.html create mode 100644 docs/java/gust/backend/PageContextManager.html create mode 100644 docs/java/gust/backend/PageRender.html create mode 100644 docs/java/gust/backend/TemplateProvider.html create mode 100644 docs/java/gust/backend/annotations/Js.html create mode 100644 docs/java/gust/backend/annotations/Page.html create mode 100644 docs/java/gust/backend/annotations/Style.MediaType.html create mode 100644 docs/java/gust/backend/annotations/Style.html create mode 100644 docs/java/gust/backend/annotations/class-use/Js.html create mode 100644 docs/java/gust/backend/annotations/class-use/Page.html create mode 100644 docs/java/gust/backend/annotations/class-use/Style.MediaType.html create mode 100644 docs/java/gust/backend/annotations/class-use/Style.html create mode 100644 docs/java/gust/backend/annotations/package-summary.html create mode 100644 docs/java/gust/backend/annotations/package-tree.html create mode 100644 docs/java/gust/backend/annotations/package-use.html create mode 100644 docs/java/gust/backend/class-use/AppController.html create mode 100644 docs/java/gust/backend/class-use/Application.html create mode 100644 docs/java/gust/backend/class-use/ApplicationBoot.html create mode 100644 docs/java/gust/backend/class-use/AssetConfiguration.AssetCachingConfiguration.html create mode 100644 docs/java/gust/backend/class-use/AssetConfiguration.AssetCompressionConfiguration.html create mode 100644 docs/java/gust/backend/class-use/AssetConfiguration.AssetVarianceConfiguration.html create mode 100644 docs/java/gust/backend/class-use/AssetConfiguration.ContentDistributionConfiguration.html create mode 100644 docs/java/gust/backend/class-use/AssetConfiguration.CrossOriginResourceConfiguration.html create mode 100644 docs/java/gust/backend/class-use/AssetConfiguration.html create mode 100644 docs/java/gust/backend/class-use/AssetController.AssetsAwareCspFilter.html create mode 100644 docs/java/gust/backend/class-use/AssetController.html create mode 100644 docs/java/gust/backend/class-use/BaseController.html create mode 100644 docs/java/gust/backend/class-use/DynamicServingConfiguration.ClientHintsConfiguration.html create mode 100644 docs/java/gust/backend/class-use/DynamicServingConfiguration.DynamicETagsConfiguration.html create mode 100644 docs/java/gust/backend/class-use/DynamicServingConfiguration.DynamicVarianceConfiguration.html create mode 100644 docs/java/gust/backend/class-use/DynamicServingConfiguration.FeaturePolicyConfiguration.html create mode 100644 docs/java/gust/backend/class-use/DynamicServingConfiguration.XSSProtectionConfiguration.html create mode 100644 docs/java/gust/backend/class-use/DynamicServingConfiguration.html create mode 100644 docs/java/gust/backend/class-use/PageContext.html create mode 100644 docs/java/gust/backend/class-use/PageContextManager.html create mode 100644 docs/java/gust/backend/class-use/PageRender.html create mode 100644 docs/java/gust/backend/class-use/TemplateProvider.html create mode 100644 docs/java/gust/backend/driver/firestore/FirestoreAdapter.html create mode 100644 docs/java/gust/backend/driver/firestore/FirestoreDriver.html create mode 100644 docs/java/gust/backend/driver/firestore/FirestoreManager.html create mode 100644 docs/java/gust/backend/driver/firestore/FirestoreTransportConfig.html create mode 100644 docs/java/gust/backend/driver/firestore/class-use/FirestoreAdapter.html create mode 100644 docs/java/gust/backend/driver/firestore/class-use/FirestoreDriver.html create mode 100644 docs/java/gust/backend/driver/firestore/class-use/FirestoreManager.html create mode 100644 docs/java/gust/backend/driver/firestore/class-use/FirestoreTransportConfig.html create mode 100644 docs/java/gust/backend/driver/firestore/package-summary.html create mode 100644 docs/java/gust/backend/driver/firestore/package-tree.html create mode 100644 docs/java/gust/backend/driver/firestore/package-use.html create mode 100644 docs/java/gust/backend/driver/inmemory/InMemoryAdapter.html create mode 100644 docs/java/gust/backend/driver/inmemory/InMemoryCache.html create mode 100644 docs/java/gust/backend/driver/inmemory/InMemoryDriver.html create mode 100644 docs/java/gust/backend/driver/inmemory/InMemoryManager.html create mode 100644 docs/java/gust/backend/driver/inmemory/class-use/InMemoryAdapter.html create mode 100644 docs/java/gust/backend/driver/inmemory/class-use/InMemoryCache.html create mode 100644 docs/java/gust/backend/driver/inmemory/class-use/InMemoryDriver.html create mode 100644 docs/java/gust/backend/driver/inmemory/class-use/InMemoryManager.html create mode 100644 docs/java/gust/backend/driver/inmemory/package-summary.html create mode 100644 docs/java/gust/backend/driver/inmemory/package-tree.html create mode 100644 docs/java/gust/backend/driver/inmemory/package-use.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerAdapter.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerCodec.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerDriver.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerDriverSettings.DefaultSettings.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerDriverSettings.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.Builder.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.InterleaveTarget.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.PropagatedAction.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.RenderableStatement.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.SortDirection.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.TableConstraint.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerManager.Builder.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerManager.ConfiguredSpannerManager.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerManager.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerMutationSerializer.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerStructDeserializer.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerTemporalConverter.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerTransportConfig.html create mode 100644 docs/java/gust/backend/driver/spanner/SpannerUtil.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerAdapter.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerCodec.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerDriver.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerDriverSettings.DefaultSettings.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerDriverSettings.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.Builder.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.InterleaveTarget.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.PropagatedAction.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.RenderableStatement.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.SortDirection.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.TableConstraint.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerManager.Builder.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerManager.ConfiguredSpannerManager.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerManager.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerMutationSerializer.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerStructDeserializer.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerTemporalConverter.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerTransportConfig.html create mode 100644 docs/java/gust/backend/driver/spanner/class-use/SpannerUtil.html create mode 100644 docs/java/gust/backend/driver/spanner/package-summary.html create mode 100644 docs/java/gust/backend/driver/spanner/package-tree.html create mode 100644 docs/java/gust/backend/driver/spanner/package-use.html create mode 100644 docs/java/gust/backend/model/CacheDriver.html create mode 100644 docs/java/gust/backend/model/CacheOptions.EvictionMode.html create mode 100644 docs/java/gust/backend/model/CacheOptions.html create mode 100644 docs/java/gust/backend/model/CollapsedMessage.Operation.html create mode 100644 docs/java/gust/backend/model/CollapsedMessage.html create mode 100644 docs/java/gust/backend/model/CollapsedMessageCodec.html create mode 100644 docs/java/gust/backend/model/CollapsedMessageSerializer.html create mode 100644 docs/java/gust/backend/model/DatabaseAdapter.html create mode 100644 docs/java/gust/backend/model/DatabaseDriver.html create mode 100644 docs/java/gust/backend/model/DatabaseManager.html create mode 100644 docs/java/gust/backend/model/DeleteOptions.html create mode 100644 docs/java/gust/backend/model/EncodedModel.html create mode 100644 docs/java/gust/backend/model/EncodingMode.html create mode 100644 docs/java/gust/backend/model/FetchOptions.MaskMode.html create mode 100644 docs/java/gust/backend/model/FetchOptions.html create mode 100644 docs/java/gust/backend/model/InvalidModelType.html create mode 100644 docs/java/gust/backend/model/MissingAnnotatedField.html create mode 100644 docs/java/gust/backend/model/ModelAdapter.html create mode 100644 docs/java/gust/backend/model/ModelCodec.html create mode 100644 docs/java/gust/backend/model/ModelDeflateException.html create mode 100644 docs/java/gust/backend/model/ModelDeserializer.DeserializationError.html create mode 100644 docs/java/gust/backend/model/ModelDeserializer.html create mode 100644 docs/java/gust/backend/model/ModelInflateException.html create mode 100644 docs/java/gust/backend/model/ModelMetadata.FieldContainer.html create mode 100644 docs/java/gust/backend/model/ModelMetadata.FieldPointer.html create mode 100644 docs/java/gust/backend/model/ModelMetadata.html create mode 100644 docs/java/gust/backend/model/ModelSerializer.EnumSerializeMode.html create mode 100644 docs/java/gust/backend/model/ModelSerializer.InstantSerializeMode.html create mode 100644 docs/java/gust/backend/model/ModelSerializer.SerializationError.html create mode 100644 docs/java/gust/backend/model/ModelSerializer.WriteDisposition.html create mode 100644 docs/java/gust/backend/model/ModelSerializer.html create mode 100644 docs/java/gust/backend/model/ModelWriteConflict.html create mode 100644 docs/java/gust/backend/model/ModelWriteFailure.html create mode 100644 docs/java/gust/backend/model/OperationOptions.html create mode 100644 docs/java/gust/backend/model/PersistenceDriver.Internals.html create mode 100644 docs/java/gust/backend/model/PersistenceDriver.html create mode 100644 docs/java/gust/backend/model/PersistenceException.html create mode 100644 docs/java/gust/backend/model/PersistenceFailure.html create mode 100644 docs/java/gust/backend/model/PersistenceManager.html create mode 100644 docs/java/gust/backend/model/PersistenceOperationFailed.html create mode 100644 docs/java/gust/backend/model/ProtoModelCodec.html create mode 100644 docs/java/gust/backend/model/SerializedModel.html create mode 100644 docs/java/gust/backend/model/Transaction.html create mode 100644 docs/java/gust/backend/model/UpdateOptions.html create mode 100644 docs/java/gust/backend/model/WriteOptions.WriteDisposition.html create mode 100644 docs/java/gust/backend/model/WriteOptions.html create mode 100644 docs/java/gust/backend/model/WriteProxy.html create mode 100644 docs/java/gust/backend/model/class-use/CacheDriver.html create mode 100644 docs/java/gust/backend/model/class-use/CacheOptions.EvictionMode.html create mode 100644 docs/java/gust/backend/model/class-use/CacheOptions.html create mode 100644 docs/java/gust/backend/model/class-use/CollapsedMessage.Operation.html create mode 100644 docs/java/gust/backend/model/class-use/CollapsedMessage.html create mode 100644 docs/java/gust/backend/model/class-use/CollapsedMessageCodec.html create mode 100644 docs/java/gust/backend/model/class-use/CollapsedMessageSerializer.html create mode 100644 docs/java/gust/backend/model/class-use/DatabaseAdapter.html create mode 100644 docs/java/gust/backend/model/class-use/DatabaseDriver.html create mode 100644 docs/java/gust/backend/model/class-use/DatabaseManager.html create mode 100644 docs/java/gust/backend/model/class-use/DeleteOptions.html create mode 100644 docs/java/gust/backend/model/class-use/EncodedModel.html create mode 100644 docs/java/gust/backend/model/class-use/EncodingMode.html create mode 100644 docs/java/gust/backend/model/class-use/FetchOptions.MaskMode.html create mode 100644 docs/java/gust/backend/model/class-use/FetchOptions.html create mode 100644 docs/java/gust/backend/model/class-use/InvalidModelType.html create mode 100644 docs/java/gust/backend/model/class-use/MissingAnnotatedField.html create mode 100644 docs/java/gust/backend/model/class-use/ModelAdapter.html create mode 100644 docs/java/gust/backend/model/class-use/ModelCodec.html create mode 100644 docs/java/gust/backend/model/class-use/ModelDeflateException.html create mode 100644 docs/java/gust/backend/model/class-use/ModelDeserializer.DeserializationError.html create mode 100644 docs/java/gust/backend/model/class-use/ModelDeserializer.html create mode 100644 docs/java/gust/backend/model/class-use/ModelInflateException.html create mode 100644 docs/java/gust/backend/model/class-use/ModelMetadata.FieldContainer.html create mode 100644 docs/java/gust/backend/model/class-use/ModelMetadata.FieldPointer.html create mode 100644 docs/java/gust/backend/model/class-use/ModelMetadata.html create mode 100644 docs/java/gust/backend/model/class-use/ModelSerializer.EnumSerializeMode.html create mode 100644 docs/java/gust/backend/model/class-use/ModelSerializer.InstantSerializeMode.html create mode 100644 docs/java/gust/backend/model/class-use/ModelSerializer.SerializationError.html create mode 100644 docs/java/gust/backend/model/class-use/ModelSerializer.WriteDisposition.html create mode 100644 docs/java/gust/backend/model/class-use/ModelSerializer.html create mode 100644 docs/java/gust/backend/model/class-use/ModelWriteConflict.html create mode 100644 docs/java/gust/backend/model/class-use/ModelWriteFailure.html create mode 100644 docs/java/gust/backend/model/class-use/OperationOptions.html create mode 100644 docs/java/gust/backend/model/class-use/PersistenceDriver.Internals.html create mode 100644 docs/java/gust/backend/model/class-use/PersistenceDriver.html create mode 100644 docs/java/gust/backend/model/class-use/PersistenceException.html create mode 100644 docs/java/gust/backend/model/class-use/PersistenceFailure.html create mode 100644 docs/java/gust/backend/model/class-use/PersistenceManager.html create mode 100644 docs/java/gust/backend/model/class-use/PersistenceOperationFailed.html create mode 100644 docs/java/gust/backend/model/class-use/ProtoModelCodec.html create mode 100644 docs/java/gust/backend/model/class-use/SerializedModel.html create mode 100644 docs/java/gust/backend/model/class-use/Transaction.html create mode 100644 docs/java/gust/backend/model/class-use/UpdateOptions.html create mode 100644 docs/java/gust/backend/model/class-use/WriteOptions.WriteDisposition.html create mode 100644 docs/java/gust/backend/model/class-use/WriteOptions.html create mode 100644 docs/java/gust/backend/model/class-use/WriteProxy.html create mode 100644 docs/java/gust/backend/model/package-summary.html create mode 100644 docs/java/gust/backend/model/package-tree.html create mode 100644 docs/java/gust/backend/model/package-use.html create mode 100644 docs/java/gust/backend/package-summary.html create mode 100644 docs/java/gust/backend/package-tree.html create mode 100644 docs/java/gust/backend/package-use.html create mode 100644 docs/java/gust/backend/runtime/AssetManager.ManagedAsset.html create mode 100644 docs/java/gust/backend/runtime/AssetManager.ManagedAssetContent.html create mode 100644 docs/java/gust/backend/runtime/AssetManager.ModuleType.html create mode 100644 docs/java/gust/backend/runtime/AssetManager.html create mode 100644 docs/java/gust/backend/runtime/Logging.html create mode 100644 docs/java/gust/backend/runtime/ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription.html create mode 100644 docs/java/gust/backend/runtime/ReactiveFuture.CompletableFuturePublisher.html create mode 100644 docs/java/gust/backend/runtime/ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription.html create mode 100644 docs/java/gust/backend/runtime/ReactiveFuture.ListenableFuturePublisher.html create mode 100644 docs/java/gust/backend/runtime/ReactiveFuture.PublisherListenableFuture.html create mode 100644 docs/java/gust/backend/runtime/ReactiveFuture.html create mode 100644 docs/java/gust/backend/runtime/class-use/AssetManager.ManagedAsset.html create mode 100644 docs/java/gust/backend/runtime/class-use/AssetManager.ManagedAssetContent.html create mode 100644 docs/java/gust/backend/runtime/class-use/AssetManager.ModuleType.html create mode 100644 docs/java/gust/backend/runtime/class-use/AssetManager.html create mode 100644 docs/java/gust/backend/runtime/class-use/Logging.html create mode 100644 docs/java/gust/backend/runtime/class-use/ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription.html create mode 100644 docs/java/gust/backend/runtime/class-use/ReactiveFuture.CompletableFuturePublisher.html create mode 100644 docs/java/gust/backend/runtime/class-use/ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription.html create mode 100644 docs/java/gust/backend/runtime/class-use/ReactiveFuture.ListenableFuturePublisher.html create mode 100644 docs/java/gust/backend/runtime/class-use/ReactiveFuture.PublisherListenableFuture.html create mode 100644 docs/java/gust/backend/runtime/class-use/ReactiveFuture.html create mode 100644 docs/java/gust/backend/runtime/package-summary.html create mode 100644 docs/java/gust/backend/runtime/package-tree.html create mode 100644 docs/java/gust/backend/runtime/package-use.html create mode 100644 docs/java/gust/backend/transport/GoogleAPIChannel.html create mode 100644 docs/java/gust/backend/transport/GoogleService.html create mode 100644 docs/java/gust/backend/transport/GoogleTransportConfig.html create mode 100644 docs/java/gust/backend/transport/GoogleTransportManager.html create mode 100644 docs/java/gust/backend/transport/GrpcTransportConfig.html create mode 100644 docs/java/gust/backend/transport/GrpcTransportCredentials.html create mode 100644 docs/java/gust/backend/transport/PooledTransportConfig.html create mode 100644 docs/java/gust/backend/transport/TransportConfig.html create mode 100644 docs/java/gust/backend/transport/TransportCredentials.html create mode 100644 docs/java/gust/backend/transport/TransportException.html create mode 100644 docs/java/gust/backend/transport/TransportManager.html create mode 100644 docs/java/gust/backend/transport/class-use/GoogleAPIChannel.html create mode 100644 docs/java/gust/backend/transport/class-use/GoogleService.html create mode 100644 docs/java/gust/backend/transport/class-use/GoogleTransportConfig.html create mode 100644 docs/java/gust/backend/transport/class-use/GoogleTransportManager.html create mode 100644 docs/java/gust/backend/transport/class-use/GrpcTransportConfig.html create mode 100644 docs/java/gust/backend/transport/class-use/GrpcTransportCredentials.html create mode 100644 docs/java/gust/backend/transport/class-use/PooledTransportConfig.html create mode 100644 docs/java/gust/backend/transport/class-use/TransportConfig.html create mode 100644 docs/java/gust/backend/transport/class-use/TransportCredentials.html create mode 100644 docs/java/gust/backend/transport/class-use/TransportException.html create mode 100644 docs/java/gust/backend/transport/class-use/TransportManager.html create mode 100644 docs/java/gust/backend/transport/package-summary.html create mode 100644 docs/java/gust/backend/transport/package-tree.html create mode 100644 docs/java/gust/backend/transport/package-use.html create mode 100644 docs/java/gust/class-use/Core.html create mode 100644 docs/java/gust/package-summary.html create mode 100644 docs/java/gust/package-tree.html create mode 100644 docs/java/gust/package-use.html create mode 100644 docs/java/gust/util/Hex.html create mode 100644 docs/java/gust/util/InstantFactory.html create mode 100644 docs/java/gust/util/MessageDifferencer.Builder.html create mode 100644 docs/java/gust/util/MessageDifferencer.DefaultFieldComparator.html create mode 100644 docs/java/gust/util/MessageDifferencer.FieldComparator.ComparisonResult.html create mode 100644 docs/java/gust/util/MessageDifferencer.FieldComparator.html create mode 100644 docs/java/gust/util/MessageDifferencer.FloatComparison.html create mode 100644 docs/java/gust/util/MessageDifferencer.IgnoreCriteria.html create mode 100644 docs/java/gust/util/MessageDifferencer.MapKeyComparator.html create mode 100644 docs/java/gust/util/MessageDifferencer.MessageFieldComparison.html create mode 100644 docs/java/gust/util/MessageDifferencer.RepeatedFieldComparison.html create mode 100644 docs/java/gust/util/MessageDifferencer.ReportType.html create mode 100644 docs/java/gust/util/MessageDifferencer.Reporter.html create mode 100644 docs/java/gust/util/MessageDifferencer.Scope.html create mode 100644 docs/java/gust/util/MessageDifferencer.SpecificField.html create mode 100644 docs/java/gust/util/MessageDifferencer.StreamReporter.StreamException.html create mode 100644 docs/java/gust/util/MessageDifferencer.StreamReporter.html create mode 100644 docs/java/gust/util/MessageDifferencer.UnknownDescriptor.html create mode 100644 docs/java/gust/util/MessageDifferencer.UnknownFieldType.html create mode 100644 docs/java/gust/util/MessageDifferencer.html create mode 100644 docs/java/gust/util/Pair.html create mode 100644 docs/java/gust/util/class-use/Hex.html create mode 100644 docs/java/gust/util/class-use/InstantFactory.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.Builder.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.DefaultFieldComparator.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.FieldComparator.ComparisonResult.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.FieldComparator.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.FloatComparison.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.IgnoreCriteria.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.MapKeyComparator.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.MessageFieldComparison.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.RepeatedFieldComparison.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.ReportType.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.Reporter.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.Scope.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.SpecificField.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.StreamReporter.StreamException.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.StreamReporter.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.UnknownDescriptor.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.UnknownFieldType.html create mode 100644 docs/java/gust/util/class-use/MessageDifferencer.html create mode 100644 docs/java/gust/util/class-use/Pair.html create mode 100644 docs/java/gust/util/package-summary.html create mode 100644 docs/java/gust/util/package-tree.html create mode 100644 docs/java/gust/util/package-use.html create mode 100644 docs/java/help-doc.html create mode 100644 docs/java/index-all.html create mode 100644 docs/java/index.html create mode 100644 docs/java/jquery/external/jquery/jquery.js create mode 100644 docs/java/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png create mode 100644 docs/java/jquery/images/ui-bg_glass_65_dadada_1x400.png create mode 100644 docs/java/jquery/images/ui-bg_glass_75_dadada_1x400.png create mode 100644 docs/java/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png create mode 100644 docs/java/jquery/images/ui-bg_glass_95_fef1ec_1x400.png create mode 100644 docs/java/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png create mode 100644 docs/java/jquery/images/ui-icons_222222_256x240.png create mode 100644 docs/java/jquery/images/ui-icons_2e83ff_256x240.png create mode 100644 docs/java/jquery/images/ui-icons_454545_256x240.png create mode 100644 docs/java/jquery/images/ui-icons_888888_256x240.png create mode 100644 docs/java/jquery/images/ui-icons_cd0a0a_256x240.png create mode 100644 docs/java/jquery/jquery-3.5.1.js create mode 100644 docs/java/jquery/jquery-ui.css create mode 100644 docs/java/jquery/jquery-ui.js create mode 100644 docs/java/jquery/jquery-ui.min.css create mode 100644 docs/java/jquery/jquery-ui.min.js create mode 100644 docs/java/jquery/jquery-ui.structure.css create mode 100644 docs/java/jquery/jquery-ui.structure.min.css create mode 100644 docs/java/member-search-index.js create mode 100644 docs/java/member-search-index.zip create mode 100644 docs/java/overview-summary.html create mode 100644 docs/java/overview-tree.html create mode 100644 docs/java/package-search-index.js create mode 100644 docs/java/package-search-index.zip create mode 100644 docs/java/resources/glass.png create mode 100644 docs/java/resources/x.png create mode 100644 docs/java/script.js create mode 100644 docs/java/search.js create mode 100644 docs/java/serialized-form.html create mode 100644 docs/java/src-html/gust/Core.html create mode 100644 docs/java/src-html/gust/backend/AppController.html create mode 100644 docs/java/src-html/gust/backend/Application.html create mode 100644 docs/java/src-html/gust/backend/ApplicationBoot.html create mode 100644 docs/java/src-html/gust/backend/AssetConfiguration.AssetCachingConfiguration.html create mode 100644 docs/java/src-html/gust/backend/AssetConfiguration.AssetCompressionConfiguration.html create mode 100644 docs/java/src-html/gust/backend/AssetConfiguration.AssetVarianceConfiguration.html create mode 100644 docs/java/src-html/gust/backend/AssetConfiguration.ContentDistributionConfiguration.html create mode 100644 docs/java/src-html/gust/backend/AssetConfiguration.CrossOriginResourceConfiguration.html create mode 100644 docs/java/src-html/gust/backend/AssetConfiguration.html create mode 100644 docs/java/src-html/gust/backend/AssetController.AssetsAwareCspFilter.html create mode 100644 docs/java/src-html/gust/backend/AssetController.html create mode 100644 docs/java/src-html/gust/backend/BaseController.html create mode 100644 docs/java/src-html/gust/backend/DynamicServingConfiguration.ClientHintsConfiguration.html create mode 100644 docs/java/src-html/gust/backend/DynamicServingConfiguration.DynamicETagsConfiguration.html create mode 100644 docs/java/src-html/gust/backend/DynamicServingConfiguration.DynamicVarianceConfiguration.html create mode 100644 docs/java/src-html/gust/backend/DynamicServingConfiguration.FeaturePolicyConfiguration.html create mode 100644 docs/java/src-html/gust/backend/DynamicServingConfiguration.XSSProtectionConfiguration.html create mode 100644 docs/java/src-html/gust/backend/DynamicServingConfiguration.html create mode 100644 docs/java/src-html/gust/backend/PageContext.html create mode 100644 docs/java/src-html/gust/backend/PageContextManager.html create mode 100644 docs/java/src-html/gust/backend/PageRender.html create mode 100644 docs/java/src-html/gust/backend/TemplateProvider.html create mode 100644 docs/java/src-html/gust/backend/annotations/Js.html create mode 100644 docs/java/src-html/gust/backend/annotations/Page.html create mode 100644 docs/java/src-html/gust/backend/annotations/Style.MediaType.html create mode 100644 docs/java/src-html/gust/backend/annotations/Style.html create mode 100644 docs/java/src-html/gust/backend/driver/firestore/FirestoreAdapter.html create mode 100644 docs/java/src-html/gust/backend/driver/firestore/FirestoreDriver.html create mode 100644 docs/java/src-html/gust/backend/driver/firestore/FirestoreManager.html create mode 100644 docs/java/src-html/gust/backend/driver/firestore/FirestoreTransportConfig.html create mode 100644 docs/java/src-html/gust/backend/driver/inmemory/InMemoryAdapter.html create mode 100644 docs/java/src-html/gust/backend/driver/inmemory/InMemoryCache.html create mode 100644 docs/java/src-html/gust/backend/driver/inmemory/InMemoryDriver.html create mode 100644 docs/java/src-html/gust/backend/driver/inmemory/InMemoryManager.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerAdapter.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerCodec.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerDriver.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerDriverSettings.DefaultSettings.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerDriverSettings.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.Builder.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.InterleaveTarget.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.PropagatedAction.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.RenderableStatement.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.SortDirection.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.TableConstraint.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerManager.Builder.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerManager.ConfiguredSpannerManager.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerManager.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerMutationSerializer.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerStructDeserializer.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerTemporalConverter.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerTransportConfig.html create mode 100644 docs/java/src-html/gust/backend/driver/spanner/SpannerUtil.html create mode 100644 docs/java/src-html/gust/backend/model/CacheDriver.html create mode 100644 docs/java/src-html/gust/backend/model/CacheOptions.EvictionMode.html create mode 100644 docs/java/src-html/gust/backend/model/CacheOptions.html create mode 100644 docs/java/src-html/gust/backend/model/CollapsedMessage.Operation.html create mode 100644 docs/java/src-html/gust/backend/model/CollapsedMessage.html create mode 100644 docs/java/src-html/gust/backend/model/CollapsedMessageCodec.html create mode 100644 docs/java/src-html/gust/backend/model/CollapsedMessageSerializer.html create mode 100644 docs/java/src-html/gust/backend/model/DatabaseAdapter.html create mode 100644 docs/java/src-html/gust/backend/model/DatabaseDriver.html create mode 100644 docs/java/src-html/gust/backend/model/DatabaseManager.html create mode 100644 docs/java/src-html/gust/backend/model/DeleteOptions.html create mode 100644 docs/java/src-html/gust/backend/model/EncodedModel.html create mode 100644 docs/java/src-html/gust/backend/model/EncodingMode.html create mode 100644 docs/java/src-html/gust/backend/model/FetchOptions.MaskMode.html create mode 100644 docs/java/src-html/gust/backend/model/FetchOptions.html create mode 100644 docs/java/src-html/gust/backend/model/InvalidModelType.html create mode 100644 docs/java/src-html/gust/backend/model/MissingAnnotatedField.html create mode 100644 docs/java/src-html/gust/backend/model/ModelAdapter.html create mode 100644 docs/java/src-html/gust/backend/model/ModelCodec.html create mode 100644 docs/java/src-html/gust/backend/model/ModelDeflateException.html create mode 100644 docs/java/src-html/gust/backend/model/ModelDeserializer.DeserializationError.html create mode 100644 docs/java/src-html/gust/backend/model/ModelDeserializer.html create mode 100644 docs/java/src-html/gust/backend/model/ModelInflateException.html create mode 100644 docs/java/src-html/gust/backend/model/ModelMetadata.FieldContainer.html create mode 100644 docs/java/src-html/gust/backend/model/ModelMetadata.FieldPointer.html create mode 100644 docs/java/src-html/gust/backend/model/ModelMetadata.html create mode 100644 docs/java/src-html/gust/backend/model/ModelSerializer.EnumSerializeMode.html create mode 100644 docs/java/src-html/gust/backend/model/ModelSerializer.InstantSerializeMode.html create mode 100644 docs/java/src-html/gust/backend/model/ModelSerializer.SerializationError.html create mode 100644 docs/java/src-html/gust/backend/model/ModelSerializer.WriteDisposition.html create mode 100644 docs/java/src-html/gust/backend/model/ModelSerializer.html create mode 100644 docs/java/src-html/gust/backend/model/ModelWriteConflict.html create mode 100644 docs/java/src-html/gust/backend/model/ModelWriteFailure.html create mode 100644 docs/java/src-html/gust/backend/model/OperationOptions.html create mode 100644 docs/java/src-html/gust/backend/model/PersistenceDriver.Internals.html create mode 100644 docs/java/src-html/gust/backend/model/PersistenceDriver.html create mode 100644 docs/java/src-html/gust/backend/model/PersistenceException.html create mode 100644 docs/java/src-html/gust/backend/model/PersistenceFailure.html create mode 100644 docs/java/src-html/gust/backend/model/PersistenceManager.html create mode 100644 docs/java/src-html/gust/backend/model/PersistenceOperationFailed.html create mode 100644 docs/java/src-html/gust/backend/model/ProtoModelCodec.html create mode 100644 docs/java/src-html/gust/backend/model/SerializedModel.html create mode 100644 docs/java/src-html/gust/backend/model/Transaction.html create mode 100644 docs/java/src-html/gust/backend/model/UpdateOptions.html create mode 100644 docs/java/src-html/gust/backend/model/WriteOptions.WriteDisposition.html create mode 100644 docs/java/src-html/gust/backend/model/WriteOptions.html create mode 100644 docs/java/src-html/gust/backend/model/WriteProxy.html create mode 100644 docs/java/src-html/gust/backend/runtime/AssetManager.ManagedAsset.html create mode 100644 docs/java/src-html/gust/backend/runtime/AssetManager.ManagedAssetContent.html create mode 100644 docs/java/src-html/gust/backend/runtime/AssetManager.ModuleType.html create mode 100644 docs/java/src-html/gust/backend/runtime/AssetManager.html create mode 100644 docs/java/src-html/gust/backend/runtime/Logging.html create mode 100644 docs/java/src-html/gust/backend/runtime/ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription.html create mode 100644 docs/java/src-html/gust/backend/runtime/ReactiveFuture.CompletableFuturePublisher.html create mode 100644 docs/java/src-html/gust/backend/runtime/ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription.html create mode 100644 docs/java/src-html/gust/backend/runtime/ReactiveFuture.ListenableFuturePublisher.html create mode 100644 docs/java/src-html/gust/backend/runtime/ReactiveFuture.PublisherListenableFuture.html create mode 100644 docs/java/src-html/gust/backend/runtime/ReactiveFuture.html create mode 100644 docs/java/src-html/gust/backend/transport/GoogleAPIChannel.html create mode 100644 docs/java/src-html/gust/backend/transport/GoogleService.html create mode 100644 docs/java/src-html/gust/backend/transport/GoogleTransportConfig.html create mode 100644 docs/java/src-html/gust/backend/transport/GoogleTransportManager.html create mode 100644 docs/java/src-html/gust/backend/transport/GrpcTransportConfig.html create mode 100644 docs/java/src-html/gust/backend/transport/GrpcTransportCredentials.html create mode 100644 docs/java/src-html/gust/backend/transport/PooledTransportConfig.html create mode 100644 docs/java/src-html/gust/backend/transport/TransportConfig.html create mode 100644 docs/java/src-html/gust/backend/transport/TransportCredentials.html create mode 100644 docs/java/src-html/gust/backend/transport/TransportException.html create mode 100644 docs/java/src-html/gust/backend/transport/TransportManager.html create mode 100644 docs/java/src-html/gust/util/Hex.html create mode 100644 docs/java/src-html/gust/util/InstantFactory.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.Builder.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.DefaultFieldComparator.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.FieldComparator.ComparisonResult.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.FieldComparator.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.FloatComparison.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.IgnoreCriteria.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.MapKeyComparator.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.MessageFieldComparison.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.RepeatedFieldComparison.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.ReportType.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.Reporter.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.Scope.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.SpecificField.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.StreamReporter.StreamException.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.StreamReporter.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.UnknownDescriptor.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.UnknownFieldType.html create mode 100644 docs/java/src-html/gust/util/MessageDifferencer.html create mode 100644 docs/java/src-html/gust/util/Pair.html create mode 100644 docs/java/stylesheet.css create mode 100644 docs/java/type-search-index.js create mode 100644 docs/java/type-search-index.zip create mode 100644 java/gust/backend/annotations/package-info.java create mode 100644 java/gust/backend/driver/spanner/BUILD.bazel create mode 100644 java/gust/backend/driver/spanner/SpannerAdapter.java create mode 100644 java/gust/backend/driver/spanner/SpannerCodec.java create mode 100644 java/gust/backend/driver/spanner/SpannerDriver.java create mode 100644 java/gust/backend/driver/spanner/SpannerDriverSettings.java create mode 100644 java/gust/backend/driver/spanner/SpannerGeneratedDDL.java create mode 100644 java/gust/backend/driver/spanner/SpannerManager.java create mode 100644 java/gust/backend/driver/spanner/SpannerMutationSerializer.java create mode 100644 java/gust/backend/driver/spanner/SpannerStructDeserializer.java create mode 100644 java/gust/backend/driver/spanner/SpannerTemporalConverter.java create mode 100644 java/gust/backend/driver/spanner/SpannerTransportConfig.java create mode 100644 java/gust/backend/driver/spanner/SpannerUtil.java create mode 100644 java/gust/backend/driver/spanner/package-info.java create mode 100644 javatests/gust/backend/driver/spanner/BUILD.bazel create mode 100644 javatests/gust/backend/driver/spanner/SpannerAdapterTest.java create mode 100644 javatests/gust/backend/driver/spanner/SpannerDDLTest.java create mode 100644 javatests/gust/backend/driver/spanner/SpannerManagerTest.java create mode 100644 javatests/gust/backend/driver/spanner/SpannerTemporalConverterTest.java create mode 100644 javatests/gust/backend/driver/spanner/SpannerUtilTest.java create mode 100644 renovate.json create mode 100755 tools/container-init.sh diff --git a/.bazelproject b/.bazelproject index 6a7c39f69..ace9095a9 100644 --- a/.bazelproject +++ b/.bazelproject @@ -22,8 +22,16 @@ targets: //javatests/... -//jstests/... +build_flags: + --config=dev + --config=labs + --define=project=elide-ai + ts_config_rules: //:tsconfig.json + //defs/toolchain/ts:tsconfig-common + //defs/toolchain/ts:tsconfig-node + //defs/toolchain/ts:tsconfig-browser test_sources: - */tests/* diff --git a/.bazelversion b/.bazelversion index ee74734aa..fae6e3d04 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -4.1.0 +4.2.1 diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index a5155f22a..201c89c13 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -13,16 +13,40 @@ steps: - command: "make build CI=yes QUIET=no" - label: ":bazel: Build: Framework" - key: framework-build + label: ":bazel: Build: Framework (Linux)" + key: framework-build-linux + agents: + "gcp:project-id": "cookies-co" - - command: "make build test report-coverage report-tests CI=yes QUIET=no" - label: ":coverage: Testsuite" - depends_on: framework-build + - command: "make build CI=yes QUIET=no" + label: ":bazel: Build: Framework (macOS)" + key: framework-build-macos + agents: + os_flavor: "macos" + + # report-coverage report-tests + - command: "make test report-coverage CI=yes QUIET=no" + label: ":coverage: Testsuite (Linux)" + depends_on: framework-build-linux + env: + CODECOV_TOKEN: "8942c419-eb75-46a4-8f08-abfaadd11bb5" + CC_TEST_REPORTER_ID: "9d5646c30c5e1f267b4a7a6ae4d6aedb22edc797e87845564b812812fff81f3c" + artifact_paths: + - "reports/coverage/index.html" + - "reports/coverage.tar.gz" + - "reports/tests.xml.html" + agents: + "gcp:project-id": "cookies-co" + + - command: "make test CI=yes QUIET=no" + label: ":coverage: Testsuite (macOS)" + depends_on: framework-build-macos artifact_paths: - "reports/coverage/index.html" - "reports/coverage.tar.gz" - "reports/tests.xml.html" + agents: + os_flavor: "macos" - command: "make build CI=yes TARGETS='//samples/rest_mvc/java:MicronautMVCSample-native-bin' && make build CI=yes TARGETS='//samples/soy_ssr/src:MicronautSSRSample-native-bin'" label: ":java: Build: Native Binaries" @@ -35,7 +59,14 @@ steps: - wait - command: "docker pull us.gcr.io/elide-ai/base/alpine" - label: ":docker: Pull Bases" + label: ":docker: Pull Bases: Alpine" + agents: + "gcp:project-id": "cookies-co" + + - command: "docker pull us.gcr.io/elide-ai/base/node" + label: ":docker: Pull Bases: Node" + agents: + "gcp:project-id": "cookies-co" - command: "make build samples CI=yes" label: ":gcloud: Publish: Images" @@ -76,3 +107,4 @@ steps: label: ":docker: Release: Docker" depends_on: native-build if: build.tag != null + diff --git a/.codeclimate.yml b/.codeclimate.yml index c5f194956..79f0bcd57 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -45,6 +45,12 @@ plugins: config: file: "tools/checkstyle.xml" checks: + com.puppycrawl.tools.checkstyle.checks.coding.VariableDeclarationUsageDistanceCheck: + enabled: false + com.puppycrawl.tools.checkstyle.checks.naming.ClassTypeParameterNameCheck: + enabled: false + com.puppycrawl.tools.checkstyle.checks.naming.MethodTypeParameterNameCheck: + enabled: false com.puppycrawl.tools.checkstyle.checks.coding.OneStatementPerLineCheck: enabled: false com.puppycrawl.tools.checkstyle.checks.whitespace.EmptyLineSeparatorCheck: @@ -123,6 +129,10 @@ plugins: sonar-java: enabled: true checks: + squid:S00107: + enabled: false + squid:S3358: + enabled: false squid:S3776: enabled: false squid:SwitchLastCaseIsDefaultCheck: @@ -159,4 +169,11 @@ exclude_patterns: - "**/vendor/" - "**/*_test.go" - "**/*.d.ts" +- "docs/" +- "docs/**.*" +- "tests/" +- "tests/**.*" +- "*tests/**.*" +- "javatests/" +- "jstests/" diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..5d9ec0392 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,12 @@ + +FROM us-docker.pkg.dev/cookies-eng/public/base/codespaces:v1h + +ENV SHELL=zsh \ + HOME=/home/dev \ + DEVCONTAINER=yes + +USER dev +WORKDIR /home/dev + +COPY defs/container.bazelrc /home/dev/bazelrc.remote + diff --git a/.devcontainer/Dockerfile.gitpod b/.devcontainer/Dockerfile.gitpod new file mode 100644 index 000000000..1691efecb --- /dev/null +++ b/.devcontainer/Dockerfile.gitpod @@ -0,0 +1,14 @@ + +FROM us-docker.pkg.dev/cookies-eng/public/base/gitpod:latest + +ENV SHELL=zsh \ + C6S_CODESPACES_VERSION=v1a \ + HOME=/home/gitpod \ + DEVCONTAINER=yes + +RUN echo "Installing BuildBuddy credentials..." \ + && echo "$BUILDBUDDY_CERT" > /home/dev/buildbuddy-cert.pem \ + && echo "$BUILDBUDDY_KEY" > /home/dev/buildbuddy-key.pem; + +COPY defs/container.bazelrc /home/gitpod/.bazelrc + diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..2fbd2ce6c --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,31 @@ +{ + "build": { + "dockerfile": "./Dockerfile", + "context": ".." + }, + "containerEnv": { + "DEVCONTAINER": "yes", + "HOME": "/home/dev", + "GOOGLE_APPLICATION_CREDENTIALS": "/home/dev/.config/gcloud/application_default_credentials.json" + }, + "postCreateCommand": "bash tools/container-init.sh", + "postAttachCommand": "make help", + "containerUser": "dev", + "extensions": [ + "BazelBuild.vscode-bazel", + "bufbuild.vscode-buf", + "redhat.java", + "redhat.vscode-commons", + "redhat.vscode-yaml", + "StackBuild.bazel-stack-vscode", + "TeamHub.teamhub", + "VisualStudioExptTeam.vscodeintellicode", + "vscjava.vscode-java-debug", + "vscjava.vscode-java-dependency", + "vscjava.vscode-java-pack", + "vscjava.vscode-java-test", + "vscjava.vscode-maven", + "zhengrenzhe.nyan-cat", + "zxh404.vscode-proto3" + ] +} diff --git a/.ijwb/.run/Bazel Testsuite.run.xml b/.ijwb/.run/Bazel Testsuite.run.xml new file mode 100644 index 000000000..a1320a68e --- /dev/null +++ b/.ijwb/.run/Bazel Testsuite.run.xml @@ -0,0 +1,13 @@ + + + + //javatests:suite + --config=dev + --combined_report=lcov + --test_output=errors + + + + + \ No newline at end of file diff --git a/Makefile b/Makefile index 8bf21f997..b0db1efdb 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,7 @@ ENABLE_REPORTCI ?= yes JS_COVERAGE_REPORT ?= no REPORTS ?= reports CI_REPO ?= sgammon/elide +DEVCONTAINER ?= $(shell echo $$DEVCONTAINER) SAMPLES ?= //samples/rest_mvc/java:MicronautMVCSample //samples/soy_ssr/src:MicronautSSRSample @@ -54,13 +55,12 @@ OUTPATH ?= $(DISTPATH)/out BINPATH ?= $(DISTPATH)/bin UNZIP ?= $(shell which unzip) REVISION ?= $(shell git describe --abbrev=7 --always --tags HEAD) -BASE_VERSION ?= v1b +BASE_VERSION ?= v2a VERSION ?= $(shell (cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[",]//g' | tr -d '[[:space:]]' | sed 's/version\://g')) CHROME_COVERAGE ?= $(shell find dist/out/$(OUTPUT_BASE)/bin -name "coverage*.dat" | grep chrome | xargs) COVERAGE_DATA ?= $(OUTPATH)/_coverage/_coverage_report.dat COVERAGE_REPORT ?= $(REPORTS)/coverage COVERAGE_ARGS ?= --function-coverage \ - --branch-coverage \ --highlight \ --demangle-cpp \ --show-details \ @@ -74,7 +74,7 @@ COVERAGE_ARGS ?= --function-coverage \ APP ?= TARGETS ?= //java/... //gust/... //style/... LABS_TARGETS ?= //js/... -TESTS ?= //javatests:suite +TESTS ?= //javatests:suite //tests:suite LABS_TESTS ?= //jstests/... COVERABLE ?= $(TESTS) @@ -168,15 +168,29 @@ LN ?= $(shell which ln) CURL ?= $(shell which curl) CHMOD ?= $(shell which chmod) MKDIR ?= $(shell which mkdir) -BAZELISK ?= $(ENV)/bin/bazelisk GENHTML ?= $(shell which genhtml) +DOCKER ?= $(shell which docker) + +ifeq ($(DEVCONTAINER),yes) +BAZELISK ?= $(shell which bazelisk) +IBAZEL ?= $(shell which ibazel) +else +BAZELISK ?= $(ENV)/bin/bazelisk IBAZEL ?= $(ENV)/bin/ibazel +endif + +PYTHON ?= $(shell which python) +VIRTUALENV ?= $(shell which virtualenv) # Flag: `CI` ifeq ($(CI),yes) TAG += --config=ci else -TAG += --config=dev +ifeq ($(DEVCONTAINER),yes) +TAG += --config=dev --config=labs +else +TAG += --config=dev-local --config=labs +endif endif # Flag: `DEBUG` @@ -202,7 +216,11 @@ endif all: devtools build test ## Build and test all framework targets. +ifeq ($(DEVCONTAINER),yes) +build: +else build: $(ENV) ## Build all framework targets. +endif $(info Building $(PROJECT_NAME)...) $(_RULE)$(BAZELISK) $(BAZELISK_ARGS) build $(TAG) $(BASE_ARGS) $(BUILD_ARGS) -- $(TARGETS) @@ -218,11 +236,11 @@ clean: clean-docs clean-reports ## Clean ephemeral targets. clean-docs: ## Clean built documentation. @echo "Cleaning docs..." - $(_RULE)rm -fr $(POSIX_FLAGS) $(DOCS) + $(_RULE)rm -fr $(POSIX_FLAGS) $(DOCS)/java clean-reports: ## Clean built reports. @echo "Cleaning reports..." - $(_RULE) -fr $(POSIX_FLAGS) $(REPORTS) + $(_RULE)rm -fr $(POSIX_FLAGS) $(REPORTS) bases: ## Build base images and push them. @echo "Building Alpine base ('$(BASE_VERSION)')..." @@ -287,8 +305,7 @@ serve-coverage: ## Serve the current coverage report (must generate first). report-tests: ## Report test results to Report.CI. @echo "Scanning for test results..." - $(_RULE)pip install -r tools/requirements.txt - $(_RULE)find dist/out/$(OUTPUT_BASE) -name test.xml | xargs python3 tools/merge_test_results.py reports/tests.xml + $(_RULE)find dist/out/$(OUTPUT_BASE) -name test.xml | xargs $(ENV)/bin/python tools/merge_test_results.py reports/tests.xml @echo "Generating HTML test report..." $(_RULE)cd reports && python3 -m junit2htmlreport tests.xml ifeq ($(ENABLE_REPORTCI),yes) @@ -348,6 +365,10 @@ $(ENV): $(_RULE)$(LN) -s $(ENV)/bazel/ibazel-$(PLATFORM) $(ENV)/bin/ibazel $(_RULE)$(CHMOD) +x $(ENV)/bazel/bazelisk-$(PLATFORM) $(ENV)/bin/bazelisk $(ENV)/bazel/ibazel-$(PLATFORM) $(ENV)/bin/ibazel $(_RULE)$(ENV)/bin/bazelisk version + @echo "Creating virtualenv..." + $(_RULE)$(VIRTUALENV) $(ENV)/python --python=$(PYTHON) + $(_RULE)$(LN) -s $(ENV)/python/bin/python3 $(ENV)/bin/python + $(_RULE)$(ENV)/python/bin/pip install -r $(PWD)/tools/requirements.txt @echo "Build environment ready." ## Crypto @@ -366,5 +387,5 @@ decrypt: $(BUILDKEY_PLAINTEXT) ## Decrypt private key material. @echo "Key material decrypted." -.PHONY: build test help samples release-images update-deps devtools +.PHONY: build test help samples release-images update-deps devtools docs diff --git a/WORKSPACE b/WORKSPACE index e8f7ef720..da6bf80cd 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -91,7 +91,6 @@ java_import( ) - # # Extensions # @@ -147,7 +146,9 @@ graal_bindist_repository( ## Java Repos/Deps load("//defs/toolchain/java:repos.bzl", "gust_java_repositories") -gust_java_repositories() +gust_java_repositories( + maven_install_json = "@gust//:maven_install.json", +) load("@maven//:compat.bzl", "compat_repositories") compat_repositories() diff --git a/defs/build.bzl b/defs/build.bzl index 87a6600f8..f43d1849f 100644 --- a/defs/build.bzl +++ b/defs/build.bzl @@ -59,9 +59,9 @@ DEPS = { "type": "github", "forceLocal": False, "repo": "sgammon/rules_closure", - "target": "67418786ac081f0c78881ce4e0cca45b7cda521e", + "target": "8ac141c8c2e131639b7c5fa4eae7d5587ec221b8", "local": "/Users/sam.g/Workspace/rules_closure", - "seal": "f99161cea10cc8c274f979a1a9e10332fbfb6ac670c0ded7bfbf6f4d90007feb"}, + "seal": "cf2a565428d8244dea163e741ca98814281fdada4694aaa934688bcf51ba640d"}, # Rules: Protobuf "rules_proto": { @@ -169,38 +169,38 @@ DEPS = { # Google: J2CL (Java-to-Closure) "com_google_j2cl": { "type": "github", - "repo": "sgammon/j2cl", - "target": "5084fe6863ded1eb72fbec4ff654af48bc8abae8", + "repo": "google/j2cl", + "target": "4fda30b07470dfc87555847af14bdbba60a41774", "local": "/workspace/GUST/vendor/bazel/j2cl", - "seal": "7874cc5e560067e2fa6788a75596af7a7fbd690194b5e7286b2b9d11ea931db7"}, + "seal": "4518030355ae19e7ba991978ed32e061edda05ea1abd8b1a1fae930596ee29b0"}, # Google: Elemental2 "com_google_elemental2": { "type": "github", - "repo": "sgammon/elemental2", - "target": "35295fcb16770fa6b424be14e2dbbcc01b964af5", - "seal": "47fa516661b3b6fe1ebaa35210726642b0a4bfbf0a7fa22eba9a94c9715bd826"}, + "repo": "google/elemental2", + "target": "281ab6c570b4d000d6786b2d8ef54e9d0169391a", + "seal": "bd67f90b779773a49baf2786063ad050a716dd31cd3381ab267c76f5a30d11b3"}, # Google: JS Interop (Base) "com_google_jsinterop_base": { "type": "github", "repo": "google/jsinterop-base", - "target": "d63376592856ef4dfd3ef68500df9745cd8c6919", - "seal": "b22b91d64f963fa7cc1fcf99430139de74306fd25ff4ef2da5f0100dae67efba"}, + "target": "fffe9c4de072e812fa6d4af385bda3969fb692fa", + "seal": "cd807fb9457db9d07aaad73e7cb16595d3a14a96de1c29e4f185751e381a0712"}, # Google: JS Interop (Generator) "com_google_jsinterop_generator": { "type": "github", "repo": "google/jsinterop-generator", - "target": "4881aa1b210d3a44db9789e1a7079679b6a4d0af", - "seal": "87cf682f565d92f3e1f20b7d0e7b7d2b2b6593deec8ab1ded4d796c1dc72fc34"}, + "target": "e4248fa7c99ee65fc797ea20307d7edbd0dd715a", + "seal": "c05d0978f487d5aa65986caece052d877b9a9576a15f915b463a53249e57b0a2"}, # Google: JS Interop (Generator) "com_google_jsinterop_annotations": { "type": "github", "repo": "google/jsinterop-annotations", - "target": "04bda45586e2a7e0ef5a02f908b828f5da6747af", - "seal": "483d3d18ace60a8e62796abef374d472cfeecf233ef1b6f384a47fb99e4bb23f"}, + "target": "b5e8b46e46a68f030e63c53140072334c4e7ee9d", + "seal": "0ac20baedf3590140fb9382a21ccd44ba1fbcea718e778639ce5e20d903c8629"}, # Google: API (Core) "com_google_api": { @@ -213,8 +213,8 @@ DEPS = { "com_google_api_codegen": { "type": "github", "repo": "googleapis/gapic-generator", - "target": "9da693477658964878d4dd5a3f4d5e0d197950db", - "seal": "8a6c58021565dd7d02d07beb0f5851f0679ac99d540034a132a759959d3a8269"}, + "target": "857e2aab8017ecbb2abc9adc02d8571c02f94b3b", + "seal": "bdaa14564a7eb408f98ba788018eb5d9a5330aaa5b5cfcdd36f3f7fe4477b489"}, # BuildStack: Protobuf Rules "build_stack_rules_proto": { @@ -235,9 +235,9 @@ DEPS = { "proto_common": { "type": "github", "repo": "googleapis/api-common-protos", - "target": "0fcae75a2c20a140137e3a9c48a87d15ceffabd7", + "target": "f37c0ecc55f19b1675448e4bad70fd45c93f5b8f", "overlay": "proto_common.bzl", - "seal": "3cc5d56cf02dbf2e41022d84135cff48d0720d229b13806a6fc799b2ad3693c5"}, + "seal": "20f5e27c83f417c34bf36f38686435dd9e5ca454bef47c08c7d359c37ae26fcf"}, # Safe HTML Types "safe_html_types": { @@ -258,17 +258,17 @@ DEPS = { "io_grpc_java": { "type": "github", "repo": "grpc/grpc-java", - "target": "c40e2dcb0b0ee0f1bba73e59ac812ee3625a9fce", - "seal": "97e2b57f13a08180fcf5504c851744e21f0434003179c761a98c81ea6d253361"}, + "target": "6cb4d90e6bbe0081d42d88bc83a8f4648087596e", + "seal": "ba7b097d3e151cc0fcdd3e1c48d487eafa2acde258bfb044d8549f48c5c50940"}, # gRPC: Web "com_github_grpc_grpc_web": { "type": "github", - "repo": "sgammon/grpc-web", + "repo": "grpc/grpc-web", "forceLocal": False, "local": "/workspace/GUST/vendor/grpc/web", - "target": "6aa18295f2f6dd6e9a608e2362f5ddcbe6e69ee1", - "seal": "42460855313b61b1b4f9bfef0723e6735cd3735234e03d74f5199e8b88ec10ed"}, + "target": "3d921fffb7ce29f03329e411711d0751328c761a", + "seal": "fc12f7cea2b3a5e52f231348d99709006c344652d4511117157ee7484ee42ae9"}, # Compression: Brotli "org_brotli": { @@ -296,8 +296,8 @@ DEPS = { "type": "github", "repo": "firebase/firebase-js-sdk", "overlay": "firebase.bzl", - "target": "9d593bc72fcc6f695ed3666525d0638dfdf50b62", - "seal": "f298860e52321aef52d62d4e6df6c8f55b522f25eac1fc3e73b89632966b4f83"}, + "target": "c9560d3103032caa6dcd2fd6daed2b119bd7b713", + "seal": "c9560d3103032caa6dcd2fd6daed2b119bd7b713"}, # Firebase: Java SDK "com_google_firebase_java": { @@ -327,8 +327,8 @@ DEPS = { "com_google_closure_stylesheets": { "type": "java", "licenses": ["notice"], - "targets": ["https://storage.googleapis.com/elide-software/closure-stylesheets-1.6.0-b15.jar"], - "seal": "190bc72d4a29f751d10dbd34125b8d35317b5db303ba37cd4d5f273c1499ec47", + "targets": ["https://storage.googleapis.com/elide-software/closure-stylesheets-1.6.0-b16.jar"], + "seal": "142123dc2c1c56c085380cd4d5b7c37965754a1805d0f30cfd5e8bf37c0a6ca0", "deps": [ "@args4j", "@com_google_javascript_closure_compiler", diff --git a/defs/container.bazelrc b/defs/container.bazelrc new file mode 100644 index 000000000..181522528 --- /dev/null +++ b/defs/container.bazelrc @@ -0,0 +1,40 @@ + +build --experimental_ui_mode=oldest_actions +build --show_timestamps +build --action_env=GOOGLE_DEFAULT_CREDENTIALS + +## Config: BuildBuddy +build --auth_enabled=true +build --bes_results_url=https://app.buildbuddy.io/invocation/ +build --bes_backend=grpcs://cloud.buildbuddy.io +build --remote_cache=grpcs://cloud.buildbuddy.io +build --remote_timeout=3600 +build --tls_client_certificate=~/buildbuddy-cert.pem +build --tls_client_key=~/buildbuddy-key.pem +build --experimental_docker_image=gcr.io/cloud-marketplace/google/rbe-ubuntu18-04@sha256:48b67b41118dbcdfc265e7335f454fbefa62681ab8d47200971fc7a52fb32054 + +build --jobs=50 + +build:rbe --remote_executor=grpcs://cloud.buildbuddy.io +build:rbe --spawn_strategy=remote,sandboxed +build:rbe --strategy=Javac=remote +build:rbe --strategy=Closure=remote +build:rbe --strategy=Genrule=remote +build:rbe --define=EXECUTOR=remote + +build --google_default_credentials=true + +build:rbe --javabase=@rbe_jdk11//java:jdk +build:rbe --host_javabase=@rbe_jdk11//java:jdk +build:rbe --crosstool_top=@rbe_jdk11//cc:toolchain +build:rbe --extra_toolchains=@rbe_jdk11//config:cc-toolchain +build:rbe --extra_execution_platforms=@rbe_jdk11//config:platform +build:rbe --host_platform=@rbe_jdk11//config:platform +build:rbe --platforms=@rbe_jdk11//config:platform +build:rbe --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 +build:rbe --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_java11 +build:rbe --java_toolchain=@bazel_tools//tools/jdk:toolchain_java11 + +build --build_metadata=REPO_URL=https://github.com/CookiesCo/elide.git +build --build_metadata=BUILDKITE_REPO=cookies/elide +build:ci --build_metadata=ROLE=CI diff --git a/defs/toolchain/java/repos.bzl b/defs/toolchain/java/repos.bzl index 0b8d094cc..0b927620f 100644 --- a/defs/toolchain/java/repos.bzl +++ b/defs/toolchain/java/repos.bzl @@ -54,9 +54,9 @@ VALIDATION_VERSION = "2.0.0.Final" JUNIT_JUPITER_VERSION = "5.6.0" JUNIT_PLATFORM_VERSION = "1.6.0" -GAX_VERSION = "1.66.0" -NETTY_VERSION = "4.1.52.Final" -NETTY_BORINGSSL_VERSION = "2.0.34.Final" +GAX_VERSION = "2.5.0" +NETTY_VERSION = "4.1.66.Final" +NETTY_BORINGSSL_VERSION = "2.0.40.Final" RXJAVA_VERSION = "2.2.21" PICOCLI_VERSION = "4.2.0" REACTIVE_VERSION = "1.0.3" @@ -68,7 +68,8 @@ GCLOUD_GRPC_VERSION = "1.95.4" GCLOUD_TASKS_VERSION = "1.33.2" GCLOUD_PUBSUB_VERSION = "1.113.5" GCLOUD_STORAGE_VERSION = "1.118.0" -GCLOUD_FIRESTORE_VERSION = "2.6.1" +GCLOUD_FIRESTORE_VERSION = "2.6.2" +GCLOUD_SPANNER_VERSION = "6.13.0" GCLOUD_MONITORING_VERSION = "2.3.4" COMMON_PROTOS_VERSION = "2.3.2" KOTLIN_TEST_VERSION = "3.4.2" @@ -83,20 +84,28 @@ GRPC_JAVA_VERSION = "1.38.1" AUTO_VALUE_VERSION = "1.8.1" TOMCAT_ANNOTATIONS_VERSION = "6.0.53" OPENTRACING_VERSION = "0.2.3" +GOOGLE_TRUTH_VERSION = "1.1.3" -MICRONAUT_VERSION = "3.0.0-M2" -MICRONAUT_DATA_VERSION = "2.4.7" +MICRONAUT_VERSION = "2.5.12" +MICRONAUT_DATA_VERSION = "2.5.0" MICRONAUT_CONTEXT_VERSION = MICRONAUT_VERSION -MICRONAUT_GRPC_VERSION = "3.0.0.RC1" -MICRONAUT_TEST_VERSION = "2.3.4" -MICRONAUT_REDIS_VERSION = "4.0.2" -MICRONAUT_CACHE_VERSION = "3.0.0.RC1" -MICRONAUT_SECURITY_VERSION = "3.0.0-M1" +MICRONAUT_GRPC_VERSION = "2.5.0" +MICRONAUT_TEST_VERSION = "2.3.7" +MICRONAUT_REDIS_VERSION = "4.0.3" +MICRONAUT_CACHE_VERSION = "2.4.0" +MICRONAUT_SECURITY_VERSION = "2.5.0" MICRONAUT_MULTITENANCY_VERSION = "2.2.3" TESTCONTAINERS_VERSION = "1.15.3" TRUTH_VERSION = "1.1.3" +KOTLIN_EXCLUSIONS = [ + "org.jetbrains.kotlin:kotlin-stdlib", + "org.jetbrains.kotlin:kotlin-stdlib-jdk7", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8", + "org.jetbrains.kotlin:kotlin-stdlib-common", +] + GRPC_EXCLUSIONS = [ maven.exclusion( group = "io.grpc", @@ -211,7 +220,7 @@ GRPC_BUILD_ARTIFACTS = [ "io.grpc:grpc-stub:%s" % GRPC_JAVA_VERSION, "io.grpc:grpc-context:%s" % GRPC_JAVA_VERSION, "io.grpc:grpc-protobuf:%s" % GRPC_JAVA_VERSION, - "io.grpc:grpc-netty:%s" % GRPC_JAVA_VERSION, +# "io.grpc:grpc-netty:%s" % GRPC_JAVA_VERSION, "io.grpc:grpc-netty-shaded:%s" % GRPC_JAVA_VERSION, "com.google.api.grpc:proto-google-common-protos:%s" % COMMON_PROTOS_VERSION, "com.google.api.grpc:grpc-google-common-protos:%s" % COMMON_PROTOS_VERSION, @@ -275,6 +284,7 @@ MICRONAUT_COORDINATES = [ "io.micronaut:micronaut-graal", "io.micronaut:micronaut-router", "io.micronaut:micronaut-tracing", + "io.micronaut:micronaut-management", "io.micronaut:micronaut-messaging", "io.micronaut:micronaut-websocket", "io.micronaut:micronaut-session", @@ -292,6 +302,7 @@ MICRONAUT_EXTRAS = [ ("io.netty:netty-handler", NETTY_VERSION), ("io.netty:netty-handler-proxy", NETTY_VERSION), ("io.micronaut:micronaut-context", MICRONAUT_CONTEXT_VERSION), + ("io.netty:netty-tcnative", NETTY_BORINGSSL_VERSION), ("io.netty:netty-tcnative-boringssl-static", NETTY_BORINGSSL_VERSION), ("io.micronaut:micronaut-multitenancy", MICRONAUT_MULTITENANCY_VERSION), ("io.micronaut.security:micronaut-security", MICRONAUT_SECURITY_VERSION), @@ -337,6 +348,7 @@ GOOGLE_CLOUD_COORDINATES = [ ("com.google.cloud:google-cloud-tasks", GCLOUD_TASKS_VERSION), ("com.google.cloud:google-cloud-pubsub", GCLOUD_PUBSUB_VERSION), ("com.google.cloud:google-cloud-storage", GCLOUD_STORAGE_VERSION), + ("com.google.cloud:google-cloud-spanner", GCLOUD_SPANNER_VERSION), ("com.google.cloud:google-cloud-firestore", GCLOUD_FIRESTORE_VERSION), ("com.google.api.grpc:proto-google-cloud-firestore-v1", GCLOUD_FIRESTORE_VERSION), ("com.google.cloud:google-cloud-monitoring", GCLOUD_MONITORING_VERSION), @@ -362,6 +374,10 @@ MICRONAUT_TEST_ARTIFACTS = [ # maven.artifact("io.kotlintest", "kotlintest-runner-jvm", KOTLIN_TEST_VERSION, testonly = True), # maven.artifact("io.kotlintest", "kotlintest-runner-junit5", KOTLIN_TEST_VERSION, testonly = True), # maven.artifact("io.kotlintest", "kotlintest-runner-console", KOTLIN_TEST_VERSION, testonly = True), + maven.artifact("com.google.truth", "truth", GOOGLE_TRUTH_VERSION, testonly = True), + maven.artifact("com.google.truth.extensions", "truth-proto-extension", GOOGLE_TRUTH_VERSION, testonly = True), + maven.artifact("com.google.truth.extensions", "truth-liteproto-extension", GOOGLE_TRUTH_VERSION, testonly = True), + maven.artifact("com.google.truth.extensions", "truth-java8-extension", GOOGLE_TRUTH_VERSION, testonly = True), maven.artifact("io.micronaut.test", "micronaut-test-core", MICRONAUT_TEST_VERSION, testonly = True), maven.artifact("io.micronaut.test", "micronaut-test-kotlintest", MICRONAUT_TEST_VERSION, testonly = True), maven.artifact("io.micronaut.test", "micronaut-test-junit5", MICRONAUT_TEST_VERSION, testonly = True), @@ -416,7 +432,7 @@ def _format_maven_jar_dep_name(group_id, artifact_id): return "@%s//jar" % _format_maven_jar_name(group_id, artifact_id) -def _gust_java_deps( +def gust_java_repositories( app_artifacts = [], app_repositories = [], app_fetch_sources = True, @@ -464,7 +480,7 @@ def _gust_java_deps( ("io.micronaut:micronaut-views", "@io_micronaut_micronaut_views"), ("io.micronaut:micronaut-views-soy", "@io_micronaut_micronaut_views_soy"), ("com.google.guava:guava", "@com_google_guava"), - ("com.google.guava", "@com_google_guava"), + ("com.google.guava:guava-jdk15", "@com_google_guava"), ("com.google.protobuf:protobuf-java", "@com_google_protobuf//:protobuf_java"), ("com.google.protobuf:protobuf-javalite", "@com_google_protobuf//:protobuf_javalite"), ("com.google.protobuf:protobuf-java-util", "@com_google_protobuf//:protobuf_java_util"), @@ -475,6 +491,7 @@ def _gust_java_deps( ("com.google.grpc:grpc-protobuf", "@io_grpc_java//protobuf:protobuf"), ("com.google.grpc:grpc-context", "@io_grpc_java//context:context"), ("com.google.grpc:grpc-netty", "@io_grpc_java//netty:netty"), + ("com.google.grpc:grpc-netty", "@io_grpc_java//netty-shaded:netty-shaded"), ("com.google.grpc:grpc-netty-shaded", "@io_grpc_java//netty-shaded:netty-shaded"), ("com.google.template:soy", "@com_google_template_soy"), ("com.google.common.html.types:types", "@safe_html_types//:java"), @@ -526,6 +543,5 @@ ALL_DEPENDENCIES = (_clean_versions( OVERRIDE_DEPS) -gust_java_repositories = _gust_java_deps format_maven_jar_name = _format_maven_jar_name format_maven_jar_dep_name = _format_maven_jar_dep_name diff --git a/defs/toolchain/java/rules.bzl b/defs/toolchain/java/rules.bzl index e3f22fc38..c52038515 100644 --- a/defs/toolchain/java/rules.bzl +++ b/defs/toolchain/java/rules.bzl @@ -164,7 +164,6 @@ INJECTED_CONTROLLER_EXPORTS = [ ] _JVM_APP_DEBUG_FLAGS = [ - "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:%s" % _JVM_DEBUG_PORT, "-Dgust.debug=true", "-Dgust.mode=debug", ] @@ -244,10 +243,19 @@ def _jdk_binary(name, jvm_flags = [], resource_jars = [], tags = [], + enable_debug = True, + suspend_debug = False, **kwargs): """ Generate a JDK binary, with built-in support for Kotlin. """ + debugger_support = [ + "-agentlib:jdwp=transport=dt_socket,server=y,suspend=%s,address=*:%s" % ( + (suspend_debug and "y") or "n", + _JVM_DEBUG_PORT + ), + ] + if len(srcs) > 0 and srcs[0].endswith(".kt"): # process as kotlin _ensure_types(srcs, ".kt") @@ -258,11 +266,11 @@ def _jdk_binary(name, data = data + INJECTED_LIBRARIES, resource_jars = resource_jars + INJECTED_RESOURCE_JARS, jvm_flags = select({ - "@gust//defs/config:live_reload": ["-DLIVE_RELOAD=enabled"] + INJECTED_JVM_FLAGS + jvm_flags, - "//conditions:default": INJECTED_JVM_FLAGS + jvm_flags, - }) + select({ + "@gust//defs/config:live_reload": ( + ["-DLIVE_RELOAD=enabled"] + INJECTED_JVM_FLAGS + jvm_flags + debugger_support), "@gust//defs/config:release": _JVM_APP_RELEASE_FLAGS, - "@gust//defs/config:debug": _JVM_APP_DEBUG_FLAGS, + "@gust//defs/config:debug": (INJECTED_JVM_FLAGS + jvm_flags + debugger_support), + "//conditions:default": (INJECTED_JVM_FLAGS + jvm_flags), }), tags = [ "ibazel_live_reload", @@ -285,7 +293,7 @@ def _jdk_binary(name, "//conditions:default": INJECTED_JVM_FLAGS + jvm_flags, }) + select({ "@gust//defs/config:release": _JVM_APP_RELEASE_FLAGS, - "@gust//defs/config:debug": _JVM_APP_DEBUG_FLAGS, + "@gust//defs/config:debug": _JVM_APP_DEBUG_FLAGS + (enable_debug and debugger_support or []), "//conditions:default": _JVM_APP_DEBUG_FLAGS, }), tags = [ @@ -441,7 +449,7 @@ native_tools = native def _micronaut_application(name, - native = False, + enable_native = False, main_class = "gust.backend.Application", config = str(Label("@gust//java/gust:application.yml")), template_loader = str(Label("@gust//java/gust/backend:TemplateProvider")), @@ -454,6 +462,7 @@ def _micronaut_application(name, native_configsets = [], registry = "us.gcr.io", image_format = "OCI", + image_name = None, srcs = [], controllers = [], services = [], @@ -474,6 +483,8 @@ def _micronaut_application(name, css_modules = {}, classpath_resources = [], enable_renaming = False, + enable_debug = True, + suspend_debug = False, **kwargs): """ Wraps a regular JDK application with injected Micronaut dependencies and plugins. """ @@ -552,7 +563,7 @@ def _micronaut_application(name, computed_deps = _dedupe_deps((deps or []) + INJECTED_MICRONAUT_DEPS + controllers + services) computed_image_deps = _dedupe_deps((deps or []) + INJECTED_MICRONAUT_DEPS) computed_image_layers = _dedupe_deps(( - INJECTED_MICRONAUT_RUNTIME_DEPS + [template_loader] + [":%s-assets" % name] + controllers + services)) + INJECTED_MICRONAUT_RUNTIME_DEPS + [template_loader] + [":%s-assets" % name] + controllers + services + (runtime_deps or []))) computed_runtime_deps = [template_loader] if inject_main: @@ -563,6 +574,7 @@ def _micronaut_application(name, computed_image_layers = [] computed_runtime_deps = _dedupe_deps( (deps or []) + + (runtime_deps or []) + INJECTED_MICRONAUT_DEPS + INJECTED_CONTROLLERS + services + @@ -580,7 +592,7 @@ def _micronaut_application(name, computed_runtime_deps.append("@gust//java/gust/backend:backend") _java_image( - name = "%s-image" % name, + name = image_name or ("%s-image" % name), srcs = srcs, main_class = main_class, deps = computed_image_deps, @@ -596,6 +608,12 @@ def _micronaut_application(name, ], ) + if image_name: + native.alias( + name = "%s-image" % name, + actual = image_name, + ) + _java_library( name = "%s-lib" % name, srcs = srcs, @@ -611,7 +629,7 @@ def _micronaut_application(name, resource_strip_prefix = "java/gust" in config and "java/gust/" or None, ) - if native: + if enable_native: _graal_binary( name = "%s-native" % name, deps = _dedupe_deps(["%s-lib" % name] + computed_runtime_deps), @@ -704,6 +722,8 @@ def _micronaut_application(name, main_class = main_class or "gust.backend.Application", jvm_flags = computed_jvm_flags, tags = (tags or []), + enable_debug = enable_debug, + suspend_debug = suspend_debug, **kwargs ) diff --git a/defs/toolchain/style/rules.bzl b/defs/toolchain/style/rules.bzl index 3fbcb988d..91a1dd0f1 100644 --- a/defs/toolchain/style/rules.bzl +++ b/defs/toolchain/style/rules.bzl @@ -113,7 +113,7 @@ def _style_binary(name, debug_state = select({ "@gust//defs/config:release": False, "@gust//defs/config:debug": True, - "//conditions:default": False + "//conditions:default": debug }) if src != None and (src.endswith(".sass") or src.endswith(".scss")): @@ -129,7 +129,7 @@ def _style_binary(name, _closure_css_library( name = "%s-lib" % name, - srcs = [":%s-sass" % name], + srcs = [":%s-inter.css" % name], ) _closure_css_binary( diff --git a/defs/toolchain/testing/file_test.bzl b/defs/toolchain/testing/file_test.bzl index fc4db5643..09e15fbba 100644 --- a/defs/toolchain/testing/file_test.bzl +++ b/defs/toolchain/testing/file_test.bzl @@ -29,10 +29,6 @@ def _impl(ctx): if content and matches != -1: fail("matches only makes sense with regexp") if not regexp: - # Make sure content ends with new file since sed will always add one in Mac - if content and not content.endswith("\n"): - content += "\n" - expected = ctx.actions.declare_file(exe.basename + ".expected") ctx.actions.write( output = expected, @@ -49,7 +45,7 @@ def _impl(ctx): ctx.actions.write( output = exe, - content = "diff -u %s %s" % (expected.short_path, actual.short_path), + content = "diff -w --strip-trailing-cr -u %s %s" % (expected.short_path, actual.short_path), is_executable = True, ) return struct(runfiles = ctx.runfiles([exe, expected, actual])) diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 000000000..7319fb51a --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +elide.cookies.engineering \ No newline at end of file diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 000000000..65eacce7d --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,2 @@ +markdown: kramdown +remote_theme: janczizikow/sleek diff --git a/docs/java/META-INF/MANIFEST.MF b/docs/java/META-INF/MANIFEST.MF new file mode 100644 index 000000000..03e7b8d4d --- /dev/null +++ b/docs/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Created-By: 11.0.11 (Azul Systems, Inc.) + diff --git a/docs/java/allclasses-index.html b/docs/java/allclasses-index.html new file mode 100644 index 000000000..3b588dab0 --- /dev/null +++ b/docs/java/allclasses-index.html @@ -0,0 +1,1034 @@ + + + + + +All Classes + + + + + + + + + + + + + +
+ +
+
+
+

All Classes

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/allclasses.html b/docs/java/allclasses.html new file mode 100644 index 000000000..790cf96e1 --- /dev/null +++ b/docs/java/allclasses.html @@ -0,0 +1,167 @@ + + + + + +All Classes + + + + + + + + + + + +

All Classes

+
+ +
+ + diff --git a/docs/java/allpackages-index.html b/docs/java/allpackages-index.html new file mode 100644 index 000000000..bf4cfa930 --- /dev/null +++ b/docs/java/allpackages-index.html @@ -0,0 +1,222 @@ + + + + + +All Packages + + + + + + + + + + + + + +
+ +
+
+
+

All Packages

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/constant-values.html b/docs/java/constant-values.html new file mode 100644 index 000000000..8b6fffb47 --- /dev/null +++ b/docs/java/constant-values.html @@ -0,0 +1,401 @@ + + + + + +Constant Field Values + + + + + + + + + + + + + +
+ +
+
+
+

Constant Field Values

+
+

Contents

+ +
+
+
+ + +
+

gust.backend.*

+
    +
  • + + + + + + + + + + + + + + + + + + + +
    gust.backend.ApplicationBoot 
    Modifier and TypeConstant FieldValue
    + +public static final java.lang.StringdefaultConfig"/gust/application.yml"
    + +public static final java.lang.StringrootConfig"/application.yml"
    +
  • +
  • + + + + + + + + + + + + + + +
    gust.backend.PageRender 
    Modifier and TypeConstant FieldValue
    + +public static final java.lang.StringPAGE_CONTEXT_IJ_NAME"page"
    +
  • +
+
    +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    gust.backend.annotations.Page 
    Modifier and TypeConstant FieldValue
    + +public static final java.lang.StringCANONICAL"canonicalUrl"
    + +public static final java.lang.StringCHANGE_FREQUENCY"changeFrequency"
    + +public static final java.lang.StringGOOGLEBOT_SETTINGS"googlebotSettings"
    + +public static final java.lang.StringLAST_MODIFIED"lastModified"
    + +public static final java.lang.StringNO_VALUE"NO_VALUE"
    + +public static final java.lang.StringPRIORITY"priority"
    + +public static final java.lang.StringROBOTS_ENABLE"enableIndexing"
    + +public static final java.lang.StringROBOTS_SETTINGS"robotsSettings"
    + +public static final java.lang.StringSITEMAP"sitemap"
    +
  • +
+ + +
    +
  • + + + + + + + + + + + + + + +
    gust.backend.transport.GoogleTransportManager 
    Modifier and TypeConstant FieldValue
    + +public static final java.lang.StringCONFIG_PREFIX"transport.google"
    +
  • +
  • + + + + + + + + + + + + + + +
    gust.backend.transport.TransportManager<A extends java.lang.annotation.Annotation,​E extends java.lang.Enum<E>,​C> 
    Modifier and TypeConstant FieldValue
    + +public static final java.lang.StringROOT_CONFIG_PREFIX"transport"
    +
  • +
+
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/deprecated-list.html b/docs/java/deprecated-list.html new file mode 100644 index 000000000..22f7d950b --- /dev/null +++ b/docs/java/deprecated-list.html @@ -0,0 +1,175 @@ + + + + + +Deprecated List + + + + + + + + + + + + + +
+ +
+
+
+

Deprecated API

+

Contents

+ +
+
+ + +
    +
  • + + + + + + + + + + + + +
    Methods 
    MethodDescription
    gust.backend.TemplateProvider.provideSoyFileSet() +
    Soy file sets are slow due to runtime template interpretation. Please use compiled templates via the + SoySauce class instead. See the see-also listings for this method for more information.
    +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/element-list b/docs/java/element-list new file mode 100644 index 000000000..ec12c2851 --- /dev/null +++ b/docs/java/element-list @@ -0,0 +1,10 @@ +gust +gust.backend +gust.backend.annotations +gust.backend.driver.firestore +gust.backend.driver.inmemory +gust.backend.driver.spanner +gust.backend.model +gust.backend.runtime +gust.backend.transport +gust.util diff --git a/docs/java/gust/Core.html b/docs/java/gust/Core.html new file mode 100644 index 000000000..2c72c0b48 --- /dev/null +++ b/docs/java/gust/Core.html @@ -0,0 +1,408 @@ + + + + + +Core + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust
+

Class Core

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.Core
    • +
    +
  • +
+
+
    +
  • +
    +
    @JsType
    +public final class Core
    +extends java.lang.Object
    +
    Provides core values, utility methods, etc, which can be used throughout the back- and front-end of a Gust-based + application. Some of these methods or properties will return different values based on where the application is + executed. So, accessing, say, getEngine() will return browser when invoked from JavaScript on the + front-end, and one of jvm or native when running on the backend.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static java.lang.StringgetDynamicAssetPrefix() +
      The dynamic asset prefix is a URL prefix under which frontend application assets are served, using a managed + file and token scheme, with a generated binary asset manifest living at the root of the JAR.
      +
      static java.lang.StringgetEngine() +
      Retrieve the current engine running this code.
      +
      static java.lang.StringgetGustVersion() +
      Retrieve the application version setting, which is applied via the JVM system property
      +
      static java.lang.BooleanisDebugMode() +
      Returns the current debug-mode flag state, which indicates whether we are running in a debugging-compatible mode or + not.
      +
      static java.lang.BooleanisDevMode() +
      Returns the current dev-mode flag state, which indicates whether we are running locally/in a development context.
      +
      static java.lang.BooleanisProductionMode() +
      "Production mode" is so-called because both isDebugMode() and isDevMode() return `false`.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getGustVersion

        +
        public static java.lang.String getGustVersion()
        +
        Retrieve the application version setting, which is applied via the JVM system property
        gust.version
        . + This value also shows up in frontend libraries as
        gust.version
        . The default value for this property, if + left unspecified by the runtime, is `alpha`.
        +
        +
        Returns:
        +
        Version assigned for the currently-running application.
        +
        +
      • +
      + + + +
        +
      • +

        getEngine

        +
        public static java.lang.String getEngine()
        +
        Retrieve the current engine running this code. By default, this is the string `unknown`. When running in frontend + contexts, this is generally overridden to be `browser`. On the backend, this value is generally either `jvm` or + `native`, depending on how the code is executing.
        +
        +
        Returns:
        +
        Current execution engine.
        +
        +
      • +
      + + + +
        +
      • +

        isDebugMode

        +
        public static java.lang.Boolean isDebugMode()
        +
        Returns the current debug-mode flag state, which indicates whether we are running in a debugging-compatible mode or + not. If not, we can generally expect a stripped binary (on the front and back-end), and perhaps expect that symbol + renaming is active.
        +
        +
        Returns:
        +
        Whether we are running in debug mode, or production mode (in which case this is `false`).
        +
        +
      • +
      + + + +
        +
      • +

        isDevMode

        +
        public static java.lang.Boolean isDevMode()
        +
        Returns the current dev-mode flag state, which indicates whether we are running locally/in a development context. + This is intentionally separate from isDebugMode() to facilitate "production" binary testing in a local + setting. Flip this flag to `true` to enable local RPC calls, logging, and so on, but with a production-optimized + set of artifacts.
        +
        +
        Returns:
        +
        Whether we are running in dev mode, or production mode (in which case this is `false`).
        +
        +
      • +
      + + + +
        +
      • +

        isProductionMode

        +
        public static java.lang.Boolean isProductionMode()
        +
        "Production mode" is so-called because both isDebugMode() and isDevMode() return `false`. In this + circumstance, we are running entirely in a mode for production use, in a production context. RPCs should be sent to + endpoints that write and read to/from production databases.
        +
        +
        Returns:
        +
        Whether we are running in production mode or not (`true` when `dev` and `debug` are both `false`).
        +
        +
      • +
      + + + +
        +
      • +

        getDynamicAssetPrefix

        +
        public static java.lang.String getDynamicAssetPrefix()
        +
        The dynamic asset prefix is a URL prefix under which frontend application assets are served, using a managed + file and token scheme, with a generated binary asset manifest living at the root of the JAR. Using this feature, + the server auto-detects managed assets and includes them in the page when directed. + +

        All assets, stylesheets and scripts included, are served under this URL if they are "managed assets," meaning + they are defined in the asset manifest and enclosed in the JAR.

        +
        +
        Returns:
        +
        Dynamic frontend asset serving prefix.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/AppController.html b/docs/java/gust/backend/AppController.html new file mode 100644 index 000000000..b8760e667 --- /dev/null +++ b/docs/java/gust/backend/AppController.html @@ -0,0 +1,541 @@ + + + + + +AppController + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Class AppController

+
+
+ +
+
    +
  • +
    +
    public abstract class AppController
    +extends BaseController
    +
    Describes a framework application controller, which is pre-loaded with convenient access to tooling, and any injected + application logic. Alternatively, if the invoking developer wishes to use their own base class, they can acquire most + or all of the enclosed functionality via injection. + +

    Various properties are made available to sub-classes which allow easy access to stuff like:

    +
      +
    • context: This property exposes a builder for the current flow's page context. This can be + manipulated to provide/inject properties, and then subsequently used in Soy.
    • +
    + +

    To make use of this controller, simply inherit from it in your own @Controller-annotated class. When a + request method is invoked, the logic provided by this object will have been initialized and will be ready to use.

    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + +
      Fields 
      Modifier and TypeFieldDescription
      protected static io.micronaut.http.MediaTypeHTML +
      Pre-ordained HTML object which ensures the character encoding is set to UTF-8.
      +
      + +
    • +
    +
    + +
    +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + + + +
      Constructors 
      ModifierConstructorDescription
      protected AppController​(PageContextManager context) +
      Initialize a new application controller.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      protected <T> io.micronaut.http.MutableHttpResponse<T>affixHeaders​(io.micronaut.http.MutableHttpResponse<T> response) +
      Affix headers to the provided response, according to current app configuration for dynamic serving.
      +
      protected <T> io.micronaut.http.MutableHttpResponse<T>affixHeaders​(io.micronaut.http.MutableHttpResponse<T> response, + DynamicServingConfiguration config) +
      Affix headers to the provided response, according to the provided app config for dynamic serving.
      +
      protected voidselectCdnPrefixes​(PageContextManager render, + io.micronaut.http.MutableHttpResponse response, + AssetConfiguration servingConfig) +
      Select the set of CDN prefixes to use in this HTTP cycle, from the configured set.
      +
      protected io.micronaut.http.MutableHttpResponse<PageRender>serve​(PageContextManager render) +
      Serve the provided rendered-page response, while applying any app configuration related to dynamic page headers.
      +
      com.google.common.html.types.TrustedResourceUrlPrototrustedResource​(java.net.URI uri) +
      Generate a trusted resource URL for the provided Java URI.
      +
      com.google.common.html.types.TrustedResourceUrlPrototrustedResource​(java.net.URL url) +
      Generate a trusted resource URL for the provided Java URL.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        HTML

        +
        @Nonnull
        +protected static final io.micronaut.http.MediaType HTML
        +
        Pre-ordained HTML object which ensures the character encoding is set to UTF-8.
        +
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        AppController

        +
        protected AppController​(@Nonnull
        +                        PageContextManager context)
        +
        Initialize a new application controller.
        +
        +
        Parameters:
        +
        context - Injected page context manager.
        +
        +
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        trustedResource

        +
        @Nonnull
        +public com.google.common.html.types.TrustedResourceUrlProto trustedResource​(@Nonnull
        +                                                                            java.net.URL url)
        +
        Generate a trusted resource URL for the provided Java URL.
        +
        +
        Parameters:
        +
        url - Pre-ordained trusted resource URL.
        +
        Returns:
        +
        Trusted resource URL specification proto.
        +
        +
      • +
      + + + +
        +
      • +

        trustedResource

        +
        @Nonnull
        +public com.google.common.html.types.TrustedResourceUrlProto trustedResource​(@Nonnull
        +                                                                            java.net.URI uri)
        +
        Generate a trusted resource URL for the provided Java URI.
        +
        +
        Parameters:
        +
        uri - Pre-ordained trusted resource URI.
        +
        Returns:
        +
        Trusted resource URL specification proto.
        +
        +
      • +
      + + + +
        +
      • +

        affixHeaders

        +
        @Nonnull
        +protected <T> io.micronaut.http.MutableHttpResponse<T> affixHeaders​(@Nonnull
        +                                                                    io.micronaut.http.MutableHttpResponse<T> response,
        +                                                                    @Nonnull
        +                                                                    DynamicServingConfiguration config)
        +
        Affix headers to the provided response, according to the provided app config for dynamic serving. The + resulting response is kept mutable for further changes.
        +
        +
        Parameters:
        +
        response - HTTP response to affix headers to.
        +
        Returns:
        +
        HTTP response, with headers affixed.
        +
        +
      • +
      + + + +
        +
      • +

        affixHeaders

        +
        @Nonnull
        +protected <T> io.micronaut.http.MutableHttpResponse<T> affixHeaders​(@Nonnull
        +                                                                    io.micronaut.http.MutableHttpResponse<T> response)
        +
        Affix headers to the provided response, according to current app configuration for dynamic serving. The resulting + response is kept mutable for further changes.
        +
        +
        Parameters:
        +
        response - HTTP response to affix headers to.
        +
        Returns:
        +
        HTTP response, with headers affixed.
        +
        +
      • +
      + + + +
        +
      • +

        selectCdnPrefixes

        +
        protected void selectCdnPrefixes​(@Nonnull
        +                                 PageContextManager render,
        +                                 @Nonnull
        +                                 io.micronaut.http.MutableHttpResponse response,
        +                                 @Nonnull
        +                                 AssetConfiguration servingConfig)
        +
        Select the set of CDN prefixes to use in this HTTP cycle, from the configured set. Once this action completes, the + set of CDN prefixes is considered "frozen" for this cycle.
        +
        +
        Parameters:
        +
        render - Page context manager, after the handler has completed.
        +
        response - Response object, on which we should set the CDN prefix property.
        +
        servingConfig - Serving configuration from which to calculate the active set of CDN prefixes.
        +
        +
      • +
      + + + +
        +
      • +

        serve

        +
        @Nonnull
        +protected io.micronaut.http.MutableHttpResponse<PageRenderserve​(@Nonnull
        +                                                                  PageContextManager render)
        +
        Serve the provided rendered-page response, while applying any app configuration related to dynamic page headers. + This may include headers like Vary, ETag, and so on, which may be calculated based on the response + intended to be provided to the client.
        +
        +
        Parameters:
        +
        render - Page render to perform before responding.
        +
        Returns:
        +
        Prepped and rendered HTTP response.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/Application.html b/docs/java/gust/backend/Application.html new file mode 100644 index 000000000..118ff1e83 --- /dev/null +++ b/docs/java/gust/backend/Application.html @@ -0,0 +1,280 @@ + + + + + +Application + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Class Application

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.Application
    • +
    +
  • +
+
+
    +
  • +
    +
    public final class Application
    +extends java.lang.Object
    +
    Main application class, which bootstraps a backend Gust app via Micronaut, including any configured controllers, + services, or assets. This is where execution starts when running on a JVM. Micronaut uses build-time annotation + processing, and a number of other techniques, to pre-initialize and wire up the app before it ever gets to the + server to be executed at runtime.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static voidmain​(java.lang.String[] args) +
      Main entrypoint into a Gust backend application, powered by Micronaut.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        main

        +
        public static void main​(java.lang.String[] args)
        +
        Main entrypoint into a Gust backend application, powered by Micronaut. This function will pre-load any static stuff + that needs to be bootstrapped, and then it initializes the app via Micronaut.
        +
        +
        Parameters:
        +
        args - Arguments passed on the command line.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/ApplicationBoot.html b/docs/java/gust/backend/ApplicationBoot.html new file mode 100644 index 000000000..c1d451be4 --- /dev/null +++ b/docs/java/gust/backend/ApplicationBoot.html @@ -0,0 +1,407 @@ + + + + + +ApplicationBoot + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Class ApplicationBoot

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.ApplicationBoot
    • +
    +
  • +
+
+
    +
  • +
    +
    public final class ApplicationBoot
    +extends java.lang.Object
    +
    Responsible for any testable functionality that occurs when bootstrapping a Java-based application, including + force-resolving critical configuration files, setting up logging, and so on.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + + + + + + +
      Fields 
      Modifier and TypeFieldDescription
      static java.lang.StringdefaultConfig +
      Default configuration provided by Gust.
      +
      static java.lang.StringrootConfig +
      Root configuration for a Micronaut app.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static voidload() +
      Load main application configs, including the `app` config (usually `application.yml`), containing configuration for + Micronaut, and `logback.xml` which contains configuration for logging.
      +
      static voidloadConfig​(java.lang.String role, + java.lang.String name, + java.lang.String alt) +
      Attempt to load a given global config file, failing if we can't find it in the expected spot, or the backup spot, + optionally provided as the second param.
      +
      static voidreportStartupError​(java.lang.Throwable err) +
      Report an error that occurred during server startup, which prevented the server from starting.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        reportStartupError

        +
        public static void reportStartupError​(@Nonnull
        +                                      java.lang.Throwable err)
        +
        Report an error that occurred during server startup, which prevented the server from starting. Errors encountered + and reported in this manner are fatal.
        +
        +
        Parameters:
        +
        err - Fatal error that occurred and prevented server startup.
        +
        +
      • +
      + + + +
        +
      • +

        loadConfig

        +
        public static void loadConfig​(@Nonnull
        +                              java.lang.String role,
        +                              @Nonnull
        +                              java.lang.String name,
        +                              @Nullable
        +                              java.lang.String alt)
        +
        Attempt to load a given global config file, failing if we can't find it in the expected spot, or the backup spot, + optionally provided as the second param.
        +
        +
        Parameters:
        +
        role - Role description for this file.
        +
        name - Configuration file name.
        +
        alt - Alternate configuration file name.
        +
        Throws:
        +
        java.lang.RuntimeException - Wrapping an IOException, If the configuration can't be loaded.
        +
        +
      • +
      + + + +
        +
      • +

        load

        +
        public static void load()
        +
        Load main application configs, including the `app` config (usually `application.yml`), containing configuration for + Micronaut, and `logback.xml` which contains configuration for logging. If either config file cannot be loaded, then + an error is thrown which prevents server startup.
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/AssetConfiguration.AssetCachingConfiguration.html b/docs/java/gust/backend/AssetConfiguration.AssetCachingConfiguration.html new file mode 100644 index 000000000..ca8bb911b --- /dev/null +++ b/docs/java/gust/backend/AssetConfiguration.AssetCachingConfiguration.html @@ -0,0 +1,439 @@ + + + + + +AssetConfiguration.AssetCachingConfiguration + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Interface AssetConfiguration.AssetCachingConfiguration

+
+
+
+ +
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Default Methods 
      Modifier and TypeMethodDescription
      default java.util.Optional<java.util.List<java.lang.String>>additionalDirectives() +
      Additional directives to inject into the HTTP caching header.
      +
      default java.lang.Booleanenabled() +
      Whether to enable intelligent HTTP caching for assets served dynamically.
      +
      default java.lang.BooleanenableShared() +
      Whether to enable a shared-cache directive in the HTTP cache settings.
      +
      default java.lang.Stringmode() +
      Main mode to apply with regard to HTTP caching for assets served dynamically.
      +
      default java.lang.LongsharedTtl() +
      When a shared-cache directive is enabled, this sets the TTL for shared caches.
      +
      default java.util.concurrent.TimeUnitsharedTtlUnit() +
      Time unit to apply to the value specified by sharedTtl().
      +
      default java.lang.Longttl() +
      Time-to-live value to apply to the main HTTP cache directive.
      +
      default java.util.concurrent.TimeUnitttlUnit() +
      Time unit to apply to the value specified by ttl().
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        enabled

        +
        @Bindable("enabled")
        +default java.lang.Boolean enabled()
        +
        Whether to enable intelligent HTTP caching for assets served dynamically.
        +
      • +
      + + + +
        +
      • +

        mode

        +
        @Bindable("mode")
        +default java.lang.String mode()
        +
        Main mode to apply with regard to HTTP caching for assets served dynamically.
        +
      • +
      + + + +
        +
      • +

        additionalDirectives

        +
        @Bindable("additionalDirectives")
        +default java.util.Optional<java.util.List<java.lang.String>> additionalDirectives()
        +
        Additional directives to inject into the HTTP caching header.
        +
      • +
      + + + +
        +
      • +

        ttl

        +
        @Bindable("ttl")
        +default java.lang.Long ttl()
        +
        Time-to-live value to apply to the main HTTP cache directive. Units tunable with ttlUnit().
        +
      • +
      + + + +
        +
      • +

        enableShared

        +
        @Bindable("shared")
        +default java.lang.Boolean enableShared()
        +
        Whether to enable a shared-cache directive in the HTTP cache settings.
        +
      • +
      + + + +
        +
      • +

        sharedTtl

        +
        @Bindable("sharedTtl")
        +default java.lang.Long sharedTtl()
        +
        When a shared-cache directive is enabled, this sets the TTL for shared caches.
        +
      • +
      + + + +
        +
      • +

        ttlUnit

        +
        default java.util.concurrent.TimeUnit ttlUnit()
        +
        Time unit to apply to the value specified by ttl(). Defaults to SECONDS.
        +
      • +
      + + + +
        +
      • +

        sharedTtlUnit

        +
        default java.util.concurrent.TimeUnit sharedTtlUnit()
        +
        Time unit to apply to the value specified by sharedTtl(). Defaults to SECONDS.
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/AssetConfiguration.AssetCompressionConfiguration.html b/docs/java/gust/backend/AssetConfiguration.AssetCompressionConfiguration.html new file mode 100644 index 000000000..c8b6e9caf --- /dev/null +++ b/docs/java/gust/backend/AssetConfiguration.AssetCompressionConfiguration.html @@ -0,0 +1,346 @@ + + + + + +AssetConfiguration.AssetCompressionConfiguration + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Interface AssetConfiguration.AssetCompressionConfiguration

+
+
+
+ +
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Default Methods 
      Modifier and TypeMethodDescription
      default java.util.SortedSet<tools.elide.core.data.CompressionMode>compressionModes() +
      Whether to enable serving of pre-compressed assets.
      +
      default java.lang.Booleanenabled() +
      Whether to enable serving of pre-compressed assets.
      +
      default java.lang.BooleanenableVary() +
      Whether to enable the `Vary` header with regard to compression.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        enabled

        +
        @Bindable("enabled")
        +default java.lang.Boolean enabled()
        +
        Whether to enable serving of pre-compressed assets.
        +
      • +
      + + + +
        +
      • +

        compressionModes

        +
        @Bindable("modes")
        +default java.util.SortedSet<tools.elide.core.data.CompressionMode> compressionModes()
        +
        Whether to enable serving of pre-compressed assets.
        +
      • +
      + + + +
        +
      • +

        enableVary

        +
        @Bindable("vary")
        +default java.lang.Boolean enableVary()
        +
        Whether to enable the `Vary` header with regard to compression.
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/AssetConfiguration.AssetVarianceConfiguration.html b/docs/java/gust/backend/AssetConfiguration.AssetVarianceConfiguration.html new file mode 100644 index 000000000..8c88742fb --- /dev/null +++ b/docs/java/gust/backend/AssetConfiguration.AssetVarianceConfiguration.html @@ -0,0 +1,384 @@ + + + + + +AssetConfiguration.AssetVarianceConfiguration + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Interface AssetConfiguration.AssetVarianceConfiguration

+
+
+
+ +
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Default Methods 
      Modifier and TypeMethodDescription
      default java.lang.Booleanaccept() +
      Whether to vary based on Accept.
      +
      default java.lang.Booleancharset() +
      Whether to vary based on Accept-Charset.
      +
      default java.lang.Booleanenabled() +
      Whether to enable Vary headers at all.
      +
      default java.lang.Booleanlanguage() +
      Whether to vary based on Accept-Language.
      +
      default java.lang.Booleanorigin() +
      Whether to vary based on the value of Origin.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        enabled

        +
        @Bindable("enabled")
        +default java.lang.Boolean enabled()
        +
        Whether to enable Vary headers at all.
        +
      • +
      + + + +
        +
      • +

        accept

        +
        @Bindable("accept")
        +default java.lang.Boolean accept()
        +
        Whether to vary based on Accept.
        +
      • +
      + + + +
        +
      • +

        language

        +
        @Bindable("language")
        +default java.lang.Boolean language()
        +
        Whether to vary based on Accept-Language.
        +
      • +
      + + + +
        +
      • +

        charset

        +
        @Bindable("charset")
        +default java.lang.Boolean charset()
        +
        Whether to vary based on Accept-Charset.
        +
      • +
      + + + +
        +
      • +

        origin

        +
        @Bindable("origin")
        +default java.lang.Boolean origin()
        +
        Whether to vary based on the value of Origin.
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/AssetConfiguration.ContentDistributionConfiguration.html b/docs/java/gust/backend/AssetConfiguration.ContentDistributionConfiguration.html new file mode 100644 index 000000000..6da8cc795 --- /dev/null +++ b/docs/java/gust/backend/AssetConfiguration.ContentDistributionConfiguration.html @@ -0,0 +1,327 @@ + + + + + +AssetConfiguration.ContentDistributionConfiguration + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Interface AssetConfiguration.ContentDistributionConfiguration

+
+
+
+ +
+
+ +
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        enabled

        +
        @Bindable("enabled")
        +default java.lang.Boolean enabled()
        +
        Whether to enable CDN features.
        +
      • +
      + + + +
        +
      • +

        hostnames

        +
        @Bindable("hostnames")
        +default java.util.List<java.lang.String> hostnames()
        +
        CDN host names to use for assets. A random selection is made from this list for each page render.
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/AssetConfiguration.CrossOriginResourceConfiguration.html b/docs/java/gust/backend/AssetConfiguration.CrossOriginResourceConfiguration.html new file mode 100644 index 000000000..a7a997fc6 --- /dev/null +++ b/docs/java/gust/backend/AssetConfiguration.CrossOriginResourceConfiguration.html @@ -0,0 +1,327 @@ + + + + + +AssetConfiguration.CrossOriginResourceConfiguration + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Interface AssetConfiguration.CrossOriginResourceConfiguration

+
+
+
+ +
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Default Methods 
      Modifier and TypeMethodDescription
      default java.lang.Booleanenabled() +
      Whether to enable Cross-Origin-Resource-Policy headers for dynamically-served content.
      +
      default tools.elide.page.Context.CrossOriginResourcePolicypolicy() +
      Specifies the default policy to employ for Cross-Origin-Resource-Policy for dynamic content.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        enabled

        +
        @Bindable("enabled")
        +default java.lang.Boolean enabled()
        +
        Whether to enable Cross-Origin-Resource-Policy headers for dynamically-served content.
        +
      • +
      + + + +
        +
      • +

        policy

        +
        @Bindable("policy")
        +default tools.elide.page.Context.CrossOriginResourcePolicy policy()
        +
        Specifies the default policy to employ for Cross-Origin-Resource-Policy for dynamic content.
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/AssetConfiguration.html b/docs/java/gust/backend/AssetConfiguration.html new file mode 100644 index 000000000..30436a7c2 --- /dev/null +++ b/docs/java/gust/backend/AssetConfiguration.html @@ -0,0 +1,509 @@ + + + + + +AssetConfiguration + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Interface AssetConfiguration

+
+
+
+
    +
  • +
    +
    @ConfigurationProperties("gust.assets")
    +public interface AssetConfiguration
    +
    App configuration bindings for asset management and serving.
    +
  • +
+
+
+ +
+
+ +
+
+
+ + + + diff --git a/docs/java/gust/backend/AssetController.AssetsAwareCspFilter.html b/docs/java/gust/backend/AssetController.AssetsAwareCspFilter.html new file mode 100644 index 000000000..227beb964 --- /dev/null +++ b/docs/java/gust/backend/AssetController.AssetsAwareCspFilter.html @@ -0,0 +1,329 @@ + + + + + +AssetController.AssetsAwareCspFilter + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Class AssetController.AssetsAwareCspFilter

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • io.micronaut.views.csp.CspFilter
    • +
    • +
        +
      • gust.backend.AssetController.AssetsAwareCspFilter
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    io.micronaut.core.order.Ordered, io.micronaut.http.filter.HttpFilter, io.micronaut.http.filter.HttpServerFilter
    +
    +
    +
    Enclosing class:
    +
    AssetController
    +
    +
    +
    @Replaces(io.micronaut.views.csp.CspFilter.class)
    +public static final class AssetController.AssetsAwareCspFilter
    +extends io.micronaut.views.csp.CspFilter
    +
    Replacement for CspFilter which disables itself when assets (or non-HTML content) are served.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class io.micronaut.views.csp.CspFilter

        +CSP_HEADER, CSP_REPORT_ONLY_HEADER, cspConfiguration, NONCE_PROPERTY, NONCE_TOKEN
      • +
      +
        +
      • + + +

        Fields inherited from interface io.micronaut.core.order.Ordered

        +HIGHEST_PRECEDENCE, LOWEST_PRECEDENCE
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      org.reactivestreams.Publisher<io.micronaut.http.MutableHttpResponse<?>>doFilter​(io.micronaut.http.HttpRequest<?> request, + io.micronaut.http.filter.ServerFilterChain chain) 
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
        +
      • + + +

        Methods inherited from interface io.micronaut.http.filter.HttpServerFilter

        +doFilter
      • +
      +
        +
      • + + +

        Methods inherited from interface io.micronaut.core.order.Ordered

        +getOrder
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        doFilter

        +
        public org.reactivestreams.Publisher<io.micronaut.http.MutableHttpResponse<?>> doFilter​(io.micronaut.http.HttpRequest<?> request,
        +                                                                                        io.micronaut.http.filter.ServerFilterChain chain)
        +
        +
        Specified by:
        +
        doFilter in interface io.micronaut.http.filter.HttpServerFilter
        +
        Overrides:
        +
        doFilter in class io.micronaut.views.csp.CspFilter
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/AssetController.html b/docs/java/gust/backend/AssetController.html new file mode 100644 index 000000000..078ddfa08 --- /dev/null +++ b/docs/java/gust/backend/AssetController.html @@ -0,0 +1,324 @@ + + + + + +AssetController + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Class AssetController

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.AssetController
    • +
    +
  • +
+
+
    +
  • +
    +
    @Controller("/_/assets")
    +@Secured("isAnonymous()")
    +public class AssetController
    +extends java.lang.Object
    +
    Built-in backend controller for serving dynamic managed assets. Assets managed in this manner are hooked into the + build pipeline, so that a binary asset manifest is produced at the root of the application JAR. + +

    AssetManager loads the binary manifest/bundle, and then using this controller, we serve the URLs and + assets mentioned therein. Controllers may then reference these assets (to be served here), via utility methods on + BaseController.

    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Nested Class Summary

      + + + + + + + + + + + + +
      Nested Classes 
      Modifier and TypeClassDescription
      static class AssetController.AssetsAwareCspFilter +
      Replacement for CspFilter which disables itself when assets (or non-HTML content) are served.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      io.reactivex.Flowable<io.micronaut.http.HttpResponse>asset​(java.lang.String asset, + java.lang.String ext, + io.micronaut.http.HttpRequest request) +
      Main GET serving endpoint for dynamic managed assets.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        asset

        +
        @Validated
        +@Get(value="/{asset}.{ext}",
        +     produces={"text/html","text/css","application/javascript"})
        +@Nonnull
        +public io.reactivex.Flowable<io.micronaut.http.HttpResponse> asset​(@Nonnull
        +                                                                   java.lang.String asset,
        +                                                                   @Nonnull
        +                                                                   java.lang.String ext,
        +                                                                   @Nonnull
        +                                                                   io.micronaut.http.HttpRequest request)
        +
        Main GET serving endpoint for dynamic managed assets. Assets come through this endpoint mentioning their unique + token and file extension, and we do the rest. + +

        We accomplish this by (1) querying the AssetManager for a matching asset (404 is yielded if a match + cannot be located), then (2) calculating headers and an appropriate variant for the subject asset, and finally (3) + writing the headers and the asset body to the response.

        +
        +
        Returns:
        +
        Response
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/BaseController.html b/docs/java/gust/backend/BaseController.html new file mode 100644 index 000000000..cef97c80f --- /dev/null +++ b/docs/java/gust/backend/BaseController.html @@ -0,0 +1,333 @@ + + + + + +BaseController + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Class BaseController

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.BaseController
    • +
    +
  • +
+
+
    +
  • +
    +
    Direct Known Subclasses:
    +
    AppController
    +
    +
    +
    public abstract class BaseController
    +extends java.lang.Object
    +
    Supplies shared logic to all framework-provided controller base classes. Responsible for managing such things as the + PageContextManager, and any other request-scoped state. + +

    Implementors of this class are provided with convenient access to initialized app logic and clients (such as gRPC + clients or database clients). However, controller authors need not select this route for convenience sake if they + have a better base class in mind: all the functionality provided here can easily be obtained via dependency + injection.

    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + +
      Fields 
      Modifier and TypeFieldDescription
      protected PageContextManagercontext +
      Holds request-bound page context as it is built.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + +
      Constructors 
      ConstructorDescription
      BaseController​(PageContextManager context) +
      Initialize a base Gust controller from scratch.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        BaseController

        +
        public BaseController​(PageContextManager context)
        +
        Initialize a base Gust controller from scratch.
        +
        +
        Parameters:
        +
        context - Page context manager, injected.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/DynamicServingConfiguration.ClientHintsConfiguration.html b/docs/java/gust/backend/DynamicServingConfiguration.ClientHintsConfiguration.html new file mode 100644 index 000000000..413676401 --- /dev/null +++ b/docs/java/gust/backend/DynamicServingConfiguration.ClientHintsConfiguration.html @@ -0,0 +1,365 @@ + + + + + +DynamicServingConfiguration.ClientHintsConfiguration + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Interface DynamicServingConfiguration.ClientHintsConfiguration

+
+
+
+ +
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Default Methods 
      Modifier and TypeMethodDescription
      default java.lang.Booleanenabled() +
      Whether to enable support for client hints.
      +
      default com.google.common.collect.ImmutableSet<tools.elide.page.Context.ClientHint>hints() +
      Return the set of hints supported by the server.
      +
      default java.util.Optional<java.lang.Long>ttl() +
      Client Hints configuration time-to-live value.
      +
      default java.util.concurrent.TimeUnitttlUnit() +
      Client Hints configuration time-to-live unit.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        enabled

        +
        @Bindable("enabled")
        +default java.lang.Boolean enabled()
        +
        Whether to enable support for client hints.
        +
      • +
      + + + +
        +
      • +

        hints

        +
        @Bindable("hints")
        +default com.google.common.collect.ImmutableSet<tools.elide.page.Context.ClientHint> hints()
        +
        Return the set of hints supported by the server.
        +
      • +
      + + + +
        +
      • +

        ttl

        +
        @Bindable("ttl")
        +default java.util.Optional<java.lang.Long> ttl()
        +
        Client Hints configuration time-to-live value.
        +
      • +
      + + + +
        +
      • +

        ttlUnit

        +
        @Bindable("ttlUnit")
        +default java.util.concurrent.TimeUnit ttlUnit()
        +
        Client Hints configuration time-to-live unit. Defaults to DAYS.
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/DynamicServingConfiguration.DynamicETagsConfiguration.html b/docs/java/gust/backend/DynamicServingConfiguration.DynamicETagsConfiguration.html new file mode 100644 index 000000000..10369fefd --- /dev/null +++ b/docs/java/gust/backend/DynamicServingConfiguration.DynamicETagsConfiguration.html @@ -0,0 +1,308 @@ + + + + + +DynamicServingConfiguration.DynamicETagsConfiguration + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Interface DynamicServingConfiguration.DynamicETagsConfiguration

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

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/DynamicServingConfiguration.DynamicVarianceConfiguration.html b/docs/java/gust/backend/DynamicServingConfiguration.DynamicVarianceConfiguration.html new file mode 100644 index 000000000..669d4bfab --- /dev/null +++ b/docs/java/gust/backend/DynamicServingConfiguration.DynamicVarianceConfiguration.html @@ -0,0 +1,403 @@ + + + + + +DynamicServingConfiguration.DynamicVarianceConfiguration + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Interface DynamicServingConfiguration.DynamicVarianceConfiguration

+
+
+
+ +
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Default Methods 
      Modifier and TypeMethodDescription
      default java.lang.Booleanaccept() +
      Whether to indicate response variance by Accept.
      +
      default java.lang.Booleancharset() +
      Whether to indicate response variance by Accept-Charset.
      +
      default java.lang.Booleanenabled() +
      Whether to enable Vary headers for dynamically-served content.
      +
      default java.lang.Booleanencoding() +
      Whether to indicate response variance by Accept-Encoding.
      +
      default java.lang.Booleanlanguage() +
      Whether to indicate response variance by Accept-Language.
      +
      default java.lang.Booleanorigin() +
      Whether to indicate response variance by Origin.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        enabled

        +
        @Bindable("enabled")
        +default java.lang.Boolean enabled()
        +
        Whether to enable Vary headers for dynamically-served content.
        +
      • +
      + + + +
        +
      • +

        accept

        +
        @Bindable("accept")
        +default java.lang.Boolean accept()
        +
        Whether to indicate response variance by Accept.
        +
      • +
      + + + +
        +
      • +

        charset

        +
        @Bindable("charset")
        +default java.lang.Boolean charset()
        +
        Whether to indicate response variance by Accept-Charset.
        +
      • +
      + + + +
        +
      • +

        encoding

        +
        @Bindable("encoding")
        +default java.lang.Boolean encoding()
        +
        Whether to indicate response variance by Accept-Encoding.
        +
      • +
      + + + +
        +
      • +

        language

        +
        @Bindable("language")
        +default java.lang.Boolean language()
        +
        Whether to indicate response variance by Accept-Language.
        +
      • +
      + + + +
        +
      • +

        origin

        +
        @Bindable("origin")
        +default java.lang.Boolean origin()
        +
        Whether to indicate response variance by Origin.
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/DynamicServingConfiguration.FeaturePolicyConfiguration.html b/docs/java/gust/backend/DynamicServingConfiguration.FeaturePolicyConfiguration.html new file mode 100644 index 000000000..eb2918572 --- /dev/null +++ b/docs/java/gust/backend/DynamicServingConfiguration.FeaturePolicyConfiguration.html @@ -0,0 +1,327 @@ + + + + + +DynamicServingConfiguration.FeaturePolicyConfiguration + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Interface DynamicServingConfiguration.FeaturePolicyConfiguration

+
+
+
+ +
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Default Methods 
      Modifier and TypeMethodDescription
      default java.lang.Booleanenabled() +
      Whether to enable Feature-Policy headers for dynamically-served content.
      +
      default java.util.SortedSet<java.lang.String>policy() +
      Specifies the default Feature-Policy to apply to dynamically-served content.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        enabled

        +
        @Bindable("enabled")
        +default java.lang.Boolean enabled()
        +
        Whether to enable Feature-Policy headers for dynamically-served content.
        +
      • +
      + + + +
        +
      • +

        policy

        +
        @Bindable("policy")
        +default java.util.SortedSet<java.lang.String> policy()
        +
        Specifies the default Feature-Policy to apply to dynamically-served content.
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/DynamicServingConfiguration.XSSProtectionConfiguration.html b/docs/java/gust/backend/DynamicServingConfiguration.XSSProtectionConfiguration.html new file mode 100644 index 000000000..2a5e5662c --- /dev/null +++ b/docs/java/gust/backend/DynamicServingConfiguration.XSSProtectionConfiguration.html @@ -0,0 +1,346 @@ + + + + + +DynamicServingConfiguration.XSSProtectionConfiguration + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Interface DynamicServingConfiguration.XSSProtectionConfiguration

+
+
+
+ +
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Default Methods 
      Modifier and TypeMethodDescription
      default java.lang.Booleanblock() +
      Whether to add the block flag.
      +
      default java.lang.Booleanenabled() +
      Whether to enable old-style XSS protection.
      +
      default java.lang.Booleanfilter() +
      Whether to specify XSS protection as active.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        enabled

        +
        @Bindable("enabled")
        +default java.lang.Boolean enabled()
        +
        Whether to enable old-style XSS protection.
        +
      • +
      + + + +
        +
      • +

        filter

        +
        @Bindable("filter")
        +default java.lang.Boolean filter()
        +
        Whether to specify XSS protection as active.
        +
      • +
      + + + +
        +
      • +

        block

        +
        @Bindable("block")
        +default java.lang.Boolean block()
        +
        Whether to add the block flag.
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/DynamicServingConfiguration.html b/docs/java/gust/backend/DynamicServingConfiguration.html new file mode 100644 index 000000000..fc7a50e55 --- /dev/null +++ b/docs/java/gust/backend/DynamicServingConfiguration.html @@ -0,0 +1,566 @@ + + + + + +DynamicServingConfiguration + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Interface DynamicServingConfiguration

+
+
+
+
    +
  • +
    +
    @ConfigurationProperties("gust.serving")
    +public interface DynamicServingConfiguration
    +
    Supplies configuration structure for dynamically-served app pages through Gust.
    +
  • +
+
+
+ +
+
+ +
+
+
+ + + + diff --git a/docs/java/gust/backend/PageContext.html b/docs/java/gust/backend/PageContext.html new file mode 100644 index 000000000..06e51303f --- /dev/null +++ b/docs/java/gust/backend/PageContext.html @@ -0,0 +1,912 @@ + + + + + +PageContext + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Class PageContext

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.PageContext
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    PageRender, io.micronaut.views.soy.SoyContextMediator
    +
    +
    +
    @Immutable
    +public final class PageContext
    +extends java.lang.Object
    +implements PageRender
    +
    Supplies page context to a Micronaut/Soy render routine, based on the context proto provided/filled out by a given + server-side controller. + +

    Because this flow occurs in two stages (i.e. building or calculating context, then, subsequently, rendering + context), the logic here is implemented to be entirely immutable, and so this object should be used in a transitory + way to mediate between a single Soy routine and the context attached to it.

    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      java.util.Optional<java.util.function.Predicate<java.lang.String>>delegatePackage() +
      Return the delegate package that should be active when rendering the desired Soy output, if applicable.
      +
      static PageContextempty() +
      Factory to create an empty page context.
      +
      static PageContextfromMap​(java.util.Map<java.lang.String,​java.lang.Object> context) +
      Factory to create a page context object from a regular Java map, of string context properties to values of any + object type.
      +
      static PageContextfromMap​(java.util.Map<java.lang.String,​java.lang.Object> context, + java.util.Map<java.lang.String,​java.lang.Object> injected) +
      Factory to create a page context object from a regular Java map, of string context properties to values of any + object type.
      +
      static PageContextfromMap​(java.util.Map<java.lang.String,​java.lang.Object> context, + java.util.Map<java.lang.String,​java.lang.Object> injected, + io.micronaut.views.soy.SoyNamingMapProvider namingMapProvider) +
      Factory to create a page context object from a regular Java map, of string context properties to values of any + object type.
      +
      static PageContextfromMap​(java.util.Map<java.lang.String,​java.lang.Object> context, + java.util.Map<java.lang.String,​java.lang.Object> injected, + io.micronaut.views.soy.SoyNamingMapProvider namingMapProvider, + io.micronaut.views.soy.SoyContext.SoyI18NContext i18n, + java.util.function.Predicate<java.lang.String> delegatePredicate) +
      Factory to create a page context object from a regular Java map, of string context properties to values of any + object type.
      +
      static PageContextfromProto​(tools.elide.page.Context pageContext) +
      Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`.
      +
      static PageContextfromProto​(tools.elide.page.Context pageContext, + java.util.Map<java.lang.String,​java.lang.Object> props) +
      Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`.
      +
      static PageContextfromProto​(tools.elide.page.Context pageContext, + java.util.Map<java.lang.String,​java.lang.Object> props, + java.util.Map<java.lang.String,​java.lang.Object> injected) +
      Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`.
      +
      static PageContextfromProto​(tools.elide.page.Context pageContext, + java.util.Map<java.lang.String,​java.lang.Object> props, + java.util.Map<java.lang.String,​java.lang.Object> injected, + io.micronaut.views.soy.SoyNamingMapProvider namingMapProvider) +
      Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`.
      +
      static PageContextfromProto​(tools.elide.page.Context pageContext, + java.util.Map<java.lang.String,​java.lang.Object> props, + java.util.Map<java.lang.String,​java.lang.Object> injected, + io.micronaut.views.soy.SoyNamingMapProvider namingMapProvider, + io.micronaut.views.soy.SoyContext.SoyI18NContext i18n, + java.util.function.Predicate<java.lang.String> delegatePredicate) +
      Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`.
      +
      java.util.Map<java.lang.String,​java.lang.Object>getInjectedProperties​(java.util.Map<java.lang.String,​java.lang.Object> framework) +
      Retrieve properties and values that should be made available via `@inject`.
      +
      tools.elide.page.ContextgetPageContext() +
      Retrieve serializable server-side-rendered page context, which should be assigned to the render flow bound to this + context mediator.
      +
      java.util.Map<java.lang.String,​java.lang.Object>getProperties() +
      Retrieve properties which should be made available via regular, declared `@param` statements.
      +
      java.util.Optional<com.google.template.soy.msgs.SoyMsgBundle>messageBundle() +
      Return the pre-fabricated Soy message bundle, or the interpreted Soy message bundle based on the installed messages + file or resource URL.
      +
      java.util.Optional<java.io.File>messagesFile() +
      Return the file that should be loaded and interpreted to perform translation when rendering this page.
      +
      java.util.Optional<java.net.URL>messagesResource() +
      Return the URL to the resource that should be loaded and interpreted to perform translation when rendering this + page.
      +
      java.util.Optional<io.micronaut.views.soy.SoyNamingMapProvider>overrideNamingMap() +
      Specify a Soy renaming map which overrides the globally-installed map, if any.
      +
      booleantranslate() +
      Specify whether to enable translation via Soy message bundles.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
        +
      • + + +

        Methods inherited from interface io.micronaut.views.soy.SoyContextMediator

        +enableETags, finalizeResponse, strongETags
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        empty

        +
        public static PageContext empty()
        +
        Factory to create an empty page context. Under the hood, this uses a static singleton representing an empty context + to avoid re-creating the object repeatedly.
        +
        +
        Returns:
        +
        Pre-fabricated empty page context.
        +
        +
      • +
      + + + +
        +
      • +

        fromMap

        +
        public static PageContext fromMap​(@Nonnull
        +                                  java.util.Map<java.lang.String,​java.lang.Object> context)
        +
        Factory to create a page context object from a regular Java map, of string context properties to values of any + object type. Under the hood, this is processed and converted/wrapped into Soy values.
        +
        +
        Parameters:
        +
        context - Context with which to render a Soy template.
        +
        Returns:
        +
        Instance of page context, enclosing the provided context.
        +
        +
      • +
      + + + +
        +
      • +

        fromMap

        +
        public static PageContext fromMap​(@Nonnull
        +                                  java.util.Map<java.lang.String,​java.lang.Object> context,
        +                                  @Nonnull
        +                                  java.util.Map<java.lang.String,​java.lang.Object> injected)
        +
        Factory to create a page context object from a regular Java map, of string context properties to values of any + object type. Additionally, this interface allows specification of properties declared via
        @inject
        . Under + the hood, all context is processed and converted/wrapped into Soy values.
        +
        +
        Parameters:
        +
        context - Context with which to render a Soy template - i.e. regular
        @param
        declarations.
        +
        injected - Injected parameters to provide to the render operation - available via
        @inject
        .
        +
        Returns:
        +
        Fabricated page context object.
        +
        +
      • +
      + + + +
        +
      • +

        fromMap

        +
        public static PageContext fromMap​(@Nonnull
        +                                  java.util.Map<java.lang.String,​java.lang.Object> context,
        +                                  @Nonnull
        +                                  java.util.Map<java.lang.String,​java.lang.Object> injected,
        +                                  @Nullable
        +                                  io.micronaut.views.soy.SoyNamingMapProvider namingMapProvider)
        +
        Factory to create a page context object from a regular Java map, of string context properties to values of any + object type. Additionally, this interface allows specification of properties declared via
        @inject
        , and + also a SoyNamingMapProvider to override any globally-installed map. Under the hood, all context is + processed and converted/wrapped into Soy values. + +

        Note that style rewriting must be enabled for the

        namingMapProvider
        override to take effect.

        +
        +
        Parameters:
        +
        context - Context with which to render a Soy template - i.e. regular
        @param
        declarations.
        +
        injected - Injected parameters to provide to the render operation - available via
        @inject
        .
        +
        namingMapProvider - Override any globally-installed naming map provider.
        +
        Returns:
        +
        Fabricated page context object.
        +
        +
      • +
      + + + +
        +
      • +

        fromMap

        +
        public static PageContext fromMap​(@Nonnull
        +                                  java.util.Map<java.lang.String,​java.lang.Object> context,
        +                                  @Nonnull
        +                                  java.util.Map<java.lang.String,​java.lang.Object> injected,
        +                                  @Nullable
        +                                  io.micronaut.views.soy.SoyNamingMapProvider namingMapProvider,
        +                                  @Nullable
        +                                  io.micronaut.views.soy.SoyContext.SoyI18NContext i18n,
        +                                  @Nullable
        +                                  java.util.function.Predicate<java.lang.String> delegatePredicate)
        +
        Factory to create a page context object from a regular Java map, of string context properties to values of any + object type. Additionally, this interface allows specification of properties declared via
        @inject
        , and + also a SoyNamingMapProvider to override any globally-installed map. Under the hood, all context is + processed and converted/wrapped into Soy values. + +

        Note that style rewriting must be enabled for the

        namingMapProvider
        override to take effect.

        + +

        If the invoking developer wishes to apply internationalization via an XLIFF message bundle or pre-constructed + Soy Messages bundle, they may do so via `i18n`.

        +
        +
        Parameters:
        +
        context - Context with which to render a Soy template - i.e. regular
        @param
        declarations.
        +
        injected - Injected parameters to provide to the render operation - available via
        @inject
        .
        +
        namingMapProvider - Override any globally-installed naming map provider.
        +
        i18n - Internationalization context to apply.
        +
        delegatePredicate - Predicate for selecting a delegate package.
        +
        Returns:
        +
        Fabricated page context object.
        +
        +
      • +
      + + + +
        +
      • +

        fromProto

        +
        @Nonnull
        +public static PageContext fromProto​(@Nonnull
        +                                    tools.elide.page.Context pageContext)
        +
        Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`. Templates may opt-in to receive this value via a parameter declaration such as +
        @inject context: gust.page.Context
        .
        +
        +
        Parameters:
        +
        pageContext - Protobuf page context.
        +
        Returns:
        +
        Page context object.
        +
        +
      • +
      + + + +
        +
      • +

        fromProto

        +
        @Nonnull
        +public static PageContext fromProto​(@Nonnull
        +                                    tools.elide.page.Context pageContext,
        +                                    @Nonnull
        +                                    java.util.Map<java.lang.String,​java.lang.Object> props)
        +
        Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`. Templates may opt-in to receive this value via a parameter declaration such as +
        @inject context: gust.page.Context
        . + +

        This method offers the additional ability to specify

        props
        , which should correspond with any +
        @param
        declarations for the subject template to be rendered.

        +
        +
        Parameters:
        +
        pageContext - Protobuf page context.
        +
        props - Parameters to render the template with.
        +
        Returns:
        +
        Page context object.
        +
        +
      • +
      + + + +
        +
      • +

        fromProto

        +
        @Nonnull
        +public static PageContext fromProto​(@Nonnull
        +                                    tools.elide.page.Context pageContext,
        +                                    @Nonnull
        +                                    java.util.Map<java.lang.String,​java.lang.Object> props,
        +                                    @Nonnull
        +                                    java.util.Map<java.lang.String,​java.lang.Object> injected)
        +
        Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`. Templates may opt-in to receive this value via a parameter declaration such as +
        @inject context: gust.page.Context
        . + +

        This method offers the additional ability to specify

        props
        and
        injected
        values. Props + should correspond with any
        @param
        declarations for the subject template to be rendered. Injected values + are opted-into with
        @inject
        , and are overlaid on any existing injected values (may not override +
        context
        ).

        +
        +
        Parameters:
        +
        pageContext - Protobuf page context.
        +
        props - Parameters to render the template with.
        +
        injected - Additional injected values (may not contain a value at key
        context
        ).
        +
        Returns:
        +
        Page context object.
        +
        +
      • +
      + + + +
        +
      • +

        fromProto

        +
        @Nonnull
        +public static PageContext fromProto​(@Nonnull
        +                                    tools.elide.page.Context pageContext,
        +                                    @Nonnull
        +                                    java.util.Map<java.lang.String,​java.lang.Object> props,
        +                                    @Nonnull
        +                                    java.util.Map<java.lang.String,​java.lang.Object> injected,
        +                                    @Nullable
        +                                    io.micronaut.views.soy.SoyNamingMapProvider namingMapProvider)
        +
        Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`. Templates may opt-in to receive this value via a parameter declaration such as +
        @inject context: gust.page.Context
        . + +

        This method offers the additional ability to specify

        props
        and
        injected
        values. Props + should correspond with any
        @param
        declarations for the subject template to be rendered. Injected values + are opted-into with
        @inject
        , and are overlaid on any existing injected values (may not override +
        context
        ).

        + +

        If desired, an invoking developer may wish to specify a

        namingMapProvider
        . To have any effect, style + renaming must be active in application config. The naming map provider passed here overrides any globally-installed + style renaming map provider.

        +
        +
        Parameters:
        +
        pageContext - Protobuf page context.
        +
        props - Parameters to render the template with.
        +
        injected - Additional injected values (may not contain a value at key
        context
        ).
        +
        namingMapProvider - Naming map provider to override any globally-installed provider with, if enabled.
        +
        Returns:
        +
        Page context object.
        +
        +
      • +
      + + + +
        +
      • +

        fromProto

        +
        @Nonnull
        +public static PageContext fromProto​(@Nonnull
        +                                    tools.elide.page.Context pageContext,
        +                                    @Nonnull
        +                                    java.util.Map<java.lang.String,​java.lang.Object> props,
        +                                    @Nonnull
        +                                    java.util.Map<java.lang.String,​java.lang.Object> injected,
        +                                    @Nullable
        +                                    io.micronaut.views.soy.SoyNamingMapProvider namingMapProvider,
        +                                    @Nullable
        +                                    io.micronaut.views.soy.SoyContext.SoyI18NContext i18n,
        +                                    @Nullable
        +                                    java.util.function.Predicate<java.lang.String> delegatePredicate)
        +
        Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`. Templates may opt-in to receive this value via a parameter declaration such as +
        @inject context: gust.page.Context
        . + +

        This method offers the additional ability to specify

        props
        and
        injected
        values. Props + should correspond with any
        @param
        declarations for the subject template to be rendered. Injected values + are opted-into with
        @inject
        , and are overlaid on any existing injected values (may not override +
        context
        ).

        + +

        If desired, an invoking developer may wish to specify a

        namingMapProvider
        . To have any effect, style + renaming must be active in application config. The naming map provider passed here overrides any globally-installed + style renaming map provider.

        + +

        In addition to the other method variant above, this method allows internationalization via the `i18n` parameter, + which accepts a SoyContext.SoyI18NContext instance. A messages file, resource, or pre-constructed Soy + Message Bundle may be passed for translation during render.

        +
        +
        Parameters:
        +
        pageContext - Protobuf page context.
        +
        props - Parameters to render the template with.
        +
        injected - Additional injected values (may not contain a value at key
        context
        ).
        +
        namingMapProvider - Naming map provider to override any globally-installed provider with, if enabled.
        +
        i18n - Internationalization context to apply.
        +
        delegatePredicate - Predicate for selecting a Soy delegate package, as applicable.
        +
        Returns:
        +
        Page context object.
        +
        +
      • +
      + + + +
        +
      • +

        getPageContext

        +
        @Nonnull
        +public tools.elide.page.Context getPageContext()
        +
        Retrieve serializable server-side-rendered page context, which should be assigned to the render flow bound to this + context mediator.
        +
        +
        Specified by:
        +
        getPageContext in interface PageRender
        +
        Returns:
        +
        Server-side rendered page context.
        +
        +
      • +
      + + + +
        +
      • +

        getProperties

        +
        @Nonnull
        +public java.util.Map<java.lang.String,​java.lang.Object> getProperties()
        +
        Retrieve properties which should be made available via regular, declared `@param` statements.
        +
        +
        Specified by:
        +
        getProperties in interface io.micronaut.views.soy.SoyContextMediator
        +
        Returns:
        +
        Map of regular template properties.
        +
        +
      • +
      + + + +
        +
      • +

        getInjectedProperties

        +
        @Nonnull
        +public java.util.Map<java.lang.String,​java.lang.Object> getInjectedProperties​(@Nonnull
        +                                                                                    java.util.Map<java.lang.String,​java.lang.Object> framework)
        +
        Retrieve properties and values that should be made available via `@inject`.
        +
        +
        Specified by:
        +
        getInjectedProperties in interface io.micronaut.views.soy.SoyContextMediator
        +
        Parameters:
        +
        framework - Framework-injected properties.
        +
        Returns:
        +
        Map of injected properties and their values.
        +
        +
      • +
      + + + +
        +
      • +

        overrideNamingMap

        +
        @Nonnull
        +public java.util.Optional<io.micronaut.views.soy.SoyNamingMapProvider> overrideNamingMap()
        +
        Specify a Soy renaming map which overrides the globally-installed map, if any. Renaming must still be activated via + config, or manually, for the return value of this method to have any effect.
        +
        +
        Specified by:
        +
        overrideNamingMap in interface io.micronaut.views.soy.SoyContextMediator
        +
        Returns:
        +
        SoyNamingMapProvider that should be used for this render routine.
        +
        +
      • +
      + + + +
        +
      • +

        translate

        +
        public boolean translate()
        +
        Specify whether to enable translation via Soy message bundles. The default implementation of this method simply + checks whether a translation file has been set by the controller.
        +
        +
        Specified by:
        +
        translate in interface io.micronaut.views.soy.SoyContextMediator
        +
        Returns:
        +
        Whether to enable translation.
        +
        +
      • +
      + + + +
        +
      • +

        messagesFile

        +
        @Nonnull
        +public java.util.Optional<java.io.File> messagesFile()
        +
        Return the file that should be loaded and interpreted to perform translation when rendering this page. If no file, + resource, or bundle is provided, no translation occurs.
        +
        +
        Specified by:
        +
        messagesFile in interface io.micronaut.views.soy.SoyContextMediator
        +
        Returns:
        +
        File to apply for translations.
        +
        +
      • +
      + + + +
        +
      • +

        messagesResource

        +
        @Nonnull
        +public java.util.Optional<java.net.URL> messagesResource()
        +
        Return the URL to the resource that should be loaded and interpreted to perform translation when rendering this + page. If no resource, file, or bundle is provided, no translation occurs.
        +
        +
        Specified by:
        +
        messagesResource in interface io.micronaut.views.soy.SoyContextMediator
        +
        Returns:
        +
        Resource to apply for translations.
        +
        +
      • +
      + + + +
        +
      • +

        messageBundle

        +
        @Nonnull
        +public java.util.Optional<com.google.template.soy.msgs.SoyMsgBundle> messageBundle()
        +                                                                            throws java.io.IOException
        +
        Return the pre-fabricated Soy message bundle, or the interpreted Soy message bundle based on the installed messages + file or resource URL.
        +
        +
        Specified by:
        +
        messageBundle in interface io.micronaut.views.soy.SoyContextMediator
        +
        Returns:
        +
        Pre-fabricated or interpreted message bundle.
        +
        Throws:
        +
        java.io.IOException - If the bundle failed to load.
        +
        +
      • +
      + + + +
        +
      • +

        delegatePackage

        +
        @Nonnull
        +public java.util.Optional<java.util.function.Predicate<java.lang.String>> delegatePackage()
        +
        Return the delegate package that should be active when rendering the desired Soy output, if applicable. If no + delegate package should be applied, an empty Optional is returned.
        +
        +
        Specified by:
        +
        delegatePackage in interface io.micronaut.views.soy.SoyContextMediator
        +
        Returns:
        +
        Predicate specifying the delegate package, or Optional.empty().
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/PageContextManager.html b/docs/java/gust/backend/PageContextManager.html new file mode 100644 index 000000000..988443e3f --- /dev/null +++ b/docs/java/gust/backend/PageContextManager.html @@ -0,0 +1,2657 @@ + + + + + +PageContextManager + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Class PageContextManager

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.PageContextManager
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    PageRender, io.micronaut.views.soy.SoyContextMediator, java.io.Closeable, java.lang.AutoCloseable
    +
    +
    +
    @RequestScope
    +public class PageContextManager
    +extends java.lang.Object
    +implements java.io.Closeable, java.lang.AutoCloseable, PageRender
    +
    Manages the process of filling out PageContext objects before they are sealed, and delivered to Closure/Soy + to be reduced and rendered into content. + +

    This object may be used from controllers via dependency injection, or used via the base controller classes + provided as part of the framework.

    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      PageContextManageraddFeaturePolicy​(java.lang.String... policies) +
      Add a `Feature-Policy` entry for the current response render flow.
      +
      PageContextManageraddKeyword​(java.lang.String... keywords) +
      Add the provided page keywords for the current render flow.
      +
      PageContextManageraddLink​(java.lang.String relevance, + java.net.URI href, + java.util.Optional<java.lang.String> type) +
      Add a regular HTML metadata link to the current render flow, specified by the provided method parameters.
      +
      PageContextManageraddLink​(tools.elide.page.Context.PageLink.Builder link) +
      Add a regular HTML metadata link to the current render flow, specified by a Context.PageLink proto record.
      +
      PageContextManagerapplyOpenGraph​(tools.elide.page.Context.Metadata.OpenGraph.Builder content) +
      Apply the provided OpenGraph metadata configuration to the current OpenGraph metadata configuration, if any.
      +
      PageContextManagercdnPrefix​(java.util.Optional<java.lang.String> prefix) +
      Set the specified prefix as the Content Distribution Network hostname prefix to use when rendering asset + links for this HTTP cycle.
      +
      PageContextManagerclearDelegatePackage() +
      Clear any current delegate package.
      +
      PageContextManagerclearFeaturePolicy() +
      Clear the current set of `Feature-Policy` entries.
      +
      PageContextManagerclearGooglebot() +
      Clear the current value, if any, set for
      +
      PageContextManagerclearKeywords() +
      Clear the current set of page keywords for the current render flow.
      +
      PageContextManagerclearLinks() +
      Clear the set of HTML metadata links assigned to the current render flow.
      +
      PageContextManagerclearOpenGraph() +
      Clear any OpenGraph metadata configuration attached to the current render flow.
      +
      PageContextManagerclearRobots() +
      Clear the current value, if any, set for
      +
      voidclose() +
      Closes this stream and releases any system resources associated with it.
      +
      java.lang.StringcontentType() +
      Returns the currently-set content type for this response render flow.
      +
      PageContextManagercontentType​(java.lang.String type) +
      Sets the content type to return for the current render response flow.
      +
      java.util.Optional<java.util.function.Predicate<java.lang.String>>delegatePackage() +
      Fetch the active delegate package, or return Optional.empty().
      +
      PageContextManagerdelegatePackage​(java.lang.String packageName) +
      Set the active delegate package name, wrapped in an implied predicate which filters explicitly against the provided + name value.
      +
      PageContextManagerdelegatePackage​(java.util.function.Predicate<java.lang.String> packagePredicate) +
      Set the active delegate package predicate directly.
      +
      java.util.Optional<java.lang.String>description() +
      Retrieve the current value for the page description, set in the builder.
      +
      PageContextManagerdescription​(java.lang.String description) +
      Set the page description for the current render flow.
      +
      PageContextManagerdnsPrefetch​(java.lang.Iterable<java.lang.String> hosts) +
      Inject the specified list of hosts as DNS records to prefetch from the browser.
      +
      PageContextManagerdnsPrefetch​(java.lang.String... hosts) +
      Inject the specified DNS hostname(s) as records to prefetch from the browser.
      +
      booleanenableETags()
      PageContextManagerenableETags​(java.lang.Boolean enableETags) +
      Enable a dynamic ETag header value, which is computed from the rendered content produced by this page + context record.
      +
      <T> io.micronaut.http.MutableHttpResponse<T>finalizeResponse​(io.micronaut.http.HttpRequest<?> request, + io.micronaut.http.MutableHttpResponse<T> soyResponse, + T body, + java.security.MessageDigest digester)
      java.util.Optional<java.lang.Object>get​(java.lang.String key) +
      Safely retrieve a value from the current render context properties.
      +
      java.util.Optional<java.lang.Object>get​(java.lang.String key, + boolean injected) +
      Safely retrieve a value from either the current render context properties, or the current injected values.
      +
      java.util.Optional<java.lang.String>getCdnPrefix() +
      Retrieve the currently-configured CDN prefix value, if one exists.
      +
      tools.elide.page.Context.BuildergetContext() 
      tools.elide.page.Context.ClientHintsgetHints() +
      Return the set of interpreted Client Hints headers for the current request.
      +
      java.util.Map<java.lang.String,​java.lang.Object>getInjectedProperties​(java.util.Map<java.lang.String,​java.lang.Object> framework) +
      Retrieve properties and values that should be made available via `@inject`.
      +
      java.util.Optional<tools.elide.page.Context.Metadata.OpenGraph.Builder>getOpenGraph() +
      Retrieve OpenGraph settings specified in the current page context.
      +
      tools.elide.page.ContextgetPageContext() +
      Retrieve serializable server-side-rendered page context, which should be assigned to the render flow bound to this + context mediator.
      +
      java.util.Map<java.lang.String,​java.lang.Object>getProperties() +
      Retrieve properties which should be made available via regular, declared `@param` statements.
      +
      io.micronaut.http.HttpRequestgetRequest() +
      Return the currently-active HTTP request object, to which the current render/controller flow is bound.
      +
      PageContextManagerheader​(java.lang.String name, + java.lang.String value) +
      Affix an arbitrary HTTP header to the response eventually produced by this page context, assuming no errors occur.
      +
      PageContextManagerheader​(java.lang.String name, + java.lang.String value, + java.lang.Boolean force) +
      Affix an arbitrary HTTP header to the response eventually produced by this page context, assuming no errors occur.
      +
      <V> java.util.Optional<V>hint​(tools.elide.page.Context.ClientHint hint) +
      Attempt to retrieve an interpreted Client Hints client-indicated value from the current HTTP request.
      +
      PageContextManagerinject​(java.lang.String key, + java.lang.Object value) +
      Install an injected context value, at the named key provided.
      +
      booleanisLiveReload() +
      Indicate whether live-reload mode is enabled or not, which is governed by the built toolchain (i.e.
      +
      java.util.Optional<java.util.List<java.lang.String>>keywords() +
      Retrieve the current value for the page keywords, set in the builder.
      +
      java.util.Optional<java.lang.String>language() +
      Return the language value set for the current render routine - i.e.
      +
      PageContextManagerlanguage​(java.util.Optional<java.lang.String> language) +
      Set the value to send back in this response's Content-Language header.
      +
      java.util.Optional<java.util.List<tools.elide.page.Context.PageLink>>links() +
      Retrieve the full set of regular HTML metadata links attached to the current render flow.
      +
      java.util.Optional<com.google.template.soy.msgs.SoyMsgBundle>messageBundle() +
      Return the current pre-fabricated Soy message bundle that will be applied during the next render routine, if + available.
      +
      PageContextManagermessageBundle​(java.util.Optional<com.google.template.soy.msgs.SoyMsgBundle> soyMsgBundle) +
      Mount a pre-fabricated Soy message bundle for translation use during render.
      +
      java.util.Optional<java.io.File>messagesFile() +
      Return the current translation file that will be applied during the next render routine, if available.
      +
      PageContextManagermessagesFile​(java.util.Optional<java.io.File> xliffFile) +
      Mount a loaded XLIFF file for use during render.
      +
      java.util.Optional<java.net.URL>messagesResource() +
      Return the current translation resource that will be applied during the next render routine, if available.
      +
      PageContextManagermessagesResource​(java.util.Optional<java.net.URL> xliffData) +
      Load and mount a referenced XLIFF resource for use during render.
      +
      java.util.Optional<io.micronaut.views.soy.SoyNamingMapProvider>overrideNamingMap() +
      Specify a Soy renaming map which overrides the globally-installed map, if any.
      +
      PageContextManagerpreconnect​(java.lang.Iterable<java.lang.String> hosts) +
      Inject the specified list of hosts as pre-connect hints for the browser.
      +
      PageContextManagerpreconnect​(java.lang.String... hosts) +
      Inject the specified list of hosts as pre-connect hints for the browser.
      +
      java.util.Optional<java.util.List<tools.elide.page.Context.RDFPrefix>>prefixes() +
      Return the set of RDFa prefixes affixed to the current render flow, if any.
      +
      PageContextManagerput​(java.lang.String key, + java.lang.Object value) +
      Install a regular context value, at the named key provided.
      +
      PageContextrender() 
      PageContextManagerrewrite​(java.util.Optional<io.micronaut.views.soy.SoyNamingMapProvider> namingMapProvider) +
      Install, or uninstall, the request-scoped renaming map provider.
      +
      PageContextManagerscript​(java.lang.String name) +
      Include the specified JavaScript resource in the rendered page, according to the specified settings.
      +
      PageContextManagerscript​(java.lang.String name, + java.lang.Boolean defer, + java.lang.Boolean async) +
      Include the specified JavaScript resource in the rendered page, according to the specified settings.
      +
      PageContextManagerscript​(java.lang.String name, + java.lang.String id, + java.lang.Boolean defer, + java.lang.Boolean async, + java.lang.Boolean module, + java.lang.Boolean nomodule, + java.lang.Boolean preload, + java.lang.Boolean push) +
      Include the specified JavaScript resource in the rendered page, according to the specified settings.
      +
      PageContextManagerscript​(tools.elide.page.Context.Scripts.JavaScript.Builder script) +
      Include the specified JavaScript resource in the rendered page, according to enclosed settings (i.e.
      +
      PageContextManagersetFeaturePolicy​(java.util.Collection<java.lang.String> policies) +
      Overwrite the set of `Feature-Policy` entries for the current render flow.
      +
      PageContextManagersetGooglebot​(java.lang.String value) +
      Overwrite the value specified for the "googlebot" metadata key in the current render flow.
      +
      PageContextManagersetKeywords​(java.util.Collection<java.lang.String> keywords) +
      Overwrite the current set of page keywords for the current render flow.
      +
      PageContextManagersetLinks​(java.util.Collection<tools.elide.page.Context.PageLink.Builder> links) +
      Overwrite the set of page metadata links for the current render flow.
      +
      PageContextManagersetOpenGraph​(tools.elide.page.Context.Metadata.OpenGraph.Builder content) +
      Overwrite the OpenGraph metadata configuration for the current render flow, with the provided OpenGraph metadata + configuration.
      +
      PageContextManagersetPrefixes​(java.util.Optional<java.util.List<tools.elide.page.Context.RDFPrefix>> prefixes) +
      Overwrite the full set of RDFa prefixes for the current render flow.
      +
      PageContextManagersetRobots​(java.lang.String value) +
      Overwrite the value specified for the "robots" metadata key in the current render flow.
      +
      booleanstrongETags()
      PageContextManagerstylesheet​(java.lang.String name) +
      Include the specified CSS stylesheet in the rendered page with default settings.
      +
      PageContextManagerstylesheet​(java.lang.String name, + java.lang.String media) +
      Include the specified CSS stylesheet in the rendered page, along with the specified media setting.
      +
      PageContextManagerstylesheet​(java.lang.String name, + java.lang.String id, + java.lang.String media, + java.lang.Boolean preload, + java.lang.Boolean push) +
      Include the specified CSS stylesheet in the rendered page, according to the specified settings.
      +
      PageContextManagerstylesheet​(tools.elide.page.Context.Styles.Stylesheet.Builder stylesheet) +
      Include the specified CSS stylesheet resource in the rendered page, according to the enclosed settings (i.e.
      +
      PageContextManagersupportedClientHints​(java.util.Optional<java.lang.Iterable<tools.elide.page.Context.ClientHint>> hints, + java.util.Optional<java.lang.Long> ttl) +
      Enable the provided set of server-indicated client hint types.
      +
      PageContextManagersupportedClientHints​(tools.elide.page.Context.ClientHint... hints) +
      Enable the provided set of server-indicated client hint types.
      +
      java.util.Optional<java.lang.String>title() +
      Retrieve the current value for the page title, set in the builder.
      +
      PageContextManagertitle​(java.lang.String title) +
      Set the page title for the current render flow.
      +
      booleantranslate() +
      Indicate whether message translation is enabled for the rendering layer.
      +
      com.google.common.html.types.TrustedResourceUrlPrototrustedResource​(java.net.URI uri) +
      Generate a trusted resource URL for the provided Java URI.
      +
      com.google.common.html.types.TrustedResourceUrlPrototrustedResource​(java.net.URL url) +
      Generate a trusted resource URL for the provided Java URL.
      +
      PageContextManagervary​(java.lang.Iterable<java.lang.String> variance) +
      Append an HTTP request header considered as part of the Vary header in the response.
      +
      PageContextManagervary​(java.lang.String variance) +
      Append an HTTP request header considered as part of the Vary header in the response.
      +
      PageContextManagervary​(java.lang.String... variance) +
      Append an HTTP request header considered as part of the Vary header in the response.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getContext

        +
        @Nonnull
        +public tools.elide.page.Context.Builder getContext()
        +
        +
        Returns:
        +
        The current page context builder.
        +
        +
      • +
      + + + + + +
        +
      • +

        finalizeResponse

        +
        @Nonnull
        +public <T> io.micronaut.http.MutableHttpResponse<T> finalizeResponse​(@Nonnull
        +                                                                     io.micronaut.http.HttpRequest<?> request,
        +                                                                     @Nonnull
        +                                                                     io.micronaut.http.MutableHttpResponse<T> soyResponse,
        +                                                                     @Nonnull
        +                                                                     T body,
        +                                                                     @Nullable
        +                                                                     java.security.MessageDigest digester)
        +
        +
        Specified by:
        +
        finalizeResponse in interface io.micronaut.views.soy.SoyContextMediator
        +
        +
      • +
      + + + +
        +
      • +

        render

        +
        @Nonnull
        +public PageContext render()
        +
        +
        Returns:
        +
        Built context. After calling this method the first time, context may no longer be mutated.
        +
        +
      • +
      + + + +
        +
      • +

        contentType

        +
        @Nonnull
        +public java.lang.String contentType()
        +
        Returns the currently-set content type for this response render flow. This value generally defaults to + MediaType.TEXT_HTML.
        +
        +
        Returns:
        +
        Content type set to serve for this render flow.
        +
        +
      • +
      + + + +
        +
      • +

        contentType

        +
        @Nonnull
        +public PageContextManager contentType​(@Nonnull
        +                                      java.lang.String type)
        +
        Sets the content type to return for the current render response flow. This value should typically be set from one + of the options on MediaType, or as a raw Content-Type header value.
        +
        +
        Parameters:
        +
        type - Content type to set.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        addFeaturePolicy

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager addFeaturePolicy​(@Nonnull
        +                                           java.lang.String... policies)
        +
        Add a `Feature-Policy` entry for the current response render flow. This value will be appended to whatever current + values are set for the `Feature-Policy` header.
        +
        +
        Parameters:
        +
        policies - Policies to add for the current page. Do not pass `null`.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the provided policies.
        +
        +
      • +
      + + + +
        +
      • +

        clearFeaturePolicy

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager clearFeaturePolicy()
        +
        Clear the current set of `Feature-Policy` entries. If the app makes use of the framework's built-in page frame and + response cycle, the value will automatically be used.
        +
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        setFeaturePolicy

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager setFeaturePolicy​(@Nonnull
        +                                           java.util.Collection<java.lang.String> policies)
        +
        Overwrite the set of `Feature-Policy` entries for the current render flow. If the app makes use of the framework's + built-in page frame and response cycle, the value will automatically be used.
        +
        +
        Parameters:
        +
        policies - Policies to set for the current page. Do not pass `null`.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the policy collection to apply.
        +
        +
      • +
      + + + +
        +
      • +

        title

        +
        @Nonnull
        +public java.util.Optional<java.lang.String> title()
        +
        Retrieve the current value for the page title, set in the builder. If there is no value, Optional.empty() + is supplied as the return value.
        +
        +
        Returns:
        +
        Current page title, wrapped in an optional value.
        +
        +
      • +
      + + + +
        +
      • +

        title

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager title​(@Nonnull
        +                                java.lang.String title)
        +
        Set the page title for the current render flow. If the app makes use of the framework's built-in page frame, the + title will automatically be used.
        +
        +
        Parameters:
        +
        title - Title to set for the current page. Do not pass `null`.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the title.
        +
        +
      • +
      + + + +
        +
      • +

        description

        +
        @Nonnull
        +public java.util.Optional<java.lang.String> description()
        +
        Retrieve the current value for the page description, set in the builder. If there is no value set, + Optional.empty() is supplied as the return value.
        +
        +
        Returns:
        +
        Current page description, wrapped in an optional value.
        +
        +
      • +
      + + + +
        +
      • +

        description

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager description​(@Nonnull
        +                                      java.lang.String description)
        +
        Set the page description for the current render flow. If the app makes use of the framework's built-in page frame, + the value will automatically be used.
        +
        +
        Parameters:
        +
        description - Description to set for the current page. Do not pass `null`.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the description.
        +
        +
      • +
      + + + +
        +
      • +

        keywords

        +
        @Nonnull
        +public java.util.Optional<java.util.List<java.lang.String>> keywords()
        +
        Retrieve the current value for the page keywords, set in the builder. If there is no value set, + Optional.empty() is supplied as the return value.
        +
        +
        Returns:
        +
        Current page keywords, wrapped in an optional value.
        +
        +
      • +
      + + + +
        +
      • +

        addKeyword

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager addKeyword​(@Nonnull
        +                                     java.lang.String... keywords)
        +
        Add the provided page keywords for the current render flow. If the app makes use of the framework's built-in page + frame, the value will automatically be used.
        +
        +
        Parameters:
        +
        keywords - Keywords to set for the current page. Do not pass `null`.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the keywords.
        +
        +
      • +
      + + + +
        +
      • +

        clearKeywords

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager clearKeywords()
        +
        Clear the current set of page keywords for the current render flow. If the app makes use of the framework's + built-in page frame, the value will automatically be used.
        +
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        setKeywords

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager setKeywords​(@Nonnull
        +                                      java.util.Collection<java.lang.String> keywords)
        +
        Overwrite the current set of page keywords for the current render flow. If the app makes use of the framework's + built-in page frame, the value will automatically be used.
        +
        +
        Parameters:
        +
        keywords - Keywords to set for the current page. Do not pass `null`.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the keywords.
        +
        +
      • +
      + + + +
        +
      • +

        links

        +
        @Nonnull
        +public java.util.Optional<java.util.List<tools.elide.page.Context.PageLink>> links()
        +
        Retrieve the full set of regular HTML metadata links attached to the current render flow. If the app makes use of + the framework's built-in age frame, these links will automatically be applied.
        +
        +
        Returns:
        +
        Current set of links that will be listed in page metadata.
        +
        +
      • +
      + + + +
        +
      • +

        addLink

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager addLink​(@Nonnull
        +                                  tools.elide.page.Context.PageLink.Builder link)
        +
        Add a regular HTML metadata link to the current render flow, specified by a Context.PageLink proto record. + Adding via this method is sufficient for the link to make it into the rendered page, so long as the framework's + page frame is invoked.
        +
        +
        Parameters:
        +
        link - HTML metadata link to add to the page.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the link.
        +
        +
      • +
      + + + +
        +
      • +

        addLink

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager addLink​(@Nonnull
        +                                  java.lang.String relevance,
        +                                  @Nonnull
        +                                  java.net.URI href,
        +                                  @Nonnull
        +                                  java.util.Optional<java.lang.String> type)
        +
        Add a regular HTML metadata link to the current render flow, specified by the provided method parameters. Each + parameter maps to an attribute specified for the
        link
        HTML element.
        +
        +
        Parameters:
        +
        relevance - HTML "rel" attribute.
        +
        href - HTML "href" attribute.
        +
        type - HTML "type" attribute. Wrapped in an optional.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for any parameter.
        +
        +
      • +
      + + + +
        +
      • +

        clearLinks

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager clearLinks()
        +
        Clear the set of HTML metadata links assigned to the current render flow.
        +
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        setLinks

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager setLinks​(@Nonnull
        +                                   java.util.Collection<tools.elide.page.Context.PageLink.Builder> links)
        +
        Overwrite the set of page metadata links for the current render flow. If the app makes use of the framework's + built-in page frame, the value will automatically be used.
        +
        +
        Parameters:
        +
        links - Link directives to set for the current page. Do not pass `null`.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the provided links.
        +
        +
      • +
      + + + +
        +
      • +

        setRobots

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager setRobots​(@Nonnull
        +                                    java.lang.String value)
        +
        Overwrite the value specified for the "robots" metadata key in the current render flow. If the app makes use of the + framework's built-in page frame, the value will automatically be used.
        +
        +
        Parameters:
        +
        value - Robots metadata value to use.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the provided value.
        +
        +
      • +
      + + + +
        +
      • +

        setGooglebot

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager setGooglebot​(@Nonnull
        +                                       java.lang.String value)
        +
        Overwrite the value specified for the "googlebot" metadata key in the current render flow. If the app makes use of + the framework's built-in page frame, the value will automatically be used.
        +
        +
        Parameters:
        +
        value - Googlebot metadata value to use.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the provided value.
        +
        +
      • +
      + + + +
        +
      • +

        clearRobots

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager clearRobots()
        +
        Clear the current value, if any, set for
        robots
        in the current render flow metadata.
        +
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        clearGooglebot

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager clearGooglebot()
        +
        Clear the current value, if any, set for
        googlebot
        in the current render flow metadata.
        +
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        getOpenGraph

        +
        @Nonnull
        +public java.util.Optional<tools.elide.page.Context.Metadata.OpenGraph.Builder> getOpenGraph()
        +
        Retrieve OpenGraph settings specified in the current page context. This method always returns a builder, to avoid + re-builds of protocol buffers during page context construction. If no OpenGraph settings are available, an empty + Optional is returned instead.
        +
        +
        Returns:
        +
        Optional-wrapped OpenGraph settings, or Optional.empty().
        +
        +
      • +
      + + + +
        +
      • +

        setOpenGraph

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager setOpenGraph​(@Nonnull
        +                                       tools.elide.page.Context.Metadata.OpenGraph.Builder content)
        +
        Overwrite the OpenGraph metadata configuration for the current render flow, with the provided OpenGraph metadata + configuration. If the rendered page uses the framework's page template, the values will be serialized and rendered + into the page head.
        +
        +
        Parameters:
        +
        content - OpenGraph content to render.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the provided content.
        +
        +
      • +
      + + + +
        +
      • +

        applyOpenGraph

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager applyOpenGraph​(@Nonnull
        +                                         tools.elide.page.Context.Metadata.OpenGraph.Builder content)
        +
        Apply the provided OpenGraph metadata configuration to the current OpenGraph metadata configuration, if any. + If no OpenGraph metadata configuration is set, this method effectively overwrites it.
        +
        +
        Parameters:
        +
        content - OpenGraph content to merge and apply.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the provided content.
        +
        +
      • +
      + + + +
        +
      • +

        clearOpenGraph

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager clearOpenGraph()
        +
        Clear any OpenGraph metadata configuration attached to the current render flow. If there is no such configuration, + this method is a no-op.
        +
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        prefixes

        +
        @Nonnull
        +public java.util.Optional<java.util.List<tools.elide.page.Context.RDFPrefix>> prefixes()
        +
        Return the set of RDFa prefixes affixed to the current render flow, if any. If none are found, + Optional.empty() is returned.
        +
        +
        Returns:
        +
        Set of prefixes available on the current render flow, if any.
        +
        +
      • +
      + + + +
        +
      • +

        setPrefixes

        +
        @Nonnull
        +public PageContextManager setPrefixes​(@Nonnull
        +                                      java.util.Optional<java.util.List<tools.elide.page.Context.RDFPrefix>> prefixes)
        +
        Overwrite the full set of RDFa prefixes for the current render flow. If the rendered page uses the framework's page + template, the values will be serialized and rendered into the page head.
        +
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        script

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager script​(@Nonnull
        +                                 java.lang.String name)
        +
        Include the specified JavaScript resource in the rendered page, according to the specified settings. The module is + expected to exist and be included in the application's asset bundle.
        +
        +
        Parameters:
        +
        name - Name of the script module to load into the page.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the module name, or it cannot be located, or is invalid.
        +
        +
      • +
      + + + +
        +
      • +

        script

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager script​(@Nonnull
        +                                 java.lang.String name,
        +                                 @Nonnull
        +                                 java.lang.Boolean defer,
        +                                 @Nonnull
        +                                 java.lang.Boolean async)
        +
        Include the specified JavaScript resource in the rendered page, according to the specified settings. The module is + expected to exist and be included in the application's asset bundle. This variant allows specification of the most + frequent attributes used with scripts.
        +
        +
        Parameters:
        +
        name - Name of the script module to load into the page.
        +
        defer - Whether to add the defer attribute to the script tag.
        +
        async - Whether to add the async attribute to the script tag.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the module name, or it cannot be located, or is invalid.
        +
        +
      • +
      + + + +
        +
      • +

        script

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager script​(@Nonnull
        +                                 java.lang.String name,
        +                                 @Nullable
        +                                 java.lang.String id,
        +                                 @Nonnull
        +                                 java.lang.Boolean defer,
        +                                 @Nonnull
        +                                 java.lang.Boolean async,
        +                                 @Nonnull
        +                                 java.lang.Boolean module,
        +                                 @Nonnull
        +                                 java.lang.Boolean nomodule,
        +                                 @Nonnull
        +                                 java.lang.Boolean preload,
        +                                 @Nonnull
        +                                 java.lang.Boolean push)
        +
        Include the specified JavaScript resource in the rendered page, according to the specified settings. The module is + expected to exist and be included in the application's asset bundle. + +

        Behavior: Script assets included in this manner are always loaded in the document head, so be judicious + with defer if you are loading a significant amount of JavaScript. There are no default script assets. + Scripts are emitted in the order in which they are attached to the page context (i.e. via this method).

        + +

        Optimization: Activating the preload flag causes the script asset to be mentioned in a + Link header, which makes supporting browsers aware of it before loading the DOM. For more aggressive + circumstances, the push flag proactively pushes the asset to the browser (where supported), unless the + framework knows the client has seen the asset already. Where HTTP/2 is not supported, special triggering + Link headers may be used.

        +
        +
        Parameters:
        +
        name - Name of the script module to load into the page.
        +
        id - ID to assign the script block in the DOM, so it may be located dynamically.
        +
        defer - Whether to add the defer attribute to the script tag.
        +
        async - Whether to add the async attribute to the script tag.
        +
        module - Whether to add the module attribute to the script tag.
        +
        nomodule - Whether to add the nomodule attribute to the script tag.
        +
        preload - Whether to link/hint about the asset in response headers.
        +
        push - Whether to pro-actively push the asset, if we think the client doesn't have it.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the module name, or it cannot be located, or is invalid.
        +
        +
      • +
      + + + +
        +
      • +

        script

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager script​(@Nonnull
        +                                 tools.elide.page.Context.Scripts.JavaScript.Builder script)
        +
        Include the specified JavaScript resource in the rendered page, according to enclosed settings (i.e. respecting + defer, async, and other attributes). If the script asset has an ID, it will not be + passed through ID rewriting before being rendered. + +

        Behavior: Script assets included in this manner are always loaded in the document head, so be judicious + with defer if you are loading a significant amount of JavaScript. There are no default script assets. + Scripts are emitted in the order in which they are attached to the page context (i.e. via this method).

        +
        +
        Parameters:
        +
        script - Script asset to load in the rendered page output. Do not pass `null`.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the module name, or it cannot be located, or is invalid.
        +
        +
      • +
      + + + +
        +
      • +

        stylesheet

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager stylesheet​(@Nonnull
        +                                     java.lang.String name)
        +
        Include the specified CSS stylesheet in the rendered page with default settings. The module is expected to exist + and be included in the application's asset bundle.
        +
        +
        Parameters:
        +
        name - Name of the module to load.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the module name, or it cannot be located, or is invalid.
        +
        +
      • +
      + + + +
        +
      • +

        stylesheet

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager stylesheet​(@Nonnull
        +                                     java.lang.String name,
        +                                     @Nullable
        +                                     java.lang.String media)
        +
        Include the specified CSS stylesheet in the rendered page, along with the specified media setting. The module is + expected to exist and be included in the application's asset bundle.
        +
        +
        Parameters:
        +
        name - Name of the module to load.
        +
        media - Media assignment for the stylesheet.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the module name, or the module cannot be located.
        +
        +
      • +
      + + + +
        +
      • +

        stylesheet

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager stylesheet​(@Nonnull
        +                                     java.lang.String name,
        +                                     @Nullable
        +                                     java.lang.String id,
        +                                     @Nullable
        +                                     java.lang.String media,
        +                                     @Nonnull
        +                                     java.lang.Boolean preload,
        +                                     @Nonnull
        +                                     java.lang.Boolean push)
        +
        Include the specified CSS stylesheet in the rendered page, according to the specified settings. The module is + expected to exist and be included in the application's asset bundle. + +

        Optimization: Activating the preload flag causes the style asset to be mentioned in a + Link header, which makes supporting browsers aware of it before loading the DOM. For more aggressive + circumstances, the push flag proactively pushes the asset to the browser (where supported), unless the + framework knows the client has seen the asset already. Where HTTP/2 is not supported, special triggering + Link headers may be used.

        +
        +
        Parameters:
        +
        name - Name of the module to load.
        +
        id - ID to assign the link tag in the DOM.
        +
        media - Media assignment for the stylesheet.
        +
        preload - Whether to link/hint about the asset in response headers.
        +
        push - Whether to pro-actively push the asset, if we think the client doesn't have it.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the module name, or it cannot be located, or is invalid.
        +
        +
      • +
      + + + +
        +
      • +

        stylesheet

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager stylesheet​(@Nonnull
        +                                     tools.elide.page.Context.Styles.Stylesheet.Builder stylesheet)
        +
        Include the specified CSS stylesheet resource in the rendered page, according to the enclosed settings (i.e. + respecting properties like
        media
        ). If the stylesheet has an ID, it will not be passed through ID + rewriting before being rendered. + +

        Stylesheets included in this manner are always loaded in the head, via a link tag. If you want to defer loading + of styles, you'll have to do so from JS. Stylesheet links are emitted in the order in which they are attached to + the page context (i.e. via this method).

        +
        +
        Parameters:
        +
        stylesheet - Stylesheet asset to load in the rendered page output. Do not pass `null`.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If `null` is passed for the stylesheet.
        +
        +
      • +
      + + + +
        +
      • +

        put

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager put​(@Nonnull
        +                              java.lang.String key,
        +                              @Nonnull
        +                              java.lang.Object value)
        +
        Install a regular context value, at the named key provided. This will make the value available in any bound Soy + render flow via a
        @param
        declaration on the subject template to be rendered.
        +
        +
        Parameters:
        +
        key - Key at which to make this available as a param.
        +
        value - Value to provide for the parameter.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If the provided
        key
        is
        null
        , or a disallowed value, like +
        context
        (which cannot be overridden).
        +
        +
      • +
      + + + +
        +
      • +

        get

        +
        @Nonnull
        +public java.util.Optional<java.lang.Object> get​(@Nonnull
        +                                                java.lang.String key)
        +
        Safely retrieve a value from the current render context properties. If no property is found at the provided key, + an empty Optional is returned. Otherwise, an Optional is returned wrapping whatever value was + found.
        +
        +
        Parameters:
        +
        key - Key at which to retrieve the desired render context property.
        +
        Returns:
        +
        Optional-wrapped context property value.
        +
        +
      • +
      + + + +
        +
      • +

        get

        +
        @Nonnull
        +public java.util.Optional<java.lang.Object> get​(@Nonnull
        +                                                java.lang.String key,
        +                                                boolean injected)
        +
        Safely retrieve a value from either the current render context properties, or the current injected values. If no + value is found in whatever context we're looking in, an empty Optional is returned. Otherwise, an + Optional is returned wrapping whatever value was found.
        +
        +
        Parameters:
        +
        key - Key at which to retrieve the desired render property or injected value.
        +
        injected - Whether to look in the injected values, or regular property values.
        +
        Returns:
        +
        Empty optional if nothing was found, otherwise, the found value wrapped in an optional.
        +
        +
      • +
      + + + +
        +
      • +

        inject

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager inject​(@Nonnull
        +                                 java.lang.String key,
        +                                 @Nonnull
        +                                 java.lang.Object value)
        +
        Install an injected context value, at the named key provided. This will make the value available in any bound Soy + render flow via the
        @inject
        declaration, on any template in the render flow.
        +
        +
        Parameters:
        +
        key - Key at which to make this available as an injected value.
        +
        value - Value to provide for the parameter.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If the provided
        key
        is
        null
        , or a disallowed value, like +
        context
        (which cannot be overridden).
        +
        +
      • +
      + + + +
        +
      • +

        rewrite

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager rewrite​(@Nonnull
        +                                  java.util.Optional<io.micronaut.views.soy.SoyNamingMapProvider> namingMapProvider)
        +
        Install, or uninstall, the request-scoped renaming map provider. This object will be used to lookup style classes + and IDs for render-time rewriting. To disable an existing naming map provider override, simply pass an empty + Optional. + +

        If no renaming map provider is set here, but a global one is, and renaming is enabled, the global map will be + used. If a renaming map is set here, but renaming is not enabled, no renaming takes place.

        +
        +
        Parameters:
        +
        namingMapProvider - Renaming map provider to install, or Optional.empty() to uninstall any existing + overriding renaming map provider.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        translate

        +
        public boolean translate()
        +
        Indicate whether message translation is enabled for the rendering layer. This checks the presence of either a + translations file, or a translations URL resource. Additionally, a check is performed for any pre-fabricated Soy + messages bundle. If any of those are present, `true` is returned.
        +
        +
        Specified by:
        +
        translate in interface io.micronaut.views.soy.SoyContextMediator
        +
        Returns:
        +
        Whether translations will be enabled during render.
        +
        +
      • +
      + + + +
        +
      • +

        messagesFile

        +
        @Nonnull
        +public PageContextManager messagesFile​(@Nonnull
        +                                       java.util.Optional<java.io.File> xliffFile)
        +
        Mount a loaded XLIFF file for use during render. Messages mentioned in the XLIFF file and in the corresponding + template are replaced as the renderer proceeds through the template. Passing Optional.empty() clears any + active translation file.
        +
        +
        Parameters:
        +
        xliffFile - Translations file to apply.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        messagesFile

        +
        @Nonnull
        +public java.util.Optional<java.io.File> messagesFile()
        +
        Return the current translation file that will be applied during the next render routine, if available.
        +
        +
        Specified by:
        +
        messagesFile in interface io.micronaut.views.soy.SoyContextMediator
        +
        Returns:
        +
        Translation file, or Optional.empty().
        +
        +
      • +
      + + + +
        +
      • +

        messagesResource

        +
        @Nonnull
        +public PageContextManager messagesResource​(@Nonnull
        +                                           java.util.Optional<java.net.URL> xliffData)
        +
        Load and mount a referenced XLIFF resource for use during render. Messages mentioned in the XLIFF resource and in + the corresponding template are replaced as the renderer proceeds through the template.
        +
        +
        Parameters:
        +
        xliffData - Translations data to apply.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        messagesResource

        +
        @Nonnull
        +public java.util.Optional<java.net.URL> messagesResource()
        +
        Return the current translation resource that will be applied during the next render routine, if available.
        +
        +
        Specified by:
        +
        messagesResource in interface io.micronaut.views.soy.SoyContextMediator
        +
        Returns:
        +
        Translation resource, or Optional.empty().
        +
        +
      • +
      + + + +
        +
      • +

        messageBundle

        +
        @Nonnull
        +public PageContextManager messageBundle​(@Nonnull
        +                                        java.util.Optional<com.google.template.soy.msgs.SoyMsgBundle> soyMsgBundle)
        +
        Mount a pre-fabricated Soy message bundle for translation use during render. Messages mentioned in the bundle and + in the corresponding template are replaced as the renderer proceeds through the template.
        +
        +
        Parameters:
        +
        soyMsgBundle - Soy message bundle to apply.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        messageBundle

        +
        @Nonnull
        +public java.util.Optional<com.google.template.soy.msgs.SoyMsgBundle> messageBundle()
        +                                                                            throws java.io.IOException
        +
        Return the current pre-fabricated Soy message bundle that will be applied during the next render routine, if + available.
        +
        +
        Specified by:
        +
        messageBundle in interface io.micronaut.views.soy.SoyContextMediator
        +
        Returns:
        +
        Soy message bundle, or Optional.empty().
        +
        Throws:
        +
        java.io.IOException
        +
        +
      • +
      + + + +
        +
      • +

        delegatePackage

        +
        @Nonnull
        +public java.util.Optional<java.util.function.Predicate<java.lang.String>> delegatePackage()
        +
        Fetch the active delegate package, or return Optional.empty(). Either a Predicate is returned which + is invoked to match the delegate package, or Optional.empty() indicates that no package should be available + (or that defaults should be used, where available).
        +
        +
        Specified by:
        +
        delegatePackage in interface io.micronaut.views.soy.SoyContextMediator
        +
        Returns:
        +
        Returns the active predicate, as applicable.
        +
        +
      • +
      + + + +
        +
      • +

        delegatePackage

        +
        @Nonnull
        +public PageContextManager delegatePackage​(@Nonnull
        +                                          java.lang.String packageName)
        +
        Set the active delegate package name, wrapped in an implied predicate which filters explicitly against the provided + name value. If the referenced Soy package is included in the build, it should be selected explicitly as the active + package during render. + +

        Calling this method overwrites any current delegate package.

        +
        +
        Parameters:
        +
        packageName - Package name to match for delegation.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        delegatePackage

        +
        @Nonnull
        +public PageContextManager delegatePackage​(@Nonnull
        +                                          java.util.function.Predicate<java.lang.String> packagePredicate)
        +
        Set the active delegate package predicate directly. This predicate is invoked to match a delegate package at + runtime, if and when one is needed. + +

        Calling this method overwrites any current delegate package.

        +
        +
        Parameters:
        +
        packagePredicate - Predicate to match packages.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        clearDelegatePackage

        +
        @Nonnull
        +public PageContextManager clearDelegatePackage()
        +
        Clear any current delegate package.
        +
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        header

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager header​(@Nonnull
        +                                 java.lang.String name,
        +                                 @Nonnull
        +                                 java.lang.String value)
        +
        Affix an arbitrary HTTP header to the response eventually produced by this page context, assuming no errors occur. + If an error occurs while rendering, an error page is served without the additional header (unless + force is passed via the companion method to this one).
        +
        +
        Parameters:
        +
        name - Name of the header value to affix to the response.
        +
        value - Value of the header to affix to the response, at name.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        header

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager header​(@Nonnull
        +                                 java.lang.String name,
        +                                 @Nonnull
        +                                 java.lang.String value,
        +                                 @Nonnull
        +                                 java.lang.Boolean force)
        +
        Affix an arbitrary HTTP header to the response eventually produced by this page context, assuming no errors occur. + If an error occurs while rendering, an error page is served without the additional header (unless + force is passed via the companion method to this one).
        +
        +
        Parameters:
        +
        name - Name of the header value to affix to the response.
        +
        value - Value of the header to affix to the response, at name.
        +
        force - Whether to force the header to be applied, even when an error occurs.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        enableETags

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager enableETags​(@Nonnull
        +                                      java.lang.Boolean enableETags)
        +
        Enable a dynamic ETag header value, which is computed from the rendered content produced by this page + context record.
        +
        +
        Parameters:
        +
        enableETags - Whether to enable the ETag header.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        supportedClientHints

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager supportedClientHints​(java.util.Optional<java.lang.Iterable<tools.elide.page.Context.ClientHint>> hints,
        +                                               @Nonnull
        +                                               java.util.Optional<java.lang.Long> ttl)
        +
        Enable the provided set of server-indicated client hint types. If the client supports any of the indicated types, + it will enclose matching client-hints accordingly, on subsequent requests for resources. If an empty optional + (Optional.empty()) is passed, the current set of client hints are cleared. This method is additive.
        +
        +
        Parameters:
        +
        hints - Client hints to indicate as supported by the server.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        supportedClientHints

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager supportedClientHints​(tools.elide.page.Context.ClientHint... hints)
        +
        Enable the provided set of server-indicated client hint types. This method is additive. If the client supports any + of the indicated types, it will enclose matching client-hints accordingly, on subsequent requests for resources.
        +
        +
        Parameters:
        +
        hints - Client hints to indicate as supported by the server.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        language

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager language​(@Nonnull
        +                                   java.util.Optional<java.lang.String> language)
        +
        Set the value to send back in this response's Content-Language header. If an Optional.empty() + instance is passed, no header is sent.
        +
        +
        Parameters:
        +
        language - Language, or empty value, to send.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        language

        +
        @Nonnull
        +public java.util.Optional<java.lang.String> language()
        +
        Return the language value set for the current render routine - i.e. bound to the current request cycle. This is + often driven by the user's browser settings.
        +
        +
        Returns:
        +
        Current language for this request cycle.
        +
        +
      • +
      + + + +
        +
      • +

        vary

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager vary​(@Nonnull
        +                               java.lang.String variance)
        +
        Append an HTTP request header considered as part of the Vary header in the response. These values are de- + duplicated before joining and affixing.
        +
        +
        Parameters:
        +
        variance - HTTP request header which causes the associated response to vary.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        vary

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager vary​(@Nonnull
        +                               java.lang.String... variance)
        +
        Append an HTTP request header considered as part of the Vary header in the response. These values are de- + duplicated before joining and affixing.
        +
        +
        Parameters:
        +
        variance - HTTP request header which causes the associated response to vary.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        vary

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager vary​(@Nonnull
        +                               java.lang.Iterable<java.lang.String> variance)
        +
        Append an HTTP request header considered as part of the Vary header in the response. These values are de- + duplicated before joining and affixing.
        +
        +
        Parameters:
        +
        variance - HTTP request header which causes the associated response to vary.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        cdnPrefix

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager cdnPrefix​(@Nonnull
        +                                    java.util.Optional<java.lang.String> prefix)
        +
        Set the specified prefix as the Content Distribution Network hostname prefix to use when rendering asset + links for this HTTP cycle. Do not use a user-provided value for this. If no prefix is set, or one is cleared + by passing Optional.empty(), configuration will be read and used instead.
        +
        +
        Parameters:
        +
        prefix - Prefix to apply as a CDN hostname for static assets.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        getCdnPrefix

        +
        @Nonnull
        +public java.util.Optional<java.lang.String> getCdnPrefix()
        +
        Retrieve the currently-configured CDN prefix value, if one exists. If none can be located, return an empty optional + via Optional.empty().
        +
        +
        Returns:
        +
        Current CDN prefix value.
        +
        +
      • +
      + + + +
        +
      • +

        dnsPrefetch

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager dnsPrefetch​(java.lang.Iterable<java.lang.String> hosts)
        +
        Inject the specified list of hosts as DNS records to prefetch from the browser. There is no guarantee made by the + browser that the records will be fetched, it's just a performance hint.
        +
        +
        Parameters:
        +
        hosts - Hosts to add to the DNS prefetch list.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        dnsPrefetch

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager dnsPrefetch​(java.lang.String... hosts)
        +
        Inject the specified DNS hostname(s) as records to prefetch from the browser. There is no guarantee made by the + browser that the records will be fetched, it's just a performance hint.
        +
        +
        Parameters:
        +
        hosts - Hosts to add to the DNS prefetch list.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        preconnect

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager preconnect​(java.lang.Iterable<java.lang.String> hosts)
        +
        Inject the specified list of hosts as pre-connect hints for the browser. There is no guarantee made by the browser + that the connections will be established, it's just a performance hint.
        +
        +
        Parameters:
        +
        hosts - Hosts to add to the server pre-connection list.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        preconnect

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public PageContextManager preconnect​(java.lang.String... hosts)
        +
        Inject the specified list of hosts as pre-connect hints for the browser. There is no guarantee made by the browser + that the connections will be established, it's just a performance hint.
        +
        +
        Parameters:
        +
        hosts - Hosts to add to the server pre-connection list.
        +
        Returns:
        +
        Current page context manager (for call chain-ability).
        +
        +
      • +
      + + + +
        +
      • +

        trustedResource

        +
        @Nonnull
        +public com.google.common.html.types.TrustedResourceUrlProto trustedResource​(@Nonnull
        +                                                                            java.net.URL url)
        +
        Generate a trusted resource URL for the provided Java URL.
        +
        +
        Parameters:
        +
        url - Pre-ordained trusted resource URL.
        +
        Returns:
        +
        Trusted resource URL specification proto.
        +
        +
      • +
      + + + +
        +
      • +

        trustedResource

        +
        @Nonnull
        +public com.google.common.html.types.TrustedResourceUrlProto trustedResource​(@Nonnull
        +                                                                            java.net.URI uri)
        +
        Generate a trusted resource URL for the provided Java URI.
        +
        +
        Parameters:
        +
        uri - Pre-ordained trusted resource URI.
        +
        Returns:
        +
        Trusted resource URL specification proto.
        +
        +
      • +
      + + + +
        +
      • +

        hint

        +
        @Nonnull
        +public <V> java.util.Optional<V> hint​(@Nonnull
        +                                      tools.elide.page.Context.ClientHint hint)
        +
        Attempt to retrieve an interpreted Client Hints client-indicated value from the current HTTP request. If no + value can be found, or no valid value can be decoded for the hint type specified, Optional.empty() is + returned.
        +
        +
        Type Parameters:
        +
        V - Value type to return for this hint. If incorrect, a warning is logged and an empty value returned.
        +
        Parameters:
        +
        hint - Hint type to look for on the current request.
        +
        Returns:
        +
        Optional-wrapped found value, or Optional.empty() if no value could be located.
        +
        +
      • +
      + + + +
        +
      • +

        getRequest

        +
        @Nonnull
        +public io.micronaut.http.HttpRequest getRequest()
        +
        Return the currently-active HTTP request object, to which the current render/controller flow is bound.
        +
        +
        Returns:
        +
        Active HTTP request object.
        +
        +
      • +
      + + + +
        +
      • +

        getHints

        +
        @Nonnull
        +public tools.elide.page.Context.ClientHints getHints()
        +
        Return the set of interpreted Client Hints headers for the current request.
        +
        +
        Returns:
        +
        Client hints configuration.
        +
        +
      • +
      + + + +
        +
      • +

        enableETags

        +
        public boolean enableETags()
        +
        +
        Specified by:
        +
        enableETags in interface io.micronaut.views.soy.SoyContextMediator
        +
        +
      • +
      + + + +
        +
      • +

        strongETags

        +
        public boolean strongETags()
        +
        +
        Specified by:
        +
        strongETags in interface io.micronaut.views.soy.SoyContextMediator
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        public void close()
        +
        Closes this stream and releases any system resources associated with it. If the stream is already closed then + invoking this method has no effect. + +

        As noted in AutoCloseable.close(), cases where the close may fail require careful attention. It is + strongly advised to relinquish the underlying resources and to internally mark the Closeable as + closed, prior to throwing the IOException.

        +
        +
        Specified by:
        +
        close in interface java.lang.AutoCloseable
        +
        Specified by:
        +
        close in interface java.io.Closeable
        +
        +
      • +
      + + + +
        +
      • +

        getPageContext

        +
        @Nonnull
        +public tools.elide.page.Context getPageContext()
        +
        Retrieve serializable server-side-rendered page context, which should be assigned to the render flow bound to this + context mediator.
        +
        +
        Specified by:
        +
        getPageContext in interface PageRender
        +
        Returns:
        +
        Server-side rendered page context.
        +
        +
      • +
      + + + +
        +
      • +

        getProperties

        +
        @Nonnull
        +public java.util.Map<java.lang.String,​java.lang.Object> getProperties()
        +
        Retrieve properties which should be made available via regular, declared `@param` statements.
        +
        +
        Specified by:
        +
        getProperties in interface io.micronaut.views.soy.SoyContextMediator
        +
        Returns:
        +
        Map of regular template properties.
        +
        +
      • +
      + + + +
        +
      • +

        getInjectedProperties

        +
        @Nonnull
        +public java.util.Map<java.lang.String,​java.lang.Object> getInjectedProperties​(@Nonnull
        +                                                                                    java.util.Map<java.lang.String,​java.lang.Object> framework)
        +
        Retrieve properties and values that should be made available via `@inject`.
        +
        +
        Specified by:
        +
        getInjectedProperties in interface io.micronaut.views.soy.SoyContextMediator
        +
        Parameters:
        +
        framework - Framework-injected properties.
        +
        Returns:
        +
        Map of injected properties and their values.
        +
        +
      • +
      + + + +
        +
      • +

        overrideNamingMap

        +
        @Nonnull
        +public java.util.Optional<io.micronaut.views.soy.SoyNamingMapProvider> overrideNamingMap()
        +
        Specify a Soy renaming map which overrides the globally-installed map, if any. Renaming must still be activated via + config, or manually, for the return value of this method to have any effect.
        +
        +
        Specified by:
        +
        overrideNamingMap in interface io.micronaut.views.soy.SoyContextMediator
        +
        Returns:
        +
        SoyNamingMapProvider that should be used for this render routine.
        +
        +
      • +
      + + + +
        +
      • +

        isLiveReload

        +
        public boolean isLiveReload()
        +
        Indicate whether live-reload mode is enabled or not, which is governed by the built toolchain (i.e. a Bazel + condition, activated by the Makefile, which injects a JDK system property). Live-reload features additionally + require dev mode to be active.
        +
        +
        Returns:
        +
        Whether live-reload is enabled.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/PageRender.html b/docs/java/gust/backend/PageRender.html new file mode 100644 index 000000000..a3f8327f2 --- /dev/null +++ b/docs/java/gust/backend/PageRender.html @@ -0,0 +1,337 @@ + + + + + +PageRender + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Interface PageRender

+
+
+
+
    +
  • +
    +
    All Superinterfaces:
    +
    io.micronaut.views.soy.SoyContextMediator
    +
    +
    +
    All Known Implementing Classes:
    +
    PageContext, PageContextManager
    +
    +
    +
    public interface PageRender
    +extends io.micronaut.views.soy.SoyContextMediator
    +
    Interface by which protobuf-driven Soy render context can be managed and orchestrated by a custom PageContext + object. Provides the ability to specify variables for
    @param
    and
    @inject
    Soy declarations, and + the ability to override objects like the SoyNamingMapProvider.
    +
    +
    Author:
    +
    Sam Gammon (sam@momentum.io)
    +
    See Also:
    +
    Default implementation of this interface
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + +
      Fields 
      Modifier and TypeFieldDescription
      static java.lang.StringPAGE_CONTEXT_IJ_NAME +
      Name at which proto-context is injected.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods 
      Modifier and TypeMethodDescription
      tools.elide.page.ContextgetPageContext() +
      Retrieve serializable server-side-rendered page context, which should be assigned to the render flow bound to this + context mediator.
      +
      +
        +
      • + + +

        Methods inherited from interface io.micronaut.views.soy.SoyContextMediator

        +delegatePackage, enableETags, finalizeResponse, getInjectedProperties, getProperties, messageBundle, messagesFile, messagesResource, overrideNamingMap, strongETags, translate
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getPageContext

        +
        @Nonnull
        +tools.elide.page.Context getPageContext()
        +
        Retrieve serializable server-side-rendered page context, which should be assigned to the render flow bound to this + context mediator.
        +
        +
        Returns:
        +
        Server-side rendered page context.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/TemplateProvider.html b/docs/java/gust/backend/TemplateProvider.html new file mode 100644 index 000000000..83179b504 --- /dev/null +++ b/docs/java/gust/backend/TemplateProvider.html @@ -0,0 +1,468 @@ + + + + + +TemplateProvider + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.backend
+

Class TemplateProvider

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.TemplateProvider
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    io.micronaut.views.soy.SoyFileSetProvider, io.micronaut.views.soy.SoyNamingMapProvider
    +
    +
    +
    @Singleton
    +public final class TemplateProvider
    +extends java.lang.Object
    +implements io.micronaut.views.soy.SoyFileSetProvider, io.micronaut.views.soy.SoyNamingMapProvider
    +
    Default Soy template provider.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + +
      Constructors 
      ConstructorDescription
      TemplateProvider() 
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods Deprecated Methods 
      Modifier and TypeMethodDescription
      com.google.template.soy.shared.SoyCssRenamingMapcssRenamingMap() +
      By default, return `null` for the Soy CSS renaming map.
      +
      com.google.template.soy.shared.SoyIdRenamingMapidRenamingMap() +
      By default, return `null` for the Soy ID renaming map.
      +
      TemplateProviderinstallRenamingMaps​(com.google.template.soy.shared.SoyCssRenamingMap cssMap, + com.google.template.soy.shared.SoyIdRenamingMap idMap) +
      Install a Soy-compatible style renaming map for CSS classes, and optionally one for CSS IDs as well.
      +
      TemplateProviderinstallTemplates​(com.google.template.soy.jbcsrc.api.SoySauce compiled) +
      Install a set of compiled templates manually into the local template provider context.
      +
      com.google.template.soy.jbcsrc.api.SoySauceprovideCompiledTemplates() +
      Provide the compiled Soy file set for embedded templates.
      +
      com.google.template.soy.SoyFileSetprovideSoyFileSet() +
      Deprecated. +
      Soy file sets are slow due to runtime template interpretation.
      +
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        installTemplates

        +
        public TemplateProvider installTemplates​(@Nonnull
        +                                         com.google.template.soy.jbcsrc.api.SoySauce compiled)
        +
        Install a set of compiled templates manually into the local template provider context. These templates will be used + instead of any detected templates on the classpath (via the defaultCompiledTemplates).
        +
        +
        Parameters:
        +
        compiled - Compiled templates to install.
        +
        Returns:
        +
        Template provider, for method chaining.
        +
        +
      • +
      + + + +
        +
      • +

        installRenamingMaps

        +
        public TemplateProvider installRenamingMaps​(@Nonnull
        +                                            com.google.template.soy.shared.SoyCssRenamingMap cssMap,
        +                                            @Nullable
        +                                            com.google.template.soy.shared.SoyIdRenamingMap idMap)
        +
        Install a Soy-compatible style renaming map for CSS classes, and optionally one for CSS IDs as well. These maps are + installed locally and provided to Soy during template render operations. Any templates rendered subsequent to this + call should have the rewritten styles applied in the DOM, and with any served assets associated with the page. + + If an existing ID rewriting map is mounted, a call to unseat it with `null` will be ignored.
        +
        +
        Parameters:
        +
        cssMap - CSS renaming map to apply during render.
        +
        idMap - Optional ID renaming map to apply during render.
        +
        Returns:
        +
        Template provider, for method chaining.
        +
        +
      • +
      + + + +
        +
      • +

        provideSoyFileSet

        +
        @Deprecated
        +@Nullable
        +public com.google.template.soy.SoyFileSet provideSoyFileSet()
        +
        Deprecated. +
        Soy file sets are slow due to runtime template interpretation. Please use compiled templates via the + SoySauce class instead. See the see-also listings for this method for more information.
        +
        +
        Provide a Soy file set for the embedded templates.
        +
        +
        Specified by:
        +
        provideSoyFileSet in interface io.micronaut.views.soy.SoyFileSetProvider
        +
        Returns:
        +
        Prepared Soy file set.
        +
        See Also:
        +
        to acquire compiled template instances.
        +
        +
      • +
      + + + +
        +
      • +

        provideCompiledTemplates

        +
        @Nonnull
        +public com.google.template.soy.jbcsrc.api.SoySauce provideCompiledTemplates()
        +
        Provide the compiled Soy file set for embedded templates.
        +
        +
        Specified by:
        +
        provideCompiledTemplates in interface io.micronaut.views.soy.SoyFileSetProvider
        +
        Returns:
        +
        Pre-compiled Soy templates.
        +
        +
      • +
      + + + +
        +
      • +

        cssRenamingMap

        +
        @Nullable
        +public com.google.template.soy.shared.SoyCssRenamingMap cssRenamingMap()
        +
        By default, return `null` for the Soy CSS renaming map.
        +
        +
        Specified by:
        +
        cssRenamingMap in interface io.micronaut.views.soy.SoyNamingMapProvider
        +
        Returns:
        +
        `null`, by default.
        +
        +
      • +
      + + + +
        +
      • +

        idRenamingMap

        +
        @Nullable
        +public com.google.template.soy.shared.SoyIdRenamingMap idRenamingMap()
        +
        By default, return `null` for the Soy ID renaming map.
        +
        +
        Specified by:
        +
        idRenamingMap in interface io.micronaut.views.soy.SoyNamingMapProvider
        +
        Returns:
        +
        `null`, by default.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/annotations/Js.html b/docs/java/gust/backend/annotations/Js.html new file mode 100644 index 000000000..34ffcf73c --- /dev/null +++ b/docs/java/gust/backend/annotations/Js.html @@ -0,0 +1,399 @@ + + + + + +Js + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Annotation Type Js

+
+
+
+
    +
  • +
    +
    @Documented
    +@Introduction
    +@Target(METHOD)
    +@Retention(RUNTIME)
    +public @interface Js
    +
    Links a script module to a given controller endpoint, such that it will automatically be included and loaded in the + head of the page with the specified settings.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Required Element Summary

      + + + + + + + + + + + + +
      Required Elements 
      Modifier and TypeRequired ElementDescription
      java.lang.Stringvalue +
      Module name for the script module.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Optional Element Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Optional Elements 
      Modifier and TypeOptional ElementDescription
      booleanasync +
      Whether to completely unlink this script's loading flow from the DOM (i.e.
      +
      booleandefer +
      Whether to defer loading this script until the body DOM has initialized.
      +
      booleanmodule +
      Whether to treat this script entry as a module.
      +
      booleannoModule +
      Whether to treat this script entry as a module fallback.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Element Detail

      + + + +
        +
      • +

        value

        +
        @Nonnull
        +java.lang.String value
        +
        Module name for the script module. This is generally the Closure module path (for example, `app.entrypoint`).
        +
        +
        Returns:
        +
        Module name for this script module.
        +
        +
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +
        +
      • +

        defer

        +
        boolean defer
        +
        Whether to defer loading this script until the body DOM has initialized.
        +
        +
        Returns:
        +
        Defaults to true.
        +
        +
        +
        Default:
        +
        true
        +
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        async

        +
        boolean async
        +
        Whether to completely unlink this script's loading flow from the DOM (i.e. async mode).
        +
        +
        Returns:
        +
        Defaults to false.
        +
        +
        +
        Default:
        +
        false
        +
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        module

        +
        boolean module
        +
        Whether to treat this script entry as a module.
        +
        +
        Returns:
        +
        Defaults to false.
        +
        +
        +
        Default:
        +
        false
        +
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        noModule

        +
        boolean noModule
        +
        Whether to treat this script entry as a module fallback.
        +
        +
        Returns:
        +
        Defaults to false.
        +
        +
        +
        Default:
        +
        false
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/annotations/Page.html b/docs/java/gust/backend/annotations/Page.html new file mode 100644 index 000000000..9b2017af7 --- /dev/null +++ b/docs/java/gust/backend/annotations/Page.html @@ -0,0 +1,753 @@ + + + + + +Page + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Annotation Type Page

+
+
+
+
    +
  • +
    +
    @Documented
    +@Introduction
    +@Target(METHOD)
    +@Retention(RUNTIME)
    +public @interface Page
    +
    Specifies settings for a given page method, that may later be applied via outputs like the application's site-map, or + other metadata assets.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Fields 
      Modifier and TypeFieldsDescription
      static java.lang.StringCANONICAL +
      Property name for the canonical URL.
      +
      static java.lang.StringCHANGE_FREQUENCY +
      Property name for the page's change-frequency value.
      +
      static java.lang.StringGOOGLEBOT_SETTINGS +
      Property for Googlebot-specific settings.
      +
      static java.lang.StringLAST_MODIFIED +
      Property name for the page's last-modified date.
      +
      static java.lang.StringNO_VALUE +
      Sentinel for no-value strings.
      +
      static java.lang.StringPRIORITY +
      Property name for the page's priority value.
      +
      static java.lang.StringROBOTS_ENABLE +
      Property name for robots enable/disable.
      +
      static java.lang.StringROBOTS_SETTINGS +
      Property name for indexing robot settings.
      +
      static java.lang.StringSITEMAP +
      Property name for sitemap enable/disable.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Required Element Summary

      + + + + + + + + + + + + +
      Required Elements 
      Modifier and TypeRequired ElementDescription
      java.lang.Stringvalue +
      Simple name / tag for the page.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Optional Element Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Optional Elements 
      Modifier and TypeOptional ElementDescription
      java.lang.StringcanonicalUrl +
      Specifies the canonical URL to use in sitemaps and robots.txt listings, etc, for this page, if applicable.
      +
      tools.elide.backend.builtin.Sitemap.ChangeFrequencychangeFrequency +
      Retrieve the change frequency set for this page, if any.
      +
      booleanenableIndexing +
      Whether to allow robots on the selected page.
      +
      java.lang.StringgooglebotSettings +
      Settings for Googlebot on the selected page.
      +
      java.lang.StringlastModified +
      Retrieve the last-modified string for this page.
      +
      java.lang.Stringpriority +
      Priority value for the page.
      +
      java.lang.StringrobotsSettings +
      Generic settings for indexing robots.
      +
      booleansitemap +
      Whether to list this page in the site map.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        ROBOTS_ENABLE

        +
        static final java.lang.String ROBOTS_ENABLE
        +
        Property name for robots enable/disable.
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        ROBOTS_SETTINGS

        +
        static final java.lang.String ROBOTS_SETTINGS
        +
        Property name for indexing robot settings.
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        GOOGLEBOT_SETTINGS

        +
        static final java.lang.String GOOGLEBOT_SETTINGS
        +
        Property for Googlebot-specific settings.
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        SITEMAP

        +
        static final java.lang.String SITEMAP
        +
        Property name for sitemap enable/disable.
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        CANONICAL

        +
        static final java.lang.String CANONICAL
        +
        Property name for the canonical URL.
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        LAST_MODIFIED

        +
        static final java.lang.String LAST_MODIFIED
        +
        Property name for the page's last-modified date.
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        CHANGE_FREQUENCY

        +
        static final java.lang.String CHANGE_FREQUENCY
        +
        Property name for the page's change-frequency value.
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        PRIORITY

        +
        static final java.lang.String PRIORITY
        +
        Property name for the page's priority value.
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        NO_VALUE

        +
        static final java.lang.String NO_VALUE
        +
        Sentinel for no-value strings.
        +
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Element Detail

      + + + +
        +
      • +

        value

        +
        @Nonnull
        +java.lang.String value
        +
        Simple name / tag for the page. This can be used to generate URLs or route requests.
        +
        +
        Returns:
        +
        Module name for this script module.
        +
        +
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +
        +
      • +

        enableIndexing

        +
        boolean enableIndexing
        +
        Whether to allow robots on the selected page.
        +
        +
        Returns:
        +
        Defaults to true.
        +
        +
        +
        Default:
        +
        true
        +
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        robotsSettings

        +
        java.lang.String robotsSettings
        +
        Generic settings for indexing robots.
        +
        +
        Returns:
        +
        Indexing settings, if present, or null.
        +
        +
        +
        Default:
        +
        "NO_VALUE"
        +
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        googlebotSettings

        +
        java.lang.String googlebotSettings
        +
        Settings for Googlebot on the selected page.
        +
        +
        Returns:
        +
        Googlebot-specific settings, if present, or null.
        +
        +
        +
        Default:
        +
        "NO_VALUE"
        +
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        sitemap

        +
        boolean sitemap
        +
        Whether to list this page in the site map.
        +
        +
        Returns:
        +
        Defaults to true.
        +
        +
        +
        Default:
        +
        true
        +
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        canonicalUrl

        +
        java.lang.String canonicalUrl
        +
        Specifies the canonical URL to use in sitemaps and robots.txt listings, etc, for this page, if applicable.
        +
        +
        Returns:
        +
        The canonical URL, or null.
        +
        +
        +
        Default:
        +
        "NO_VALUE"
        +
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        lastModified

        +
        java.lang.String lastModified
        +
        Retrieve the last-modified string for this page. For now, this value is static.
        +
        +
        Returns:
        +
        Last-modified value, or null.
        +
        +
        +
        Default:
        +
        "NO_VALUE"
        +
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        changeFrequency

        +
        tools.elide.backend.builtin.Sitemap.ChangeFrequency changeFrequency
        +
        Retrieve the change frequency set for this page, if any.
        +
        +
        Returns:
        +
        Change frequency value, or null.
        +
        +
        +
        Default:
        +
        tools.elide.backend.builtin.Sitemap.ChangeFrequency.UNSPECIFIED_CHANGE_FREQUENCY
        +
        +
      • +
      +
    • +
    +
    +
    +
      +
    • + + +
        +
      • +

        priority

        +
        java.lang.String priority
        +
        Priority value for the page.
        +
        +
        Returns:
        +
        Priority value, or null.
        +
        +
        +
        Default:
        +
        "NO_VALUE"
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/annotations/Style.MediaType.html b/docs/java/gust/backend/annotations/Style.MediaType.html new file mode 100644 index 000000000..1911772db --- /dev/null +++ b/docs/java/gust/backend/annotations/Style.MediaType.html @@ -0,0 +1,392 @@ + + + + + +Style.MediaType + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Enum Style.MediaType

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Enum<Style.MediaType>
    • +
    • +
        +
      • gust.backend.annotations.Style.MediaType
      • +
      +
    • +
    +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      PRINT +
      For print display.
      +
      SCREEN +
      For screen display.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static Style.MediaTypevalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static Style.MediaType[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static Style.MediaType[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (Style.MediaType c : Style.MediaType.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static Style.MediaType valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/annotations/Style.html b/docs/java/gust/backend/annotations/Style.html new file mode 100644 index 000000000..6dc26c0e2 --- /dev/null +++ b/docs/java/gust/backend/annotations/Style.html @@ -0,0 +1,307 @@ + + + + + +Style + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Annotation Type Style

+
+
+
+
    +
  • +
    +
    @Documented
    +@Introduction
    +@Target(METHOD)
    +@Retention(RUNTIME)
    +public @interface Style
    +
    Links a style module to a given controller endpoint, such that it will automatically be included and loaded in the + head of the page, applying any applicable rewrite settings (if enabled).
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Required Element Summary

      + + + + + + + + + + + + +
      Required Elements 
      Modifier and TypeRequired ElementDescription
      java.lang.Stringvalue +
      Module name for the style module.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Optional Element Summary

      + + + + + + + + + + + + +
      Optional Elements 
      Modifier and TypeOptional ElementDescription
      Style.MediaTypemedia +
      Media type to apply to the injected spreadsheet, if applicable.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Element Detail

      + + + +
        +
      • +

        value

        +
        @Nonnull
        +java.lang.String value
        +
        Module name for the style module. This is generally the GSS/style module path (for example, `app.styles`).
        +
        +
        Returns:
        +
        Module name for this style module.
        +
        +
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +
        +
      • +

        media

        +
        @Nullable
        +Style.MediaType media
        +
        Media type to apply to the injected spreadsheet, if applicable.
        +
        +
        Returns:
        +
        Defaults to SCREEN.
        +
        +
        +
        Default:
        +
        gust.backend.annotations.Style.MediaType.SCREEN
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/annotations/class-use/Js.html b/docs/java/gust/backend/annotations/class-use/Js.html new file mode 100644 index 000000000..664ff099a --- /dev/null +++ b/docs/java/gust/backend/annotations/class-use/Js.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.annotations.Js + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.annotations.Js

+
+
No usage of gust.backend.annotations.Js
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/annotations/class-use/Page.html b/docs/java/gust/backend/annotations/class-use/Page.html new file mode 100644 index 000000000..fe076a947 --- /dev/null +++ b/docs/java/gust/backend/annotations/class-use/Page.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.annotations.Page + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.annotations.Page

+
+
No usage of gust.backend.annotations.Page
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/annotations/class-use/Style.MediaType.html b/docs/java/gust/backend/annotations/class-use/Style.MediaType.html new file mode 100644 index 000000000..d82a4e659 --- /dev/null +++ b/docs/java/gust/backend/annotations/class-use/Style.MediaType.html @@ -0,0 +1,211 @@ + + + + + +Uses of Class gust.backend.annotations.Style.MediaType + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.annotations.Style.MediaType

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/annotations/class-use/Style.html b/docs/java/gust/backend/annotations/class-use/Style.html new file mode 100644 index 000000000..48f55e51b --- /dev/null +++ b/docs/java/gust/backend/annotations/class-use/Style.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.annotations.Style + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.annotations.Style

+
+
No usage of gust.backend.annotations.Style
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/annotations/package-summary.html b/docs/java/gust/backend/annotations/package-summary.html new file mode 100644 index 000000000..7a808ab87 --- /dev/null +++ b/docs/java/gust/backend/annotations/package-summary.html @@ -0,0 +1,206 @@ + + + + + +gust.backend.annotations + + + + + + + + + + + + + + +
+ +
+
+
+

Package gust.backend.annotations

+
+
+
+ + +
Provides annotations for backend Java applications.
+
+
    +
  • + + + + + + + + + + + + +
    Enum Summary 
    EnumDescription
    Style.MediaType +
    Applicable media types.
    +
    +
  • +
  • + + + + + + + + + + + + + + + + + + + + +
    Annotation Types Summary 
    Annotation TypeDescription
    Js +
    Links a script module to a given controller endpoint, such that it will automatically be included and loaded in the + head of the page with the specified settings.
    +
    Page +
    Specifies settings for a given page method, that may later be applied via outputs like the application's site-map, or + other metadata assets.
    +
    Style +
    Links a style module to a given controller endpoint, such that it will automatically be included and loaded in the + head of the page, applying any applicable rewrite settings (if enabled).
    +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/annotations/package-tree.html b/docs/java/gust/backend/annotations/package-tree.html new file mode 100644 index 000000000..f090d79cd --- /dev/null +++ b/docs/java/gust/backend/annotations/package-tree.html @@ -0,0 +1,175 @@ + + + + + +gust.backend.annotations Class Hierarchy + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package gust.backend.annotations

+Package Hierarchies: + +
+
+
+

Annotation Type Hierarchy

+
    +
  • gust.backend.annotations.Js (implements java.lang.annotation.Annotation)
  • +
  • gust.backend.annotations.Page (implements java.lang.annotation.Annotation)
  • +
  • gust.backend.annotations.Style (implements java.lang.annotation.Annotation)
  • +
+
+
+

Enum Hierarchy

+
    +
  • java.lang.Object +
      +
    • java.lang.Enum<E> (implements java.lang.Comparable<T>, java.io.Serializable) + +
    • +
    +
  • +
+
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/annotations/package-use.html b/docs/java/gust/backend/annotations/package-use.html new file mode 100644 index 000000000..c6f297537 --- /dev/null +++ b/docs/java/gust/backend/annotations/package-use.html @@ -0,0 +1,187 @@ + + + + + +Uses of Package gust.backend.annotations + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Package
gust.backend.annotations

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/AppController.html b/docs/java/gust/backend/class-use/AppController.html new file mode 100644 index 000000000..7697040b9 --- /dev/null +++ b/docs/java/gust/backend/class-use/AppController.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.AppController + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.AppController

+
+
No usage of gust.backend.AppController
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/Application.html b/docs/java/gust/backend/class-use/Application.html new file mode 100644 index 000000000..45b5896e2 --- /dev/null +++ b/docs/java/gust/backend/class-use/Application.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.Application + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.Application

+
+
No usage of gust.backend.Application
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/ApplicationBoot.html b/docs/java/gust/backend/class-use/ApplicationBoot.html new file mode 100644 index 000000000..2d1c2e685 --- /dev/null +++ b/docs/java/gust/backend/class-use/ApplicationBoot.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.ApplicationBoot + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.ApplicationBoot

+
+
No usage of gust.backend.ApplicationBoot
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/AssetConfiguration.AssetCachingConfiguration.html b/docs/java/gust/backend/class-use/AssetConfiguration.AssetCachingConfiguration.html new file mode 100644 index 000000000..5a169a184 --- /dev/null +++ b/docs/java/gust/backend/class-use/AssetConfiguration.AssetCachingConfiguration.html @@ -0,0 +1,213 @@ + + + + + +Uses of Interface gust.backend.AssetConfiguration.AssetCachingConfiguration + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.AssetConfiguration.AssetCachingConfiguration

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/AssetConfiguration.AssetCompressionConfiguration.html b/docs/java/gust/backend/class-use/AssetConfiguration.AssetCompressionConfiguration.html new file mode 100644 index 000000000..177dbcdd8 --- /dev/null +++ b/docs/java/gust/backend/class-use/AssetConfiguration.AssetCompressionConfiguration.html @@ -0,0 +1,213 @@ + + + + + +Uses of Interface gust.backend.AssetConfiguration.AssetCompressionConfiguration + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.AssetConfiguration.AssetCompressionConfiguration

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/AssetConfiguration.AssetVarianceConfiguration.html b/docs/java/gust/backend/class-use/AssetConfiguration.AssetVarianceConfiguration.html new file mode 100644 index 000000000..807d8ea23 --- /dev/null +++ b/docs/java/gust/backend/class-use/AssetConfiguration.AssetVarianceConfiguration.html @@ -0,0 +1,213 @@ + + + + + +Uses of Interface gust.backend.AssetConfiguration.AssetVarianceConfiguration + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.AssetConfiguration.AssetVarianceConfiguration

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/AssetConfiguration.ContentDistributionConfiguration.html b/docs/java/gust/backend/class-use/AssetConfiguration.ContentDistributionConfiguration.html new file mode 100644 index 000000000..6ba207808 --- /dev/null +++ b/docs/java/gust/backend/class-use/AssetConfiguration.ContentDistributionConfiguration.html @@ -0,0 +1,213 @@ + + + + + +Uses of Interface gust.backend.AssetConfiguration.ContentDistributionConfiguration + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.AssetConfiguration.ContentDistributionConfiguration

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/AssetConfiguration.CrossOriginResourceConfiguration.html b/docs/java/gust/backend/class-use/AssetConfiguration.CrossOriginResourceConfiguration.html new file mode 100644 index 000000000..e73887146 --- /dev/null +++ b/docs/java/gust/backend/class-use/AssetConfiguration.CrossOriginResourceConfiguration.html @@ -0,0 +1,213 @@ + + + + + +Uses of Interface gust.backend.AssetConfiguration.CrossOriginResourceConfiguration + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.AssetConfiguration.CrossOriginResourceConfiguration

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/AssetConfiguration.html b/docs/java/gust/backend/class-use/AssetConfiguration.html new file mode 100644 index 000000000..4c56fa75e --- /dev/null +++ b/docs/java/gust/backend/class-use/AssetConfiguration.html @@ -0,0 +1,215 @@ + + + + + +Uses of Interface gust.backend.AssetConfiguration + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.AssetConfiguration

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/AssetController.AssetsAwareCspFilter.html b/docs/java/gust/backend/class-use/AssetController.AssetsAwareCspFilter.html new file mode 100644 index 000000000..a222b33a2 --- /dev/null +++ b/docs/java/gust/backend/class-use/AssetController.AssetsAwareCspFilter.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.AssetController.AssetsAwareCspFilter + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.AssetController.AssetsAwareCspFilter

+
+
No usage of gust.backend.AssetController.AssetsAwareCspFilter
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/AssetController.html b/docs/java/gust/backend/class-use/AssetController.html new file mode 100644 index 000000000..5ea12bcdf --- /dev/null +++ b/docs/java/gust/backend/class-use/AssetController.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.AssetController + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.AssetController

+
+
No usage of gust.backend.AssetController
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/BaseController.html b/docs/java/gust/backend/class-use/BaseController.html new file mode 100644 index 000000000..9efc39cb2 --- /dev/null +++ b/docs/java/gust/backend/class-use/BaseController.html @@ -0,0 +1,197 @@ + + + + + +Uses of Class gust.backend.BaseController + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.BaseController

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use BaseController 
    PackageDescription
    gust.backend +
    Defines backend logic for Gust-based applications.
    +
    +
  • +
  • + +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/DynamicServingConfiguration.ClientHintsConfiguration.html b/docs/java/gust/backend/class-use/DynamicServingConfiguration.ClientHintsConfiguration.html new file mode 100644 index 000000000..9ee02cae3 --- /dev/null +++ b/docs/java/gust/backend/class-use/DynamicServingConfiguration.ClientHintsConfiguration.html @@ -0,0 +1,213 @@ + + + + + +Uses of Interface gust.backend.DynamicServingConfiguration.ClientHintsConfiguration + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.DynamicServingConfiguration.ClientHintsConfiguration

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/DynamicServingConfiguration.DynamicETagsConfiguration.html b/docs/java/gust/backend/class-use/DynamicServingConfiguration.DynamicETagsConfiguration.html new file mode 100644 index 000000000..d22ec2eab --- /dev/null +++ b/docs/java/gust/backend/class-use/DynamicServingConfiguration.DynamicETagsConfiguration.html @@ -0,0 +1,213 @@ + + + + + +Uses of Interface gust.backend.DynamicServingConfiguration.DynamicETagsConfiguration + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.DynamicServingConfiguration.DynamicETagsConfiguration

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/DynamicServingConfiguration.DynamicVarianceConfiguration.html b/docs/java/gust/backend/class-use/DynamicServingConfiguration.DynamicVarianceConfiguration.html new file mode 100644 index 000000000..730b8fc56 --- /dev/null +++ b/docs/java/gust/backend/class-use/DynamicServingConfiguration.DynamicVarianceConfiguration.html @@ -0,0 +1,213 @@ + + + + + +Uses of Interface gust.backend.DynamicServingConfiguration.DynamicVarianceConfiguration + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.DynamicServingConfiguration.DynamicVarianceConfiguration

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/DynamicServingConfiguration.FeaturePolicyConfiguration.html b/docs/java/gust/backend/class-use/DynamicServingConfiguration.FeaturePolicyConfiguration.html new file mode 100644 index 000000000..ee8e64fad --- /dev/null +++ b/docs/java/gust/backend/class-use/DynamicServingConfiguration.FeaturePolicyConfiguration.html @@ -0,0 +1,213 @@ + + + + + +Uses of Interface gust.backend.DynamicServingConfiguration.FeaturePolicyConfiguration + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.DynamicServingConfiguration.FeaturePolicyConfiguration

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/DynamicServingConfiguration.XSSProtectionConfiguration.html b/docs/java/gust/backend/class-use/DynamicServingConfiguration.XSSProtectionConfiguration.html new file mode 100644 index 000000000..df6482aa3 --- /dev/null +++ b/docs/java/gust/backend/class-use/DynamicServingConfiguration.XSSProtectionConfiguration.html @@ -0,0 +1,213 @@ + + + + + +Uses of Interface gust.backend.DynamicServingConfiguration.XSSProtectionConfiguration + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.DynamicServingConfiguration.XSSProtectionConfiguration

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/DynamicServingConfiguration.html b/docs/java/gust/backend/class-use/DynamicServingConfiguration.html new file mode 100644 index 000000000..ee27483ed --- /dev/null +++ b/docs/java/gust/backend/class-use/DynamicServingConfiguration.html @@ -0,0 +1,214 @@ + + + + + +Uses of Interface gust.backend.DynamicServingConfiguration + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.DynamicServingConfiguration

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/PageContext.html b/docs/java/gust/backend/class-use/PageContext.html new file mode 100644 index 000000000..29024738a --- /dev/null +++ b/docs/java/gust/backend/class-use/PageContext.html @@ -0,0 +1,291 @@ + + + + + +Uses of Class gust.backend.PageContext + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.PageContext

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use PageContext 
    PackageDescription
    gust.backend +
    Defines backend logic for Gust-based applications.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of PageContext in gust.backend

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend that return PageContext 
      Modifier and TypeMethodDescription
      static PageContextPageContext.empty() +
      Factory to create an empty page context.
      +
      static PageContextPageContext.fromMap​(java.util.Map<java.lang.String,​java.lang.Object> context) +
      Factory to create a page context object from a regular Java map, of string context properties to values of any + object type.
      +
      static PageContextPageContext.fromMap​(java.util.Map<java.lang.String,​java.lang.Object> context, + java.util.Map<java.lang.String,​java.lang.Object> injected) +
      Factory to create a page context object from a regular Java map, of string context properties to values of any + object type.
      +
      static PageContextPageContext.fromMap​(java.util.Map<java.lang.String,​java.lang.Object> context, + java.util.Map<java.lang.String,​java.lang.Object> injected, + io.micronaut.views.soy.SoyNamingMapProvider namingMapProvider) +
      Factory to create a page context object from a regular Java map, of string context properties to values of any + object type.
      +
      static PageContextPageContext.fromMap​(java.util.Map<java.lang.String,​java.lang.Object> context, + java.util.Map<java.lang.String,​java.lang.Object> injected, + io.micronaut.views.soy.SoyNamingMapProvider namingMapProvider, + io.micronaut.views.soy.SoyContext.SoyI18NContext i18n, + java.util.function.Predicate<java.lang.String> delegatePredicate) +
      Factory to create a page context object from a regular Java map, of string context properties to values of any + object type.
      +
      static PageContextPageContext.fromProto​(tools.elide.page.Context pageContext) +
      Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`.
      +
      static PageContextPageContext.fromProto​(tools.elide.page.Context pageContext, + java.util.Map<java.lang.String,​java.lang.Object> props) +
      Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`.
      +
      static PageContextPageContext.fromProto​(tools.elide.page.Context pageContext, + java.util.Map<java.lang.String,​java.lang.Object> props, + java.util.Map<java.lang.String,​java.lang.Object> injected) +
      Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`.
      +
      static PageContextPageContext.fromProto​(tools.elide.page.Context pageContext, + java.util.Map<java.lang.String,​java.lang.Object> props, + java.util.Map<java.lang.String,​java.lang.Object> injected, + io.micronaut.views.soy.SoyNamingMapProvider namingMapProvider) +
      Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`.
      +
      static PageContextPageContext.fromProto​(tools.elide.page.Context pageContext, + java.util.Map<java.lang.String,​java.lang.Object> props, + java.util.Map<java.lang.String,​java.lang.Object> injected, + io.micronaut.views.soy.SoyNamingMapProvider namingMapProvider, + io.micronaut.views.soy.SoyContext.SoyI18NContext i18n, + java.util.function.Predicate<java.lang.String> delegatePredicate) +
      Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`.
      +
      PageContextPageContextManager.render() 
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/PageContextManager.html b/docs/java/gust/backend/class-use/PageContextManager.html new file mode 100644 index 000000000..de8402d9e --- /dev/null +++ b/docs/java/gust/backend/class-use/PageContextManager.html @@ -0,0 +1,643 @@ + + + + + +Uses of Class gust.backend.PageContextManager + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.PageContextManager

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use PageContextManager 
    PackageDescription
    gust.backend +
    Defines backend logic for Gust-based applications.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of PageContextManager in gust.backend

      + + + + + + + + + + + + + + +
      Fields in gust.backend declared as PageContextManager 
      Modifier and TypeFieldDescription
      protected PageContextManagerBaseController.context +
      Holds request-bound page context as it is built.
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend that return PageContextManager 
      Modifier and TypeMethodDescription
      PageContextManagerPageContextManager.addFeaturePolicy​(java.lang.String... policies) +
      Add a `Feature-Policy` entry for the current response render flow.
      +
      PageContextManagerPageContextManager.addKeyword​(java.lang.String... keywords) +
      Add the provided page keywords for the current render flow.
      +
      PageContextManagerPageContextManager.addLink​(java.lang.String relevance, + java.net.URI href, + java.util.Optional<java.lang.String> type) +
      Add a regular HTML metadata link to the current render flow, specified by the provided method parameters.
      +
      PageContextManagerPageContextManager.addLink​(tools.elide.page.Context.PageLink.Builder link) +
      Add a regular HTML metadata link to the current render flow, specified by a Context.PageLink proto record.
      +
      PageContextManagerPageContextManager.applyOpenGraph​(tools.elide.page.Context.Metadata.OpenGraph.Builder content) +
      Apply the provided OpenGraph metadata configuration to the current OpenGraph metadata configuration, if any.
      +
      PageContextManagerPageContextManager.cdnPrefix​(java.util.Optional<java.lang.String> prefix) +
      Set the specified prefix as the Content Distribution Network hostname prefix to use when rendering asset + links for this HTTP cycle.
      +
      PageContextManagerPageContextManager.clearDelegatePackage() +
      Clear any current delegate package.
      +
      PageContextManagerPageContextManager.clearFeaturePolicy() +
      Clear the current set of `Feature-Policy` entries.
      +
      PageContextManagerPageContextManager.clearGooglebot() +
      Clear the current value, if any, set for
      +
      PageContextManagerPageContextManager.clearKeywords() +
      Clear the current set of page keywords for the current render flow.
      +
      PageContextManagerPageContextManager.clearLinks() +
      Clear the set of HTML metadata links assigned to the current render flow.
      +
      PageContextManagerPageContextManager.clearOpenGraph() +
      Clear any OpenGraph metadata configuration attached to the current render flow.
      +
      PageContextManagerPageContextManager.clearRobots() +
      Clear the current value, if any, set for
      +
      PageContextManagerPageContextManager.contentType​(java.lang.String type) +
      Sets the content type to return for the current render response flow.
      +
      PageContextManagerPageContextManager.delegatePackage​(java.lang.String packageName) +
      Set the active delegate package name, wrapped in an implied predicate which filters explicitly against the provided + name value.
      +
      PageContextManagerPageContextManager.delegatePackage​(java.util.function.Predicate<java.lang.String> packagePredicate) +
      Set the active delegate package predicate directly.
      +
      PageContextManagerPageContextManager.description​(java.lang.String description) +
      Set the page description for the current render flow.
      +
      PageContextManagerPageContextManager.dnsPrefetch​(java.lang.Iterable<java.lang.String> hosts) +
      Inject the specified list of hosts as DNS records to prefetch from the browser.
      +
      PageContextManagerPageContextManager.dnsPrefetch​(java.lang.String... hosts) +
      Inject the specified DNS hostname(s) as records to prefetch from the browser.
      +
      PageContextManagerPageContextManager.enableETags​(java.lang.Boolean enableETags) +
      Enable a dynamic ETag header value, which is computed from the rendered content produced by this page + context record.
      +
      PageContextManagerPageContextManager.header​(java.lang.String name, + java.lang.String value) +
      Affix an arbitrary HTTP header to the response eventually produced by this page context, assuming no errors occur.
      +
      PageContextManagerPageContextManager.header​(java.lang.String name, + java.lang.String value, + java.lang.Boolean force) +
      Affix an arbitrary HTTP header to the response eventually produced by this page context, assuming no errors occur.
      +
      PageContextManagerPageContextManager.inject​(java.lang.String key, + java.lang.Object value) +
      Install an injected context value, at the named key provided.
      +
      PageContextManagerPageContextManager.language​(java.util.Optional<java.lang.String> language) +
      Set the value to send back in this response's Content-Language header.
      +
      PageContextManagerPageContextManager.messageBundle​(java.util.Optional<com.google.template.soy.msgs.SoyMsgBundle> soyMsgBundle) +
      Mount a pre-fabricated Soy message bundle for translation use during render.
      +
      PageContextManagerPageContextManager.messagesFile​(java.util.Optional<java.io.File> xliffFile) +
      Mount a loaded XLIFF file for use during render.
      +
      PageContextManagerPageContextManager.messagesResource​(java.util.Optional<java.net.URL> xliffData) +
      Load and mount a referenced XLIFF resource for use during render.
      +
      PageContextManagerPageContextManager.preconnect​(java.lang.Iterable<java.lang.String> hosts) +
      Inject the specified list of hosts as pre-connect hints for the browser.
      +
      PageContextManagerPageContextManager.preconnect​(java.lang.String... hosts) +
      Inject the specified list of hosts as pre-connect hints for the browser.
      +
      PageContextManagerPageContextManager.put​(java.lang.String key, + java.lang.Object value) +
      Install a regular context value, at the named key provided.
      +
      PageContextManagerPageContextManager.rewrite​(java.util.Optional<io.micronaut.views.soy.SoyNamingMapProvider> namingMapProvider) +
      Install, or uninstall, the request-scoped renaming map provider.
      +
      PageContextManagerPageContextManager.script​(java.lang.String name) +
      Include the specified JavaScript resource in the rendered page, according to the specified settings.
      +
      PageContextManagerPageContextManager.script​(java.lang.String name, + java.lang.Boolean defer, + java.lang.Boolean async) +
      Include the specified JavaScript resource in the rendered page, according to the specified settings.
      +
      PageContextManagerPageContextManager.script​(java.lang.String name, + java.lang.String id, + java.lang.Boolean defer, + java.lang.Boolean async, + java.lang.Boolean module, + java.lang.Boolean nomodule, + java.lang.Boolean preload, + java.lang.Boolean push) +
      Include the specified JavaScript resource in the rendered page, according to the specified settings.
      +
      PageContextManagerPageContextManager.script​(tools.elide.page.Context.Scripts.JavaScript.Builder script) +
      Include the specified JavaScript resource in the rendered page, according to enclosed settings (i.e.
      +
      PageContextManagerPageContextManager.setFeaturePolicy​(java.util.Collection<java.lang.String> policies) +
      Overwrite the set of `Feature-Policy` entries for the current render flow.
      +
      PageContextManagerPageContextManager.setGooglebot​(java.lang.String value) +
      Overwrite the value specified for the "googlebot" metadata key in the current render flow.
      +
      PageContextManagerPageContextManager.setKeywords​(java.util.Collection<java.lang.String> keywords) +
      Overwrite the current set of page keywords for the current render flow.
      +
      PageContextManagerPageContextManager.setLinks​(java.util.Collection<tools.elide.page.Context.PageLink.Builder> links) +
      Overwrite the set of page metadata links for the current render flow.
      +
      PageContextManagerPageContextManager.setOpenGraph​(tools.elide.page.Context.Metadata.OpenGraph.Builder content) +
      Overwrite the OpenGraph metadata configuration for the current render flow, with the provided OpenGraph metadata + configuration.
      +
      PageContextManagerPageContextManager.setPrefixes​(java.util.Optional<java.util.List<tools.elide.page.Context.RDFPrefix>> prefixes) +
      Overwrite the full set of RDFa prefixes for the current render flow.
      +
      PageContextManagerPageContextManager.setRobots​(java.lang.String value) +
      Overwrite the value specified for the "robots" metadata key in the current render flow.
      +
      PageContextManagerPageContextManager.stylesheet​(java.lang.String name) +
      Include the specified CSS stylesheet in the rendered page with default settings.
      +
      PageContextManagerPageContextManager.stylesheet​(java.lang.String name, + java.lang.String media) +
      Include the specified CSS stylesheet in the rendered page, along with the specified media setting.
      +
      PageContextManagerPageContextManager.stylesheet​(java.lang.String name, + java.lang.String id, + java.lang.String media, + java.lang.Boolean preload, + java.lang.Boolean push) +
      Include the specified CSS stylesheet in the rendered page, according to the specified settings.
      +
      PageContextManagerPageContextManager.stylesheet​(tools.elide.page.Context.Styles.Stylesheet.Builder stylesheet) +
      Include the specified CSS stylesheet resource in the rendered page, according to the enclosed settings (i.e.
      +
      PageContextManagerPageContextManager.supportedClientHints​(java.util.Optional<java.lang.Iterable<tools.elide.page.Context.ClientHint>> hints, + java.util.Optional<java.lang.Long> ttl) +
      Enable the provided set of server-indicated client hint types.
      +
      PageContextManagerPageContextManager.supportedClientHints​(tools.elide.page.Context.ClientHint... hints) +
      Enable the provided set of server-indicated client hint types.
      +
      PageContextManagerPageContextManager.title​(java.lang.String title) +
      Set the page title for the current render flow.
      +
      PageContextManagerPageContextManager.vary​(java.lang.Iterable<java.lang.String> variance) +
      Append an HTTP request header considered as part of the Vary header in the response.
      +
      PageContextManagerPageContextManager.vary​(java.lang.String variance) +
      Append an HTTP request header considered as part of the Vary header in the response.
      +
      PageContextManagerPageContextManager.vary​(java.lang.String... variance) +
      Append an HTTP request header considered as part of the Vary header in the response.
      +
      + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend with parameters of type PageContextManager 
      Modifier and TypeMethodDescription
      protected voidAppController.selectCdnPrefixes​(PageContextManager render, + io.micronaut.http.MutableHttpResponse response, + AssetConfiguration servingConfig) +
      Select the set of CDN prefixes to use in this HTTP cycle, from the configured set.
      +
      protected io.micronaut.http.MutableHttpResponse<PageRender>AppController.serve​(PageContextManager render) +
      Serve the provided rendered-page response, while applying any app configuration related to dynamic page headers.
      +
      + + + + + + + + + + + + + + + + +
      Constructors in gust.backend with parameters of type PageContextManager 
      ConstructorDescription
      AppController​(PageContextManager context) +
      Initialize a new application controller.
      +
      BaseController​(PageContextManager context) +
      Initialize a base Gust controller from scratch.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/PageRender.html b/docs/java/gust/backend/class-use/PageRender.html new file mode 100644 index 000000000..972ba1465 --- /dev/null +++ b/docs/java/gust/backend/class-use/PageRender.html @@ -0,0 +1,222 @@ + + + + + +Uses of Interface gust.backend.PageRender + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.PageRender

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use PageRender 
    PackageDescription
    gust.backend +
    Defines backend logic for Gust-based applications.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of PageRender in gust.backend

      + + + + + + + + + + + + + + + + + + + +
      Classes in gust.backend that implement PageRender 
      Modifier and TypeClassDescription
      class PageContext +
      Supplies page context to a Micronaut/Soy render routine, based on the context proto provided/filled out by a given + server-side controller.
      +
      class PageContextManager +
      Manages the process of filling out PageContext objects before they are sealed, and delivered to Closure/Soy + to be reduced and rendered into content.
      +
      + + + + + + + + + + + + + + +
      Methods in gust.backend that return types with arguments of type PageRender 
      Modifier and TypeMethodDescription
      protected io.micronaut.http.MutableHttpResponse<PageRender>AppController.serve​(PageContextManager render) +
      Serve the provided rendered-page response, while applying any app configuration related to dynamic page headers.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/class-use/TemplateProvider.html b/docs/java/gust/backend/class-use/TemplateProvider.html new file mode 100644 index 000000000..eb579193b --- /dev/null +++ b/docs/java/gust/backend/class-use/TemplateProvider.html @@ -0,0 +1,204 @@ + + + + + +Uses of Class gust.backend.TemplateProvider + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.TemplateProvider

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use TemplateProvider 
    PackageDescription
    gust.backend +
    Defines backend logic for Gust-based applications.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of TemplateProvider in gust.backend

      + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend that return TemplateProvider 
      Modifier and TypeMethodDescription
      TemplateProviderTemplateProvider.installRenamingMaps​(com.google.template.soy.shared.SoyCssRenamingMap cssMap, + com.google.template.soy.shared.SoyIdRenamingMap idMap) +
      Install a Soy-compatible style renaming map for CSS classes, and optionally one for CSS IDs as well.
      +
      TemplateProviderTemplateProvider.installTemplates​(com.google.template.soy.jbcsrc.api.SoySauce compiled) +
      Install a set of compiled templates manually into the local template provider context.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/firestore/FirestoreAdapter.html b/docs/java/gust/backend/driver/firestore/FirestoreAdapter.html new file mode 100644 index 000000000..20b03f501 --- /dev/null +++ b/docs/java/gust/backend/driver/firestore/FirestoreAdapter.html @@ -0,0 +1,665 @@ + + + + + +FirestoreAdapter + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class FirestoreAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.firestore.FirestoreAdapter<Key,​Model>
    • +
    +
  • +
+
+
    +
  • +
    +
    Type Parameters:
    +
    Model - Model type which this adapter adapts to Firestore.
    +
    +
    +
    All Implemented Interfaces:
    +
    DatabaseAdapter<Key,​Model,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>, ModelAdapter<Key,​Model,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>, PersistenceDriver<Key,​Model,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>
    +
    +
    +
    @Immutable
    +@ThreadSafe
    +public final class FirestoreAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message>
    +extends java.lang.Object
    +implements DatabaseAdapter<Key,​Model,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>
    +
    Defines a built-in database adapter for interacting with Google Cloud Firestore, using business-data models defined + through Protobuf annotated with framework-provided metadata. + +

    This adapter makes use of a specialized DatabaseDriver (FirestoreDriver), and supports a custom + configuration class (FirestoreTransportConfig) which is loaded from application config during service channel + initialization. Connections are pooled and cached against a caching executor by the active transport manager.

    + +

    Instantiation is disallowed to facilitate restriction of the active ModelCodec to Firestore's own model + codec, which uses ObjectModelCodec to produce generic collapsed messages during + serialization, and to translate from proto-maps during deserialization. Optionally, a compliant instance of + CacheDriver may be provided at construction time, which enables caching against that driver for calls that + are eligible (according, again, to settings from FirestoreTransportConfig).

    +
    +
    See Also:
    +
    Driver for speaking to Firestore., +Transport configuration for Firestore.
    +
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        forModel

        +
        @Nonnull
        +public static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message> FirestoreAdapter<K,​M> forModel​(@Nonnull
        +                                                                                                                                       com.google.protobuf.Message.Builder builder,
        +                                                                                                                                       @Nonnull
        +                                                                                                                                       FirestoreDriver<K,​M> driver)
        +
        Create or otherwise resolve a FirestoreAdapter for the provided model type and builder. This additionally + resolves a model codec, driver, and optionally a caching engine as well (although one may be provided explicitly at + the invoking developer's discretion - see forModel(Message.Builder, FirestoreDriver, Optional)). + +

        The resulting adapter may not be created fresh for the task at hand, but it is threadsafe and shares no direct + state with any other operation.

        +
        +
        Type Parameters:
        +
        M - Model type for which we are requesting a Firestore adapter instance.
        +
        Parameters:
        +
        builder - Model builder instance, which the engine will clone for each retrieve operation.
        +
        driver - Driver which we should use when handling instances of M.
        +
        Returns:
        +
        Pre-fabricated (or otherwise resolved) Firestore adapter for the requested model.
        +
        See Also:
        +
        To provide an explicit cache driver for this type.
        +
        +
      • +
      + + + +
        +
      • +

        forModel

        +
        @Nonnull
        +public static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message> FirestoreAdapter<K,​M> forModel​(@Nonnull
        +                                                                                                                                       com.google.protobuf.Message.Builder builder,
        +                                                                                                                                       @Nonnull
        +                                                                                                                                       FirestoreDriver<K,​M> driver,
        +                                                                                                                                       @Nonnull
        +                                                                                                                                       java.util.Optional<CacheDriver<K,​M>> cacheDriver)
        +
        Create or otherwise resolve a FirestoreAdapter for the provided model type and builder. This additionally + resolves a model codec, driver, and optionally a caching engine as well. + +

        The resulting adapter may not be created fresh for the task at hand, but it is threadsafe and shares no direct + state with any other operation.

        +
        +
        Type Parameters:
        +
        M - Model type for which we are requesting a Firestore adapter instance.
        +
        Parameters:
        +
        driver - Driver which we should use when handling instances of M.
        +
        builder - Model builder instance, which the engine will clone for each retrieve operation.
        +
        Returns:
        +
        Pre-fabricated (or otherwise resolved) Firestore adapter for the requested model.
        +
        +
      • +
      + + + + + +
        +
      • +

        acquire

        +
        @Nonnull
        +public static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message> FirestoreAdapter<K,​M> acquire​(@Nonnull
        +                                                                                                                                      K keyInstance,
        +                                                                                                                                      @Nonnull
        +                                                                                                                                      M messageInstance,
        +                                                                                                                                      @Nonnull
        +                                                                                                                                      com.google.common.util.concurrent.ListeningScheduledExecutorService executorService)
        +
        Acquire an instance of the FirestoreAdapter and FirestoreDriver, customized for the provided + `modelInstance` and `keyInstance`. This method variant makes use of a default object for the gRPC transport + provider and Google credential provider.
        +
        +
        Type Parameters:
        +
        K - Key type.
        +
        M - Message type.
        +
        Parameters:
        +
        keyInstance - Key type instance for the record in question.
        +
        messageInstance - Message type instance for the record in question.
        +
        executorService - Background executor service for Firestore operations.
        +
        Returns:
        +
        Instance of the FirestoreAdapter and FirestoreDriver, customized as described.
        +
        +
      • +
      + + + + + +
        +
      • +

        acquire

        +
        @Nonnull
        +public static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message> FirestoreAdapter<K,​M> acquire​(@Nonnull
        +                                                                                                                                      K keyInstance,
        +                                                                                                                                      @Nonnull
        +                                                                                                                                      M messageInstance,
        +                                                                                                                                      @Nonnull
        +                                                                                                                                      com.google.cloud.firestore.FirestoreOptions.Builder baseOptions,
        +                                                                                                                                      @Nonnull
        +                                                                                                                                      com.google.common.util.concurrent.ListeningScheduledExecutorService executorService)
        +
        Acquire an instance of the FirestoreAdapter and FirestoreDriver, customized for the provided + `modelInstance` and `keyInstance`. This method variant makes use of a default object for the gRPC transport + provider and Google credential provider, but allows specifying custom FirestoreOptions.
        +
        +
        Type Parameters:
        +
        K - Key type.
        +
        M - Message type.
        +
        Parameters:
        +
        baseOptions - Base options to apply to the Firestore driver.
        +
        keyInstance - Key type instance for the record in question.
        +
        messageInstance - Message type instance for the record in question.
        +
        executorService - Background executor service for Firestore operations.
        +
        Returns:
        +
        Instance of the FirestoreAdapter and FirestoreDriver, customized as described.
        +
        +
      • +
      + + + + + +
        +
      • +

        acquire

        +
        @Nonnull
        +public static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message> FirestoreAdapter<K,​M> acquire​(@Nonnull
        +                                                                                                                                      com.google.cloud.firestore.FirestoreOptions.Builder baseOptions,
        +                                                                                                                                      @Nonnull @GoogleAPIChannel(service=FIRESTORE)
        +                                                                                                                                      com.google.api.gax.rpc.TransportChannelProvider firestoreChannel,
        +                                                                                                                                      @Nonnull
        +                                                                                                                                      com.google.api.gax.core.CredentialsProvider credentialsProvider,
        +                                                                                                                                      @Nonnull
        +                                                                                                                                      com.google.cloud.grpc.GrpcTransportOptions transportOptions,
        +                                                                                                                                      @Nonnull
        +                                                                                                                                      com.google.common.util.concurrent.ListeningScheduledExecutorService executorService,
        +                                                                                                                                      @Nonnull
        +                                                                                                                                      K keyInstance,
        +                                                                                                                                      @Nonnull
        +                                                                                                                                      M messageInstance)
        +
        Acquire an instance of the FirestoreAdapter and FirestoreDriver, customized for the provided + `modelInstance` and `keyInstance`. This method variant allows specification of the full set of objects which + govern the connection and interaction with Firestore.
        +
        +
        Type Parameters:
        +
        K - Key type.
        +
        M - Message type.
        +
        Parameters:
        +
        baseOptions - Base options to apply to the Firestore driver.
        +
        firestoreChannel - Transport provider for Firestore communication channels via gRPC.
        +
        credentialsProvider - Provider for transport/call credentials, when interacting with Firestore.
        +
        transportOptions - gRPC transport options, to apply when instantiating channels for Firestore communications.
        +
        executorService - Background executor service for Firestore operations.
        +
        keyInstance - Key type instance for the record in question.
        +
        messageInstance - Message type instance for the record in question.
        +
        Returns:
        +
        Instance of the FirestoreAdapter and FirestoreDriver, customized as described.
        +
        +
      • +
      + + + +
        +
      • +

        codec

        +
        @Nonnull
        +public ModelCodec<Model,​CollapsedMessage,​com.google.cloud.firestore.DocumentSnapshot> codec()
        +
        Acquire an instance of the codec used by this adapter. Codecs are either injected/otherwise provided during adapter + construction, or they are specified statically if the adapter depends on a specific codec.
        +
        +
        Specified by:
        +
        codec in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>
        +
        Returns:
        +
        Model codec currently in use by this adapter.
        +
        +
      • +
      + + + +
        +
      • +

        cache

        +
        @Nonnull
        +public java.util.Optional<CacheDriver<Key,​Model>> cache()
        +
        Return the cache driver in use for this particular model adapter. If a cache driver is present, and active/enabled + according to database driver settings, it will be used on read-paths (such as fetching objects by ID).
        +
        +
        Specified by:
        +
        cache in interface ModelAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>
        +
        Returns:
        +
        Cache driver currently in use by this model adapter.
        +
        +
      • +
      + + + +
        +
      • +

        engine

        +
        @Nonnull
        +public DatabaseDriver<Key,​Model,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessageengine()
        +
        Return the lower-level DatabaseDriver powering this adapter. The driver is responsible for communicating + with the actual database or storage service, either via local stubs/emulators or a production API.
        +
        +
        Specified by:
        +
        engine in interface DatabaseAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>
        +
        Specified by:
        +
        engine in interface ModelAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>
        +
        Returns:
        +
        Database driver instance currently in use by this model adapter.
        +
        +
      • +
      + + + +
        +
      • +

        executorService

        +
        @Nonnull
        +public com.google.common.util.concurrent.ListeningScheduledExecutorService executorService()
        +
        Resolve an executor service for use with this persistence driver. Operations will be executed against this as they + are received.
        +
        +
        Specified by:
        +
        executorService in interface ModelAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>
        +
        Specified by:
        +
        executorService in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>
        +
        Returns:
        +
        Scheduled executor service.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/firestore/FirestoreDriver.html b/docs/java/gust/backend/driver/firestore/FirestoreDriver.html new file mode 100644 index 000000000..247b2ea27 --- /dev/null +++ b/docs/java/gust/backend/driver/firestore/FirestoreDriver.html @@ -0,0 +1,562 @@ + + + + + +FirestoreDriver + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class FirestoreDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.firestore.FirestoreDriver<Key,​Model>
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    DatabaseDriver<Key,​Model,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>, PersistenceDriver<Key,​Model,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>
    +
    +
    +
    @Immutable
    +@ThreadSafe
    +public final class FirestoreDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message>
    +extends java.lang.Object
    +implements DatabaseDriver<Key,​Model,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>
    +
    Defines a built-in framework DatabaseDriver for interacting seamlessly with Google Cloud Firestore. This + enables Firestore-based persistence for any Message-derived (schema-driven) business model in a given Gust + app's ecosystem. + +

    Model storage can be deeply customized on a per-model basis, thanks to the built-in proto annotations available + in gust.core. The Firestore adapter supports basic persistence (i.e. as a regular +

    PersistenceDriver
    ), but also supports generic, object index-style queries.

    + +

    Caching may be facilitated by any compliant cache driver, via the main Firestore adapter.

    +
    +
    See Also:
    +
    main adapter interface for Firestore., +logic and connection manager for Firestore., +configuration class for Firestore access.
    +
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        executorService

        +
        @Nonnull
        +public com.google.common.util.concurrent.ListeningScheduledExecutorService executorService()
        +
        Resolve an executor service for use with this persistence driver. Operations will be executed against this as they + are received.
        +
        +
        Specified by:
        +
        executorService in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>
        +
        Returns:
        +
        Scheduled executor service.
        +
        +
      • +
      + + + +
        +
      • +

        codec

        +
        @Nonnull
        +public ModelCodec<Model,​CollapsedMessage,​com.google.cloud.firestore.DocumentSnapshot> codec()
        +
        Acquire an instance of the codec used by this adapter. Codecs are either injected/otherwise provided during adapter + construction, or they are specified statically if the adapter depends on a specific codec.
        +
        +
        Specified by:
        +
        codec in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>
        +
        Returns:
        +
        Model codec currently in use by this adapter.
        +
        +
      • +
      + + + +
        +
      • +

        generateKey

        +
        @Nonnull
        +public Key generateKey​(@Nonnull
        +                       com.google.protobuf.Message instance)
        +
        Generate a key for a new entity, which must be stored by this driver, but does not yet have a key. If the driver + does not support key generation, UnsupportedOperationException is thrown. + +

        Generated keys are expected to be best-effort unique. Generally, Java's built-in UUID should + do the trick just fine. In more complex or scalable circumstances, this method can be overridden to reach out to + the data engine to generate a key.

        +
        +
        Specified by:
        +
        generateKey in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>
        +
        Parameters:
        +
        instance - Default instance of the model type for which a key is desired.
        +
        Returns:
        +
        Generated key for an entity to be stored.
        +
        +
      • +
      + + + + + + + + + + + +
        +
      • +

        persist

        +
        @Nonnull
        +public ReactiveFuture<Modelpersist​(@Nonnull
        +                                     Key key,
        +                                     @Nonnull
        +                                     Model model,
        +                                     @Nonnull
        +                                     WriteOptions options)
        +
        Low-level record persistence method. Effectively called by all other create/put variants. Asynchronously write a + data model instance to storage, which will populate the provided ReactiveFuture value. + +

        Optionally, a key may be provided as a nominated value to the storage engine. Whether the engine accepts + nominated keys is up to the implementation. In all cases, the engine must return the key used to store and address + the value henceforth. If the engine does support nominated keys, it must operate in an idempotent + manner with regard to those keys. In other words, repeated calls to create the same entity with the same key will + not cause spurious side-effects - only one record will be created, with the remaining calls being rejected by the + underlying engine.

        + +

        All futures emitted via the persistence framework (and Gust writ-large) are ListenableFuture-compliant + implementations under the hood, but ReactiveFuture allows a model-layer result to be used as a + Future, or a one-item reactive Publisher.

        + +

        This method additionally enables specification of custom WriteOptions, which are applied on a per- + operation basis to override global defaults.

        + +

        Exceptions: Instead of throwing a PersistenceException as other methods do, this operation will + emit the exception over the Future channel instead, or raise the exception in the event + Future.get() is called to surface it in the invoking (or dependent) code.

        +
        +
        Specified by:
        +
        persist in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>
        +
        Parameters:
        +
        key - Key nominated by invoking code for storing this record. If no key is provided, the underlying storage + engine is expected to allocate one. Where unsupported, PersistenceException will be thrown.
        +
        model - Model to store at the specified key, if provided.
        +
        options - Options to apply to this persist operation.
        +
        Returns:
        +
        Reactive future, which resolves to the key where the provided model is now stored. In no case should this + method return null. Instead, PersistenceException will be thrown.
        +
        +
      • +
      + + + + + +
        +
      • +

        delete

        +
        @Nonnull
        +public ReactiveFuture<Keydelete​(@Nonnull
        +                                  Key key,
        +                                  @Nonnull
        +                                  DeleteOptions options)
        +
        Low-level record delete method. Effectively called by all other delete variants. Asynchronously and permanently + erase an existing data model instance from storage, addressed by its key unique key or ID. + +

        If no key or ID field, or value, may be located, an error is raised (see below for details). This operation is + expected to operate in an idempotent manner (i.e. repeated calls with identical parameters do not yield + different side effects). Calls referring to an already-deleted entity should silently succeed.

        + +

        All futures emitted via the persistence framework (and Gust writ-large) are ListenableFuture-compliant + implementations under the hood, but ReactiveFuture allows a model-layer result to be used as a + Future, or a one-item reactive Publisher.

        + +

        This method additionally enables specification of custom DeleteOptions, which are applied on a per- + operation basis to override global defaults.

        + +

        Exceptions: Instead of throwing a PersistenceException as other methods do, this operation will + emit the exception over the Future channel instead, or raise the exception in the event + Future.get() is called to surface it in the invoking (or dependent) code.

        +
        +
        Specified by:
        +
        delete in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.firestore.DocumentSnapshot,​CollapsedMessage>
        +
        Parameters:
        +
        key - Unique key referring to the record in storage that should be deleted.
        +
        options - Options to apply to this specific delete operation.
        +
        Returns:
        +
        Future value, which resolves to the deleted record's key when the operation completes.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/firestore/FirestoreManager.html b/docs/java/gust/backend/driver/firestore/FirestoreManager.html new file mode 100644 index 000000000..89a70cd49 --- /dev/null +++ b/docs/java/gust/backend/driver/firestore/FirestoreManager.html @@ -0,0 +1,274 @@ + + + + + +FirestoreManager + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class FirestoreManager

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.firestore.FirestoreManager
    • +
    +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + +
      Constructors 
      ConstructorDescription
      FirestoreManager() 
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+ +
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/firestore/FirestoreTransportConfig.html b/docs/java/gust/backend/driver/firestore/FirestoreTransportConfig.html new file mode 100644 index 000000000..b269a6b63 --- /dev/null +++ b/docs/java/gust/backend/driver/firestore/FirestoreTransportConfig.html @@ -0,0 +1,644 @@ + + + + + +FirestoreTransportConfig + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class FirestoreTransportConfig

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.firestore.FirestoreTransportConfig
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    GrpcTransportConfig, GrpcTransportCredentials, PooledTransportConfig, TransportConfig, TransportCredentials
    +
    +
    +
    @ConfigurationProperties("transport.google.firestore")
    +public final class FirestoreTransportConfig
    +extends java.lang.Object
    +implements GrpcTransportConfig
    +
    Specifies configuration property bindings for a managed transport channel interacting with Cloud Firestore. Also in + charge of supplying a sensible set of defaults, if no config properties are specified. + +

    Configurable aspects of the framework's connection to Firestore include keepalive timings, retries, connection + pooling, connection refreshing, and more. All of these may be customized via a number of code paths: +

      +
    • Environment: By default, Micronaut will automatically merge config with environment vars.
    • +
    • Config: You can configure things under the
      transport.google.firestore
      prefix.
    • +
    • Bean events: You can watch for a bean event creating this object, and use the methods on it to change + configured values before they are used.
    • +

    +
  • +
+
+
+ +
+
+ +
+
+
+ + + + diff --git a/docs/java/gust/backend/driver/firestore/class-use/FirestoreAdapter.html b/docs/java/gust/backend/driver/firestore/class-use/FirestoreAdapter.html new file mode 100644 index 000000000..566e16487 --- /dev/null +++ b/docs/java/gust/backend/driver/firestore/class-use/FirestoreAdapter.html @@ -0,0 +1,241 @@ + + + + + +Uses of Class gust.backend.driver.firestore.FirestoreAdapter + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.firestore.FirestoreAdapter

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use FirestoreAdapter 
    PackageDescription
    gust.backend.driver.firestore +
    Provides a DatabaseDriver implementation, using Google Cloud Firestore.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of FirestoreAdapter in gust.backend.driver.firestore

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.driver.firestore that return FirestoreAdapter 
      Modifier and TypeMethodDescription
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      FirestoreAdapter<K,​M>
      FirestoreAdapter.acquire​(com.google.cloud.firestore.FirestoreOptions.Builder baseOptions, + com.google.api.gax.rpc.TransportChannelProvider firestoreChannel, + com.google.api.gax.core.CredentialsProvider credentialsProvider, + com.google.cloud.grpc.GrpcTransportOptions transportOptions, + com.google.common.util.concurrent.ListeningScheduledExecutorService executorService, + K keyInstance, + M messageInstance) +
      Acquire an instance of the FirestoreAdapter and FirestoreDriver, customized for the provided + `modelInstance` and `keyInstance`.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      FirestoreAdapter<K,​M>
      FirestoreAdapter.acquire​(K keyInstance, + M messageInstance, + com.google.cloud.firestore.FirestoreOptions.Builder baseOptions, + com.google.common.util.concurrent.ListeningScheduledExecutorService executorService) +
      Acquire an instance of the FirestoreAdapter and FirestoreDriver, customized for the provided + `modelInstance` and `keyInstance`.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      FirestoreAdapter<K,​M>
      FirestoreAdapter.acquire​(K keyInstance, + M messageInstance, + com.google.common.util.concurrent.ListeningScheduledExecutorService executorService) +
      Acquire an instance of the FirestoreAdapter and FirestoreDriver, customized for the provided + `modelInstance` and `keyInstance`.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      FirestoreAdapter<K,​M>
      FirestoreAdapter.forModel​(com.google.protobuf.Message.Builder builder, + FirestoreDriver<K,​M> driver) +
      Create or otherwise resolve a FirestoreAdapter for the provided model type and builder.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      FirestoreAdapter<K,​M>
      FirestoreAdapter.forModel​(com.google.protobuf.Message.Builder builder, + FirestoreDriver<K,​M> driver, + java.util.Optional<CacheDriver<K,​M>> cacheDriver) +
      Create or otherwise resolve a FirestoreAdapter for the provided model type and builder.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/firestore/class-use/FirestoreDriver.html b/docs/java/gust/backend/driver/firestore/class-use/FirestoreDriver.html new file mode 100644 index 000000000..7beb1995e --- /dev/null +++ b/docs/java/gust/backend/driver/firestore/class-use/FirestoreDriver.html @@ -0,0 +1,206 @@ + + + + + +Uses of Class gust.backend.driver.firestore.FirestoreDriver + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.firestore.FirestoreDriver

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/firestore/class-use/FirestoreManager.html b/docs/java/gust/backend/driver/firestore/class-use/FirestoreManager.html new file mode 100644 index 000000000..da4ee3f38 --- /dev/null +++ b/docs/java/gust/backend/driver/firestore/class-use/FirestoreManager.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.driver.firestore.FirestoreManager + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.firestore.FirestoreManager

+
+
No usage of gust.backend.driver.firestore.FirestoreManager
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/firestore/class-use/FirestoreTransportConfig.html b/docs/java/gust/backend/driver/firestore/class-use/FirestoreTransportConfig.html new file mode 100644 index 000000000..a62178f96 --- /dev/null +++ b/docs/java/gust/backend/driver/firestore/class-use/FirestoreTransportConfig.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.driver.firestore.FirestoreTransportConfig + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.firestore.FirestoreTransportConfig

+
+
No usage of gust.backend.driver.firestore.FirestoreTransportConfig
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/firestore/package-summary.html b/docs/java/gust/backend/driver/firestore/package-summary.html new file mode 100644 index 000000000..47a202331 --- /dev/null +++ b/docs/java/gust/backend/driver/firestore/package-summary.html @@ -0,0 +1,191 @@ + + + + + +gust.backend.driver.firestore + + + + + + + + + + + + + + +
+ +
+
+
+

Package gust.backend.driver.firestore

+
+
+
+ + +
Provides a DatabaseDriver implementation, using Google Cloud Firestore.
+
+
    +
  • + + + + + + + + + + + + + + + + + + + + + + + + +
    Class Summary 
    ClassDescription
    FirestoreAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message> +
    Defines a built-in database adapter for interacting with Google Cloud Firestore, using business-data models defined + through Protobuf annotated with framework-provided metadata.
    +
    FirestoreDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message> +
    Defines a built-in framework DatabaseDriver for interacting seamlessly with Google Cloud Firestore.
    +
    FirestoreManager 
    FirestoreTransportConfig +
    Specifies configuration property bindings for a managed transport channel interacting with Cloud Firestore.
    +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/firestore/package-tree.html b/docs/java/gust/backend/driver/firestore/package-tree.html new file mode 100644 index 000000000..cfc149ba2 --- /dev/null +++ b/docs/java/gust/backend/driver/firestore/package-tree.html @@ -0,0 +1,166 @@ + + + + + +gust.backend.driver.firestore Class Hierarchy + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package gust.backend.driver.firestore

+Package Hierarchies: + +
+
+
+

Class Hierarchy

+ +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/firestore/package-use.html b/docs/java/gust/backend/driver/firestore/package-use.html new file mode 100644 index 000000000..2c847e5ff --- /dev/null +++ b/docs/java/gust/backend/driver/firestore/package-use.html @@ -0,0 +1,194 @@ + + + + + +Uses of Package gust.backend.driver.firestore + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Package
gust.backend.driver.firestore

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/inmemory/InMemoryAdapter.html b/docs/java/gust/backend/driver/inmemory/InMemoryAdapter.html new file mode 100644 index 000000000..91f668272 --- /dev/null +++ b/docs/java/gust/backend/driver/inmemory/InMemoryAdapter.html @@ -0,0 +1,490 @@ + + + + + +InMemoryAdapter + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class InMemoryAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.inmemory.InMemoryAdapter<Key,​Model>
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    ModelAdapter<Key,​Model,​EncodedModel,​EncodedModel>, PersistenceDriver<Key,​Model,​EncodedModel,​EncodedModel>
    +
    +
    +
    public final class InMemoryAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message>
    +extends java.lang.Object
    +implements ModelAdapter<Key,​Model,​EncodedModel,​EncodedModel>
    +
    Reference implementation of a ModelAdapter. Stores persisted models in a static concurrent hash map. It is + not a good idea to use this in production, under any circumstances (especially because there is no persistence across + restarts or between hosts). + +

    This adapter can use any model codec, and any cache driver, in front of its storage operations. The backing map + stores entities as opaque blobs, so it doesn't care how they are serialized or inflated. Queries are not supported by + this engine.

    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + + + +
        +
      • +

        acquire

        +
        @Nonnull
        +public static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message> InMemoryAdapter<K,​M> acquire​(@Nonnull
        +                                                                                                                                     K keyInstance,
        +                                                                                                                                     @Nonnull
        +                                                                                                                                     M instance,
        +                                                                                                                                     @Nonnull
        +                                                                                                                                     com.google.common.util.concurrent.ListeningScheduledExecutorService executorService)
        +                                                                                                                              throws InvalidModelType
        +
        Acquire an instance of the InMemoryAdapter, specialized for the provided empty model instance. + +

        An empty instance can easily be acquired for any given model, via MessageLiteOrBuilder.getDefaultInstanceForType(). + The instance is used only for builder-spawning and type information. The provided ExecutorService is used + for model codec activities and callback dispatch.

        +
        +
        Type Parameters:
        +
        M - Type of model for which an InMemoryAdapter is being requested.
        +
        Parameters:
        +
        keyInstance - Empty instance of the key type for
        instance
        .
        +
        instance - Empty model instance with which to spawn new builders, and resolve type information.
        +
        executorService - Executor to use for callbacks and model codec activities.
        +
        Returns:
        +
        Instance of an in-memory data adapter for the provided model.
        +
        Throws:
        +
        InvalidModelType - If the specified model is not meant to be used for storage.
        +
        +
      • +
      + + + + + +
        +
      • +

        acquire

        +
        @Nonnull
        +public static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message> InMemoryAdapter<K,​M> acquire​(@Nonnull
        +                                                                                                                                     K keyInstance,
        +                                                                                                                                     @Nonnull
        +                                                                                                                                     M instance,
        +                                                                                                                                     @Nonnull
        +                                                                                                                                     java.util.Optional<CacheDriver<K,​M>> cache,
        +                                                                                                                                     @Nonnull
        +                                                                                                                                     com.google.common.util.concurrent.ListeningScheduledExecutorService executorService)
        +                                                                                                                              throws InvalidModelType
        +
        Acquire an instance of the InMemoryAdapter, specialized for the provided empty model instance, optionally + specifying a CacheDriver to use. + +

        An empty instance can easily be acquired for any given model, via MessageLiteOrBuilder.getDefaultInstanceForType(). + The instance is used only for builder-spawning and type information. The provided ExecutorService is used + for model codec activities and callback dispatch.

        + +

        If Optional.empty()

        is passed as the cache, no caching will take place. If a valid + CacheDriver instance is provided, it will be used only if options on a request allow for it + (caching defaults to being active).

        +
        +
        Type Parameters:
        +
        M - Type of model for which an InMemoryAdapter is being requested.
        +
        Parameters:
        +
        keyInstance - Empty instance of the key type for
        instance
        .
        +
        instance - Empty model instance with which to spawn new builders, and resolve type information.
        +
        cache - Cache driver to use for read-path code in the adapter.
        +
        executorService - Executor to use for callbacks and model codec activities.
        +
        Returns:
        +
        Instance of an in-memory data adapter for the provided model.
        +
        Throws:
        +
        InvalidModelType - If the specified model is not meant to be used for storage.
        +
        +
      • +
      + + + +
        +
      • +

        codec

        +
        @Nonnull
        +public ModelCodec<Model,​EncodedModel,​EncodedModelcodec()
        +
        Acquire an instance of the codec used by this adapter. Codecs are either injected/otherwise provided during adapter + construction, or they are specified statically if the adapter depends on a specific codec.
        +
        +
        Specified by:
        +
        codec in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​EncodedModel,​EncodedModel>
        +
        Returns:
        +
        Model codec currently in use by this adapter.
        +
        +
      • +
      + + + +
        +
      • +

        cache

        +
        @Nonnull
        +public java.util.Optional<CacheDriver<Key,​Model>> cache()
        +
        Return the cache driver in use for this particular model adapter. If a cache driver is present, and active/enabled + according to database driver settings, it will be used on read-paths (such as fetching objects by ID).
        +
        +
        Specified by:
        +
        cache in interface ModelAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​EncodedModel,​EncodedModel>
        +
        Returns:
        +
        Cache driver currently in use by this model adapter.
        +
        +
      • +
      + + + +
        +
      • +

        engine

        +
        @Nonnull
        +public InMemoryDriver<Key,​Modelengine()
        +
        Return the lower-level PersistenceDriver powering this adapter. The driver is responsible for communicating + with the actual backing storage service, either via local stubs/emulators or a production API.
        +
        +
        Specified by:
        +
        engine in interface ModelAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​EncodedModel,​EncodedModel>
        +
        Returns:
        +
        Persistence driver instance currently in use by this model adapter.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/inmemory/InMemoryCache.html b/docs/java/gust/backend/driver/inmemory/InMemoryCache.html new file mode 100644 index 000000000..a7d57fa74 --- /dev/null +++ b/docs/java/gust/backend/driver/inmemory/InMemoryCache.html @@ -0,0 +1,491 @@ + + + + + +InMemoryCache + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class InMemoryCache<K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.inmemory.InMemoryCache<K,​M>
    • +
    +
  • +
+
+
    +
  • +
    +
    Type Parameters:
    +
    K - Type of key used with the cache and model.
    +
    M - Type of model supported by this cache facade.
    +
    +
    +
    All Implemented Interfaces:
    +
    CacheDriver<K,​M>
    +
    +
    +
    @ThreadSafe
    +public final class InMemoryCache<K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
    +extends java.lang.Object
    +implements CacheDriver<K,​M>
    +
    Defines a CacheDriver backed by a Guava in-memory cache, which statically holds onto cached full model + instances, potentially on behalf of some other persistence driver (via use with a ModelAdapter). + +

    Cache options may be adjusted based on the operation being memoized, using the CacheOptions interface, + which is supported by various other higher-order options interfaces (i.e. FetchOptions).

    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + +
      Constructors 
      ConstructorDescription
      InMemoryCache() 
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      InMemoryCache<K,​M>
      acquire() +
      Acquire an instance of the in-memory caching driver, generalized to support the provided key type K and + model instance type M.
      +
      ReactiveFutureevict​(K key, + com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Force-evict any cached record at the provided key in the cache managed by this driver.
      +
      ReactiveFuture<java.util.Optional<M>>fetch​(K key, + FetchOptions options, + com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Attempt to resolve a known model, addressed by key, from the cache powered/backed by this driver, according + to options and making use of executor.
      +
      ReactiveFutureflush​(com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Flush the entire cache managed by this driver.
      +
      ReactiveFutureput​(com.google.protobuf.Message key, + com.google.protobuf.Message model, + com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Write a record (model) at key into the cache, overwriting any model currently stored at the same + key, if applicable.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      + +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        acquire

        +
        @Nonnull
        +public static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message> InMemoryCache<K,​M> acquire()
        +
        Acquire an instance of the in-memory caching driver, generalized to support the provided key type K and + model instance type M.
        +
        +
        Type Parameters:
        +
        K - Generic type for the key associated with model type M.
        +
        M - Generic model type managed by this cache.
        +
        Returns:
        +
        Instance of the acquired cache engine.
        +
        +
      • +
      + + + +
        +
      • +

        put

        +
        @Nonnull
        +public ReactiveFuture put​(@Nonnull
        +                          com.google.protobuf.Message key,
        +                          @Nonnull
        +                          com.google.protobuf.Message model,
        +                          @Nonnull
        +                          com.google.common.util.concurrent.ListeningScheduledExecutorService executor)
        +
        Write a record (model) at key into the cache, overwriting any model currently stored at the same + key, if applicable. The resulting future completes with no value, when the cache write has finished, to let the + framework know the cache is done following up.
        +
        +
        Specified by:
        +
        put in interface CacheDriver<K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
        +
        Parameters:
        +
        key - Key for the record we should inject into the cache.
        +
        model - Record data to inject into the cache.
        +
        executor - Executor to use for any async operations.
        +
        Returns:
        +
        Future, which simply completes when the write is done.
        +
        +
      • +
      + + + + + +
        +
      • +

        fetch

        +
        @Nonnull
        +public ReactiveFuture<java.util.Optional<M>> fetch​(@Nonnull
        +                                                   K key,
        +                                                   @Nonnull
        +                                                   FetchOptions options,
        +                                                   @Nonnull
        +                                                   com.google.common.util.concurrent.ListeningScheduledExecutorService executor)
        +
        Attempt to resolve a known model, addressed by key, from the cache powered/backed by this driver, according + to options and making use of executor. + +

        If no value is available in the cache, Optional.empty() must be returned, which triggers a call to the + driver to resolve the record. If the record can be fetched originally, it will later be added to the cache by a + separate call to CacheDriver.put(Message, Message, ListeningScheduledExecutorService).

        +
        +
        Specified by:
        +
        fetch in interface CacheDriver<K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
        +
        Parameters:
        +
        key - Key for the record which we should look for in the cache.
        +
        options - Options to apply to the fetch routine taking place.
        +
        executor - Executor to use for async tasks. Provided by the driver or adapter.
        +
        Returns:
        +
        Future value, which resolves either to Optional.empty() or a wrapped result value.
        +
        +
      • +
      + + + + + +
        +
      • +

        evict

        +
        @Nonnull
        +public ReactiveFuture evict​(@Nonnull
        +                            K key,
        +                            @Nonnull
        +                            com.google.common.util.concurrent.ListeningScheduledExecutorService executor)
        +
        Force-evict any cached record at the provided key in the cache managed by this driver. This operation is + expected to succeed in all cases and perform its work in an idempotent manner.
        +
        +
        Specified by:
        +
        evict in interface CacheDriver<K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
        +
        Parameters:
        +
        key - Key for the record to force-evict from the cache.
        +
        executor - Executor to use for any async operations.
        +
        Returns:
        +
        Future, which resolves to the evicted key when the operation completes.
        +
        +
      • +
      + + + +
        +
      • +

        flush

        +
        @Nonnull
        +public ReactiveFuture flush​(@Nonnull
        +                            com.google.common.util.concurrent.ListeningScheduledExecutorService executor)
        +
        Flush the entire cache managed by this driver. This should drop all keys related to model instance caching that are + currently held by the cache.
        +
        +
        Specified by:
        +
        flush in interface CacheDriver<K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
        +
        Parameters:
        +
        executor - Executor to use for any async operations.
        +
        Returns:
        +
        Future, which simply completes when the flush is done.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/inmemory/InMemoryDriver.html b/docs/java/gust/backend/driver/inmemory/InMemoryDriver.html new file mode 100644 index 000000000..889f6c6e2 --- /dev/null +++ b/docs/java/gust/backend/driver/inmemory/InMemoryDriver.html @@ -0,0 +1,519 @@ + + + + + +InMemoryDriver + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class InMemoryDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.inmemory.InMemoryDriver<Key,​Model>
    • +
    +
  • +
+
+
    +
  • +
    +
    Type Parameters:
    +
    Model - Model/message type which we are storing with this driver.
    +
    +
    +
    All Implemented Interfaces:
    +
    PersistenceDriver<Key,​Model,​EncodedModel,​EncodedModel>
    +
    +
    +
    public final class InMemoryDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message>
    +extends java.lang.Object
    +implements PersistenceDriver<Key,​Model,​EncodedModel,​EncodedModel>
    +
    Proxies calls to a static concurrent map, held by a private singleton. This nicely supplies local entity storage for + simple testing and mocking purposes. Please do not use this in production. The in-memory data engine does not support + queries, persistence, or nearly anything except get/put/delete.
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        codec

        +
        @Nonnull
        +public ModelCodec<Model,​EncodedModel,​EncodedModelcodec()
        +
        Acquire an instance of the codec used by this adapter. Codecs are either injected/otherwise provided during adapter + construction, or they are specified statically if the adapter depends on a specific codec.
        +
        +
        Specified by:
        +
        codec in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​EncodedModel,​EncodedModel>
        +
        Returns:
        +
        Model codec currently in use by this adapter.
        +
        +
      • +
      + + + +
        +
      • +

        executorService

        +
        @Nonnull
        +public com.google.common.util.concurrent.ListeningScheduledExecutorService executorService()
        +
        Resolve an executor service for use with this persistence driver. Operations will be executed against this as they + are received.
        +
        +
        Specified by:
        +
        executorService in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​EncodedModel,​EncodedModel>
        +
        Returns:
        +
        Scheduled executor service.
        +
        +
      • +
      + + + + + + + + + + + +
        +
      • +

        persist

        +
        @Nonnull
        +public ReactiveFuture<Modelpersist​(@Nullable
        +                                     Key key,
        +                                     @Nonnull
        +                                     Model model,
        +                                     @Nonnull
        +                                     WriteOptions options)
        +
        Low-level record persistence method. Effectively called by all other create/put variants. Asynchronously write a + data model instance to storage, which will populate the provided ReactiveFuture value. + +

        Optionally, a key may be provided as a nominated value to the storage engine. Whether the engine accepts + nominated keys is up to the implementation. In all cases, the engine must return the key used to store and address + the value henceforth. If the engine does support nominated keys, it must operate in an idempotent + manner with regard to those keys. In other words, repeated calls to create the same entity with the same key will + not cause spurious side-effects - only one record will be created, with the remaining calls being rejected by the + underlying engine.

        + +

        All futures emitted via the persistence framework (and Gust writ-large) are ListenableFuture-compliant + implementations under the hood, but ReactiveFuture allows a model-layer result to be used as a + Future, or a one-item reactive Publisher.

        + +

        This method additionally enables specification of custom WriteOptions, which are applied on a per- + operation basis to override global defaults.

        + +

        Exceptions: Instead of throwing a PersistenceException as other methods do, this operation will + emit the exception over the Future channel instead, or raise the exception in the event + Future.get() is called to surface it in the invoking (or dependent) code.

        +
        +
        Specified by:
        +
        persist in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​EncodedModel,​EncodedModel>
        +
        Parameters:
        +
        key - Key nominated by invoking code for storing this record. If no key is provided, the underlying storage + engine is expected to allocate one. Where unsupported, PersistenceException will be thrown.
        +
        model - Model to store at the specified key, if provided.
        +
        options - Options to apply to this persist operation.
        +
        Returns:
        +
        Reactive future, which resolves to the key where the provided model is now stored. In no case should this + method return null. Instead, PersistenceException will be thrown.
        +
        +
      • +
      + + + + + +
        +
      • +

        delete

        +
        @Nonnull
        +public ReactiveFuture<Keydelete​(@Nonnull
        +                                  Key key,
        +                                  @Nonnull
        +                                  DeleteOptions options)
        +
        Low-level record delete method. Effectively called by all other delete variants. Asynchronously and permanently + erase an existing data model instance from storage, addressed by its key unique key or ID. + +

        If no key or ID field, or value, may be located, an error is raised (see below for details). This operation is + expected to operate in an idempotent manner (i.e. repeated calls with identical parameters do not yield + different side effects). Calls referring to an already-deleted entity should silently succeed.

        + +

        All futures emitted via the persistence framework (and Gust writ-large) are ListenableFuture-compliant + implementations under the hood, but ReactiveFuture allows a model-layer result to be used as a + Future, or a one-item reactive Publisher.

        + +

        This method additionally enables specification of custom DeleteOptions, which are applied on a per- + operation basis to override global defaults.

        + +

        Exceptions: Instead of throwing a PersistenceException as other methods do, this operation will + emit the exception over the Future channel instead, or raise the exception in the event + Future.get() is called to surface it in the invoking (or dependent) code.

        +
        +
        Specified by:
        +
        delete in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​EncodedModel,​EncodedModel>
        +
        Parameters:
        +
        key - Unique key referring to the record in storage that should be deleted.
        +
        options - Options to apply to this specific delete operation.
        +
        Returns:
        +
        Future value, which resolves to the deleted record's key when the operation completes.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/inmemory/InMemoryManager.html b/docs/java/gust/backend/driver/inmemory/InMemoryManager.html new file mode 100644 index 000000000..cd1876015 --- /dev/null +++ b/docs/java/gust/backend/driver/inmemory/InMemoryManager.html @@ -0,0 +1,274 @@ + + + + + +InMemoryManager + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class InMemoryManager

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.inmemory.InMemoryManager
    • +
    +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + +
      Constructors 
      ConstructorDescription
      InMemoryManager() 
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+ +
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/inmemory/class-use/InMemoryAdapter.html b/docs/java/gust/backend/driver/inmemory/class-use/InMemoryAdapter.html new file mode 100644 index 000000000..14bd1a59d --- /dev/null +++ b/docs/java/gust/backend/driver/inmemory/class-use/InMemoryAdapter.html @@ -0,0 +1,209 @@ + + + + + +Uses of Class gust.backend.driver.inmemory.InMemoryAdapter + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.inmemory.InMemoryAdapter

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use InMemoryAdapter 
    PackageDescription
    gust.backend.driver.inmemory +
    Provides a reference implementation of a persistence driver, backed by a concurrent map.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of InMemoryAdapter in gust.backend.driver.inmemory

      + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.driver.inmemory that return InMemoryAdapter 
      Modifier and TypeMethodDescription
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      InMemoryAdapter<K,​M>
      InMemoryAdapter.acquire​(K keyInstance, + M instance, + com.google.common.util.concurrent.ListeningScheduledExecutorService executorService) +
      Acquire an instance of the InMemoryAdapter, specialized for the provided empty model instance.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      InMemoryAdapter<K,​M>
      InMemoryAdapter.acquire​(K keyInstance, + M instance, + java.util.Optional<CacheDriver<K,​M>> cache, + com.google.common.util.concurrent.ListeningScheduledExecutorService executorService) +
      Acquire an instance of the InMemoryAdapter, specialized for the provided empty model instance, optionally + specifying a CacheDriver to use.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/inmemory/class-use/InMemoryCache.html b/docs/java/gust/backend/driver/inmemory/class-use/InMemoryCache.html new file mode 100644 index 000000000..a2adcb311 --- /dev/null +++ b/docs/java/gust/backend/driver/inmemory/class-use/InMemoryCache.html @@ -0,0 +1,197 @@ + + + + + +Uses of Class gust.backend.driver.inmemory.InMemoryCache + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.inmemory.InMemoryCache

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/inmemory/class-use/InMemoryDriver.html b/docs/java/gust/backend/driver/inmemory/class-use/InMemoryDriver.html new file mode 100644 index 000000000..de2d7ace8 --- /dev/null +++ b/docs/java/gust/backend/driver/inmemory/class-use/InMemoryDriver.html @@ -0,0 +1,196 @@ + + + + + +Uses of Class gust.backend.driver.inmemory.InMemoryDriver + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.inmemory.InMemoryDriver

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/inmemory/class-use/InMemoryManager.html b/docs/java/gust/backend/driver/inmemory/class-use/InMemoryManager.html new file mode 100644 index 000000000..69c29829a --- /dev/null +++ b/docs/java/gust/backend/driver/inmemory/class-use/InMemoryManager.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.driver.inmemory.InMemoryManager + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.inmemory.InMemoryManager

+
+
No usage of gust.backend.driver.inmemory.InMemoryManager
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/inmemory/package-summary.html b/docs/java/gust/backend/driver/inmemory/package-summary.html new file mode 100644 index 000000000..d84c9c6f7 --- /dev/null +++ b/docs/java/gust/backend/driver/inmemory/package-summary.html @@ -0,0 +1,191 @@ + + + + + +gust.backend.driver.inmemory + + + + + + + + + + + + + + +
+ +
+
+
+

Package gust.backend.driver.inmemory

+
+
+
+ + +
Provides a reference implementation of a persistence driver, backed by a concurrent map.
+
+
    +
  • + + + + + + + + + + + + + + + + + + + + + + + + +
    Class Summary 
    ClassDescription
    InMemoryAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message> +
    Reference implementation of a ModelAdapter.
    +
    InMemoryCache<K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message> +
    Defines a CacheDriver backed by a Guava in-memory cache, which statically holds onto cached full model + instances, potentially on behalf of some other persistence driver (via use with a ModelAdapter).
    +
    InMemoryDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message> +
    Proxies calls to a static concurrent map, held by a private singleton.
    +
    InMemoryManager 
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/inmemory/package-tree.html b/docs/java/gust/backend/driver/inmemory/package-tree.html new file mode 100644 index 000000000..59fc76970 --- /dev/null +++ b/docs/java/gust/backend/driver/inmemory/package-tree.html @@ -0,0 +1,166 @@ + + + + + +gust.backend.driver.inmemory Class Hierarchy + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package gust.backend.driver.inmemory

+Package Hierarchies: + +
+
+
+

Class Hierarchy

+ +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/inmemory/package-use.html b/docs/java/gust/backend/driver/inmemory/package-use.html new file mode 100644 index 000000000..fa133f5bb --- /dev/null +++ b/docs/java/gust/backend/driver/inmemory/package-use.html @@ -0,0 +1,200 @@ + + + + + +Uses of Package gust.backend.driver.inmemory + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Package
gust.backend.driver.inmemory

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/SpannerAdapter.html b/docs/java/gust/backend/driver/spanner/SpannerAdapter.html new file mode 100644 index 000000000..c07b9a500 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerAdapter.html @@ -0,0 +1,890 @@ + + + + + +SpannerAdapter + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class SpannerAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.spanner.SpannerAdapter<Key,​Model>
    • +
    +
  • +
+
+
    +
  • +
    +
    Type Parameters:
    +
    Key - Typed Message which implements a concrete model key structure, as defined and annotated by the + core Gust annotations.
    +
    Model - Typed Message which implements a concrete model object structure, as defined and annotated by + the core Gust annotations.
    +
    +
    +
    All Implemented Interfaces:
    +
    DatabaseAdapter<Key,​Model,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>, ModelAdapter<Key,​Model,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>, PersistenceDriver<Key,​Model,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>, java.io.Closeable, java.lang.AutoCloseable
    +
    +
    +
    @Immutable
    +@ThreadSafe
    +public final class SpannerAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message>
    +extends java.lang.Object
    +implements DatabaseAdapter<Key,​Model,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>, java.io.Closeable, java.lang.AutoCloseable
    +
    Implementation of a DatabaseAdapter backed by Google Cloud Spanner, capable of marshalling arbitrary + schema-generated Message objects back and forth from structured columnar storage. + +

    This adapter is implemented by the SpannerDriver and related codec classes (SpannerCodec, + SpannerStructDeserializer and SpannerMutationSerializer). Background execution and gRPC channels are + hooked into driver acquisition and may be managed by the developer, or by the framework automatically. See below for + a summary of application-level Spanner features supported by this engine:

    + +

    Caching may be facilitated by any compliant model CacheDriver.

    + +

    Transactions are supported under the hood, and can be controlled via Spanner-extended operation options + interfaces (such as SpannerDriver.SpannerWriteOptions and SpannerDriver.SpannerFetchOptions). + Invoking code may either opt-in to transactional protection automatically, or drive external transactions with this + adapter/driver by specifying an input transaction for a given operation.

    + +

    Collections are supported by this engine, with additional support for nested models encoded via JSON. In + cases where model JSON is involved, JsonFormat is used to produce and consume + compliant Proto-JSON.

    +
    +
    See Also:
    +
    Adapter instance manager and factory., +Driver-level settings specific to Spanner., +Similar adapter implementation, built on top of Cloud Firestore, + which itself is implemented on top of Cloud Spanner.
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      SpannerAdapter<K,​M>
      acquire​(com.google.cloud.spanner.SpannerOptions.Builder baseOptions, + com.google.cloud.spanner.DatabaseId defaultDatabase, + com.google.api.gax.rpc.TransportChannelProvider spannerChannel, + java.util.Optional<com.google.api.gax.core.CredentialsProvider> credentialsProvider, + java.util.Optional<com.google.cloud.spanner.SpannerOptions.CallCredentialsProvider> callCredentialProvider, + com.google.cloud.grpc.GrpcTransportOptions transportOptions, + com.google.common.util.concurrent.ListeningScheduledExecutorService executorService, + K keyInstance, + M messageInstance, + SpannerDriverSettings driverSettings, + java.util.Optional<CacheDriver<K,​M>> cacheDriver) +
      Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      SpannerAdapter<K,​M>
      acquire​(K keyInstance, + M messageInstance, + com.google.cloud.spanner.DatabaseId defaultDatabase) +
      Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      SpannerAdapter<K,​M>
      acquire​(K keyInstance, + M messageInstance, + com.google.cloud.spanner.DatabaseId defaultDatabase, + com.google.cloud.spanner.SpannerOptions.Builder baseOptions, + com.google.common.util.concurrent.ListeningScheduledExecutorService executorService) +
      Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      SpannerAdapter<K,​M>
      acquire​(K keyInstance, + M messageInstance, + com.google.cloud.spanner.DatabaseId defaultDatabase, + com.google.common.util.concurrent.ListeningScheduledExecutorService executorService) +
      Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      SpannerAdapter<K,​M>
      acquire​(K keyInstance, + M messageInstance, + com.google.cloud.spanner.DatabaseId defaultDatabase, + java.util.Optional<com.google.common.util.concurrent.ListeningScheduledExecutorService> executorService, + java.util.Optional<SpannerDriverSettings> driverSettings, + java.util.Optional<com.google.cloud.spanner.SpannerOptions.Builder> baseOptions, + java.util.Optional<CacheDriver<K,​M>> cacheDriver) +
      Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId.
      +
      java.util.Optional<CacheDriver<Key,​Model>>cache() +
      Return the cache driver in use for this particular model adapter.
      +
      voidclose() 
      ModelCodec<Model,​com.google.cloud.spanner.Mutation,​com.google.cloud.spanner.Struct>codec() +
      Acquire an instance of the codec used by this adapter.
      +
      DatabaseDriver<Key,​Model,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>engine() +
      Return the lower-level DatabaseDriver powering this adapter.
      +
      com.google.common.util.concurrent.ListeningScheduledExecutorServiceexecutorService() +
      Resolve an executor service for use with this persistence driver.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      SpannerAdapter<K,​M>
      forModel​(SpannerDriver<K,​M> driver) +
      Create or resolve a SpannerAdapter for the pre-fabricated SpannerDriver.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      SpannerAdapter<K,​M>
      forModel​(SpannerDriver<K,​M> driver, + java.util.Optional<CacheDriver<K,​M>> cacheDriver) +
      Create or resolve a SpannerAdapter for the pre-fabricated SpannerDriver, optionally using the + provided CacheDriver, if present.
      +
      com.google.cloud.spanner.Spannerspanner() +
      Acquire the Spanner for Java client powering this adapter.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      + + + +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        forModel

        +
        @Nonnull
        +public static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message> SpannerAdapter<K,​M> forModel​(@Nonnull
        +                                                                                                                                     SpannerDriver<K,​M> driver)
        +
        Create or resolve a SpannerAdapter for the pre-fabricated SpannerDriver. + +

        Note: this method has no way to specify a cache. See below for alternatives.

        +
        +
        Type Parameters:
        +
        K - Typed model key structure which the resulting adapter should be specialized to.
        +
        M - Typed model object structure which the resulting adapter should be specialized to.
        +
        Parameters:
        +
        driver - Pre-fabricated Spanner driver to wrap with an adapter instance.
        +
        Returns:
        +
        Adapter instance, wrapping the provided driver.
        +
        See Also:
        +
        Variant of this method that allows invoking code to provide a + compliant instance.
        +
        +
      • +
      + + + +
        +
      • +

        forModel

        +
        @Nonnull
        +public static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message> SpannerAdapter<K,​M> forModel​(@Nonnull
        +                                                                                                                                     SpannerDriver<K,​M> driver,
        +                                                                                                                                     @Nonnull
        +                                                                                                                                     java.util.Optional<CacheDriver<K,​M>> cacheDriver)
        +
        Create or resolve a SpannerAdapter for the pre-fabricated SpannerDriver, optionally using the + provided CacheDriver, if present.
        +
        +
        Type Parameters:
        +
        K - Typed model key structure which the resulting adapter should be specialized to.
        +
        M - Typed model object structure which the resulting adapter should be specialized to.
        +
        Parameters:
        +
        driver - Pre-fabricated Spanner driver to wrap with an adapter instance.
        +
        cacheDriver - Optional cache engine to employ when interacting with the provided driver.
        +
        Returns:
        +
        Adapter instance, wrapping the provided driver.
        +
        +
      • +
      + + + + + +
        +
      • +

        acquire

        +
        @Nonnull
        +public static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message> SpannerAdapter<K,​M> acquire​(@Nonnull
        +                                                                                                                                    K keyInstance,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    M messageInstance,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    com.google.cloud.spanner.DatabaseId defaultDatabase)
        +
        Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId. + +

        This method variant is the simplest invocation option for acquiring an adapter. Variants of this method + provide deeper control over interactions with the Spanner service. See below for alternatives if deeper control + is necessary. Generally, it is best to let the framework manage transport and stub settings.

        +
        +
        Type Parameters:
        +
        K - Model key structure for which the resulting adapter should be specialized.
        +
        M - Model object structure for which the resulting adapter should be specialized.
        +
        Parameters:
        +
        keyInstance - Generated key Message structure, for which the adapter should be specialized.
        +
        messageInstance - Generated object Message structure, for which the adapter should be specialized.
        +
        defaultDatabase - Default Spanner database to use when interacting with this adapter. This value may be + overridden on an individual operation basis via specifying custom + SpannerDriver.SpannerOperationOptions and descendents.
        +
        Returns:
        +
        Spanner adapter instance, specialized to the provided model and key Messages.
        +
        See Also:
        +
        For an option which lets invoking + code specify a background executor for RPC transmission and followup., +Variant of + this same method which offers control of the used to spawn RPC clients., +Full control over creation of the Spanner adapter and driver.
        +
        +
      • +
      + + + + + +
        +
      • +

        acquire

        +
        @Nonnull
        +public static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message> SpannerAdapter<K,​M> acquire​(@Nonnull
        +                                                                                                                                    K keyInstance,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    M messageInstance,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    com.google.cloud.spanner.DatabaseId defaultDatabase,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    com.google.common.util.concurrent.ListeningScheduledExecutorService executorService)
        +
        Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId. + +

        This method variant additionally allows the developer to specify a custom + ListeningScheduledExecutorService to use for background operation execution. This executor service is + injected directly into the SpannerDriver and underlying Spanner RPC client, and is used for both RPC + operational execution and async followup.

        + +

        Variants of this method provide deeper control over interactions with the Spanner service. See below for + alternatives.

        +
        +
        Type Parameters:
        +
        K - Model key structure for which the resulting adapter should be specialized.
        +
        M - Model object structure for which the resulting adapter should be specialized.
        +
        Parameters:
        +
        keyInstance - Generated key Message structure, for which the adapter should be specialized.
        +
        messageInstance - Generated object Message structure, for which the adapter should be specialized.
        +
        defaultDatabase - Default Spanner database to use when interacting with this adapter. This value may be + overridden on an individual operation basis via specifying custom + SpannerDriver.SpannerOperationOptions and descendents.
        +
        executorService - Executor service to use for primary RPC execution and related followup.
        +
        Returns:
        +
        Spanner adapter instance, specialized to the provided model and key Messages.
        +
        See Also:
        +
        For a simpler version of this method which uses managed driver + settings and a sensible cached threadpool executor., +Variant of + this same method which offers control of the used to spawn RPC clients., +Full control over creation of the Spanner adapter and driver.
        +
        +
      • +
      + + + + + +
        +
      • +

        acquire

        +
        @Nonnull
        +public static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message> SpannerAdapter<K,​M> acquire​(@Nonnull
        +                                                                                                                                    K keyInstance,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    M messageInstance,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    com.google.cloud.spanner.DatabaseId defaultDatabase,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    java.util.Optional<com.google.common.util.concurrent.ListeningScheduledExecutorService> executorService,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    java.util.Optional<SpannerDriverSettings> driverSettings,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    java.util.Optional<com.google.cloud.spanner.SpannerOptions.Builder> baseOptions,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    java.util.Optional<CacheDriver<K,​M>> cacheDriver)
        +
        Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId. + +

        This method variant is a balanced invocation which allows invoking code to control most settings, + without coupling too tightly to Google SDKs.

        +
        +
        Type Parameters:
        +
        K - Model key structure for which the resulting adapter should be specialized.
        +
        M - Model object structure for which the resulting adapter should be specialized.
        +
        Parameters:
        +
        keyInstance - Generated key Message structure, for which the adapter should be specialized.
        +
        messageInstance - Generated object Message structure, for which the adapter should be specialized.
        +
        defaultDatabase - Default Spanner database to use when interacting with this adapter. This value may be + overridden on an individual operation basis via specifying custom + SpannerDriver.SpannerOperationOptions and descendents.
        +
        executorService - Executor service to use for primary RPC execution and related followup.
        +
        driverSettings - Custom driver settings to apply. SpannerDriverSettings.DEFAULTS is a good start.
        +
        Returns:
        +
        Spanner adapter instance, specialized to the provided model and key Messages.
        +
        See Also:
        +
        For an option which lets invoking + code specify a background executor for RPC transmission and followup., +Variant of + this same method which offers control of the used to spawn RPC clients., +Full control over creation of the Spanner adapter and driver.
        +
        +
      • +
      + + + + + +
        +
      • +

        acquire

        +
        @Nonnull
        +public static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message> SpannerAdapter<K,​M> acquire​(@Nonnull
        +                                                                                                                                    K keyInstance,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    M messageInstance,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    com.google.cloud.spanner.DatabaseId defaultDatabase,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    com.google.cloud.spanner.SpannerOptions.Builder baseOptions,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    com.google.common.util.concurrent.ListeningScheduledExecutorService executorService)
        +
        Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId. + +

        This method variant additionally allows the developer to specify a custom + ListeningScheduledExecutorService to use for background operation execution, and a set of + SpannerOptions to use when spawning RPC clients. This executor service is injected directly into the + SpannerDriver and underlying Spanner RPC clients, and is used for both RPC operational execution and + async followup.

        + +

        Variants of this method provide either simpler invocation, or deeper control over interactions with the + Spanner service. See below for alternatives.

        +
        +
        Type Parameters:
        +
        K - Model key structure for which the resulting adapter should be specialized.
        +
        M - Model object structure for which the resulting adapter should be specialized.
        +
        Parameters:
        +
        keyInstance - Generated key Message structure, for which the adapter should be specialized.
        +
        messageInstance - Generated object Message structure, for which the adapter should be specialized.
        +
        defaultDatabase - Default Spanner database to use when interacting with this adapter. This value may be + overridden on an individual operation basis via specifying custom + SpannerDriver.SpannerOperationOptions and descendents.
        +
        baseOptions - Spanner options to use when spawning RPC clients.
        +
        executorService - Executor service to use for primary RPC execution and related followup.
        +
        Returns:
        +
        Spanner adapter instance, specialized to the provided model and key Messages.
        +
        See Also:
        +
        For a simpler version of this method which uses managed driver + settings and a sensible cached threadpool executor., +For a simpler version of this + method which uses managed driver settings., +Full control over creation of the Spanner adapter and driver.
        +
        +
      • +
      + + + + + +
        +
      • +

        acquire

        +
        @Nonnull
        +public static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message> SpannerAdapter<K,​M> acquire​(@Nonnull
        +                                                                                                                                    com.google.cloud.spanner.SpannerOptions.Builder baseOptions,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    com.google.cloud.spanner.DatabaseId defaultDatabase,
        +                                                                                                                                    @Nonnull @GoogleAPIChannel(service=SPANNER)
        +                                                                                                                                    com.google.api.gax.rpc.TransportChannelProvider spannerChannel,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    java.util.Optional<com.google.api.gax.core.CredentialsProvider> credentialsProvider,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    java.util.Optional<com.google.cloud.spanner.SpannerOptions.CallCredentialsProvider> callCredentialProvider,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    com.google.cloud.grpc.GrpcTransportOptions transportOptions,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    com.google.common.util.concurrent.ListeningScheduledExecutorService executorService,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    K keyInstance,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    M messageInstance,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    SpannerDriverSettings driverSettings,
        +                                                                                                                                    @Nonnull
        +                                                                                                                                    java.util.Optional<CacheDriver<K,​M>> cacheDriver)
        +
        Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId. + +

        This method variant additionally allows the developer to specify all custom settings available for the Spanner + driver and adapter.

        + +

        Variants of this method provide simpler invocation, for looser coupling with applications. See below to + consider these alternatives for situations where deep control isn't necessary.

        +
        +
        Type Parameters:
        +
        K - Model key structure for which the resulting adapter should be specialized.
        +
        M - Model object structure for which the resulting adapter should be specialized.
        +
        Parameters:
        +
        baseOptions - Spanner options to use when spawning RPC clients.
        +
        defaultDatabase - Default Spanner database to use when interacting with this adapter. This value may be + overridden on an individual operation basis via specifying custom + SpannerDriver.SpannerOperationOptions and descendents.
        +
        spannerChannel - Transport channel provider to use when spawning RPC connections to Spanner.
        +
        credentialsProvider - Credentials provider to use when authorizing calls to Spanner.
        +
        callCredentialProvider - Call-level credentials provider to use when authorizing calls to Spanner. Optional.
        +
        transportOptions - Transport options to apply when interacting with Spanner services.
        +
        executorService - Executor service to use for primary RPC execution and related followup.
        +
        keyInstance - Generated key Message structure, for which the adapter should be specialized.
        +
        messageInstance - Generated object Message structure, for which the adapter should be specialized.
        +
        driverSettings - Settings to apply to the Spanner driver and adapter itself.
        +
        cacheDriver - Cache engine to use when interacting with the underlying driver.
        +
        Returns:
        +
        Spanner adapter instance, specialized to the provided model and key Messages.
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        public void close()
        +
        +
        Specified by:
        +
        close in interface java.lang.AutoCloseable
        +
        Specified by:
        +
        close in interface java.io.Closeable
        +
        +
      • +
      + + + +
        +
      • +

        codec

        +
        @Nonnull
        +public ModelCodec<Model,​com.google.cloud.spanner.Mutation,​com.google.cloud.spanner.Struct> codec()
        +
        Acquire an instance of the codec used by this adapter. Codecs are either injected/otherwise provided during adapter + construction, or they are specified statically if the adapter depends on a specific codec.
        +
        +
        Specified by:
        +
        codec in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>
        +
        Returns:
        +
        Model codec currently in use by this adapter.
        +
        +
      • +
      + + + +
        +
      • +

        cache

        +
        @Nonnull
        +public java.util.Optional<CacheDriver<Key,​Model>> cache()
        +
        Return the cache driver in use for this particular model adapter. If a cache driver is present, and active/enabled + according to database driver settings, it will be used on read-paths (such as fetching objects by ID).
        +
        +
        Specified by:
        +
        cache in interface ModelAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>
        +
        Returns:
        +
        Cache driver currently in use by this model adapter.
        +
        +
      • +
      + + + +
        +
      • +

        engine

        +
        @Nonnull
        +public DatabaseDriver<Key,​Model,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation> engine()
        +
        Return the lower-level DatabaseDriver powering this adapter. The driver is responsible for communicating + with the actual database or storage service, either via local stubs/emulators or a production API.
        +
        +
        Specified by:
        +
        engine in interface DatabaseAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>
        +
        Specified by:
        +
        engine in interface ModelAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>
        +
        Returns:
        +
        Database driver instance currently in use by this model adapter.
        +
        +
      • +
      + + + +
        +
      • +

        executorService

        +
        @Nonnull
        +public com.google.common.util.concurrent.ListeningScheduledExecutorService executorService()
        +
        Resolve an executor service for use with this persistence driver. Operations will be executed against this as they + are received.
        +
        +
        Specified by:
        +
        executorService in interface ModelAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>
        +
        Specified by:
        +
        executorService in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>
        +
        Returns:
        +
        Scheduled executor service.
        +
        +
      • +
      + + + +
        +
      • +

        spanner

        +
        @Nonnull
        +public com.google.cloud.spanner.Spanner spanner()
        +
        Acquire the Spanner for Java client powering this adapter.
        +
        +
        Returns:
        +
        Spanner client for Java.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/SpannerCodec.html b/docs/java/gust/backend/driver/spanner/SpannerCodec.html new file mode 100644 index 000000000..773995463 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerCodec.html @@ -0,0 +1,516 @@ + + + + + +SpannerCodec + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class SpannerCodec<Model extends com.google.protobuf.Message>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.spanner.SpannerCodec<Model>
    • +
    +
  • +
+
+
    +
  • +
    +
    Type Parameters:
    +
    Model - Typed Message which implements a concrete model object structure, as defined and annotated by + the core Gust annotations.
    +
    +
    +
    All Implemented Interfaces:
    +
    ModelCodec<Model,​com.google.cloud.spanner.Mutation,​com.google.cloud.spanner.Struct>
    +
    +
    +
    @Factory
    +@Immutable
    +@ThreadSafe
    +public final class SpannerCodec<Model extends com.google.protobuf.Message>
    +extends java.lang.Object
    +implements ModelCodec<Model,​com.google.cloud.spanner.Mutation,​com.google.cloud.spanner.Struct>
    +
    Implements a ModelCodec for Spanner types used as intermediaries during database operations. In particular, + Spanner uses Mutation objects to express writes, and yields reads in the form of Struct objects which + each identify a result row. + +

    This codec is implemented with custom serialization via SpannerMutationSerializer, and de-serialization + via SpannerStructDeserializer, which are similarly specialized at compile time to a given Message + generated schema implementation.

    +
    +
    See Also:
    +
    Specialized serialization from objects to Spanner s., +Specialized de-serialization from objects to s.
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      ModelDeserializer<com.google.cloud.spanner.Struct,​Model>deserializer() +
      Acquire an instance of the ModelDeserializer attached to this adapter.
      +
      static <M extends com.google.protobuf.Message>
      SpannerCodec<M>
      forModel​(M instance) +
      Create a Spanner message codec which adapts the provided builder to a Spanner Mutation and back with + default codecs and settings.
      +
      static <M extends com.google.protobuf.Message>
      SpannerCodec<M>
      forModel​(M instance, + SpannerDriverSettings driverSettings) +
      Create a Spanner message codec which adapts the provided builder to a Spanner Mutation and back with + default codecs and custom settings.
      +
      static <M extends com.google.protobuf.Message>
      SpannerCodec<M>
      forModel​(M instance, + ModelSerializer<M,​com.google.cloud.spanner.Mutation> serializer, + ModelDeserializer<com.google.cloud.spanner.Struct,​M> deserializer) +
      Create a Spanner message codec which adapts the provided builder to a Spanner Mutation and back.
      +
      Modelinstance() +
      Retrieve the default instance stored with this codec.
      +
      com.google.cloud.spanner.Mutationserialize​(com.google.cloud.spanner.Mutation.WriteBuilder initial, + Model model) +
      Specialized entrypoint for converting model instances into Mutation instances so they may be written to + Spanner.
      +
      ModelSerializer<Model,​com.google.cloud.spanner.Mutation>serializer() +
      Acquire an instance of the ModelSerializer attached to this adapter.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      + +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + + + +
        +
      • +

        forModel

        +
        @Context
        +@Nonnull
        +public static <M extends com.google.protobuf.Message> SpannerCodec<M> forModel​(@Nonnull
        +                                                                               M instance)
        +
        Create a Spanner message codec which adapts the provided builder to a Spanner Mutation and back with + default codecs and settings.
        +
        +
        Type Parameters:
        +
        M - Model type for which we will construct or otherwise resolve a collapsed message codec.
        +
        Parameters:
        +
        instance - Default instance for the model we will be serializing/deserializing.
        +
        Returns:
        +
        Mutation codec bound to the provided message type.
        +
        +
      • +
      + + + + + +
        +
      • +

        forModel

        +
        @Context
        +@Nonnull
        +public static <M extends com.google.protobuf.Message> SpannerCodec<M> forModel​(@Nonnull
        +                                                                               M instance,
        +                                                                               @Nonnull
        +                                                                               SpannerDriverSettings driverSettings)
        +
        Create a Spanner message codec which adapts the provided builder to a Spanner Mutation and back with + default codecs and custom settings.
        +
        +
        Type Parameters:
        +
        M - Model type for which we will construct or otherwise resolve a collapsed message codec.
        +
        Parameters:
        +
        instance - Default instance for the model we will be serializing/deserializing.
        +
        driverSettings - Settings for the Spanner driver itself.
        +
        Returns:
        +
        Mutation codec bound to the provided message type.
        +
        +
      • +
      + + + + + +
        +
      • +

        forModel

        +
        @Context
        +@Nonnull
        +public static <M extends com.google.protobuf.Message> SpannerCodec<M> forModel​(@Nonnull
        +                                                                               M instance,
        +                                                                               @Nonnull
        +                                                                               ModelSerializer<M,​com.google.cloud.spanner.Mutation> serializer,
        +                                                                               @Nonnull
        +                                                                               ModelDeserializer<com.google.cloud.spanner.Struct,​M> deserializer)
        +
        Create a Spanner message codec which adapts the provided builder to a Spanner Mutation and back.
        +
        +
        Type Parameters:
        +
        M - Model type for which we will construct or otherwise resolve a collapsed message codec.
        +
        Parameters:
        +
        instance - Default instance for the model we will be serializing/deserializing.
        +
        serializer - Custom serializer to use for this codec.
        +
        deserializer - Custom deserializer to use for this codec.
        +
        Returns:
        +
        Mutation codec bound to the provided message type.
        +
        +
      • +
      + + + + + +
        +
      • +

        serialize

        +
        @Nonnull
        +public com.google.cloud.spanner.Mutation serialize​(@Nonnull
        +                                                   com.google.cloud.spanner.Mutation.WriteBuilder initial,
        +                                                   @Nonnull
        +                                                   Model model)
        +                                            throws java.io.IOException
        +
        Specialized entrypoint for converting model instances into Mutation instances so they may be written to + Spanner.
        +
        +
        Parameters:
        +
        initial - Initial empty mutation to populate with the serialized message result.
        +
        model - Model which we intend to store in Spanner.
        +
        Returns:
        +
        Initialized and serialized mutation.
        +
        Throws:
        +
        java.io.IOException - If some serialization error occurs while processing the model.
        +
        +
      • +
      + + + +
        +
      • +

        instance

        +
        @Nonnull
        +public Model instance()
        +
        Description copied from interface: ModelCodec
        +
        Retrieve the default instance stored with this codec. Each Message with a paired ModelCodec retains + a reference to its corresponding default instance.
        +
        +
        Specified by:
        +
        instance in interface ModelCodec<Model extends com.google.protobuf.Message,​com.google.cloud.spanner.Mutation,​com.google.cloud.spanner.Struct>
        +
        Returns:
        +
        Default model instance.
        +
        +
      • +
      + + + + + + + + +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/SpannerDriver.html b/docs/java/gust/backend/driver/spanner/SpannerDriver.html new file mode 100644 index 000000000..60666bfd8 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerDriver.html @@ -0,0 +1,542 @@ + + + + + +SpannerDriver + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class SpannerDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.spanner.SpannerDriver<Key,​Model>
    • +
    +
  • +
+
+
    +
  • +
    +
    Type Parameters:
    +
    Key - Typed Message which implements a concrete model key structure, as defined and annotated by the + core Gust annotations.
    +
    Model - Typed Message which implements a concrete model object structure, as defined and annotated by + the core Gust annotations.
    +
    +
    +
    All Implemented Interfaces:
    +
    DatabaseDriver<Key,​Model,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>, PersistenceDriver<Key,​Model,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>
    +
    +
    +
    @Immutable
    +@ThreadSafe
    +public final class SpannerDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message>
    +extends java.lang.Object
    +implements DatabaseDriver<Key,​Model,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>
    +
    Provides a DatabaseDriver implementation which enables seamless Protocol Buffer persistence with Google Cloud + Spanner, for any Message-derived (schema-driven) business model in a given Gust app's ecosystem. + +

    Model storage can be deeply customized on a per-model basis, thanks to the built-in proto annotations available + in gust.core. The Spanner adapter supports basic persistence (i.e. as a regular +

    PersistenceDriver
    ), but also supports generic, object index-style queries.

    + +

    See the main SpannerAdapter for a full description of supported application-level functionality.

    +
    +
    See Also:
    +
    Main typed adapter interface for Spanner., +Adapter instance manager and factory., +Driver-level settings specific to Spanner., +Similar driver implementation, built on top of Cloud Firestore, + which itself is implemented on top of Cloud Spanner.
    +
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        executorService

        +
        @Nonnull
        +public com.google.common.util.concurrent.ListeningScheduledExecutorService executorService()
        +
        Description copied from interface: PersistenceDriver
        +
        Resolve an executor service for use with this persistence driver. Operations will be executed against this as they + are received.
        +
        +
        Specified by:
        +
        executorService in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>
        +
        Returns:
        +
        Scheduled executor service.
        +
        +
      • +
      + + + +
        +
      • +

        codec

        +
        @Nonnull
        +public ModelCodec<Model,​com.google.cloud.spanner.Mutation,​com.google.cloud.spanner.Struct> codec()
        +
        Description copied from interface: PersistenceDriver
        +
        Acquire an instance of the codec used by this adapter. Codecs are either injected/otherwise provided during adapter + construction, or they are specified statically if the adapter depends on a specific codec.
        +
        +
        Specified by:
        +
        codec in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>
        +
        Returns:
        +
        Model codec currently in use by this adapter.
        +
        +
      • +
      + + + + + + + + + + + +
        +
      • +

        persist

        +
        @Nonnull
        +public ReactiveFuture<Modelpersist​(@Nullable
        +                                     Key key,
        +                                     @Nonnull
        +                                     Model model,
        +                                     @Nonnull
        +                                     WriteOptions options)
        +
        Description copied from interface: PersistenceDriver
        +
        Low-level record persistence method. Effectively called by all other create/put variants. Asynchronously write a + data model instance to storage, which will populate the provided ReactiveFuture value. + +

        Optionally, a key may be provided as a nominated value to the storage engine. Whether the engine accepts + nominated keys is up to the implementation. In all cases, the engine must return the key used to store and address + the value henceforth. If the engine does support nominated keys, it must operate in an idempotent + manner with regard to those keys. In other words, repeated calls to create the same entity with the same key will + not cause spurious side-effects - only one record will be created, with the remaining calls being rejected by the + underlying engine.

        + +

        All futures emitted via the persistence framework (and Gust writ-large) are ListenableFuture-compliant + implementations under the hood, but ReactiveFuture allows a model-layer result to be used as a + Future, or a one-item reactive Publisher.

        + +

        This method additionally enables specification of custom WriteOptions, which are applied on a per- + operation basis to override global defaults.

        + +

        Exceptions: Instead of throwing a PersistenceException as other methods do, this operation will + emit the exception over the Future channel instead, or raise the exception in the event + Future.get() is called to surface it in the invoking (or dependent) code.

        +
        +
        Specified by:
        +
        persist in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>
        +
        Parameters:
        +
        key - Key nominated by invoking code for storing this record. If no key is provided, the underlying storage + engine is expected to allocate one. Where unsupported, PersistenceException will be thrown.
        +
        model - Model to store at the specified key, if provided.
        +
        options - Options to apply to this persist operation.
        +
        Returns:
        +
        Reactive future, which resolves to the key where the provided model is now stored. In no case should this + method return null. Instead, PersistenceException will be thrown.
        +
        +
      • +
      + + + + + +
        +
      • +

        delete

        +
        @Nonnull
        +public ReactiveFuture<Keydelete​(@Nonnull
        +                                  Key key,
        +                                  @Nonnull
        +                                  DeleteOptions baseOptions)
        +
        Description copied from interface: PersistenceDriver
        +
        Low-level record delete method. Effectively called by all other delete variants. Asynchronously and permanently + erase an existing data model instance from storage, addressed by its key unique key or ID. + +

        If no key or ID field, or value, may be located, an error is raised (see below for details). This operation is + expected to operate in an idempotent manner (i.e. repeated calls with identical parameters do not yield + different side effects). Calls referring to an already-deleted entity should silently succeed.

        + +

        All futures emitted via the persistence framework (and Gust writ-large) are ListenableFuture-compliant + implementations under the hood, but ReactiveFuture allows a model-layer result to be used as a + Future, or a one-item reactive Publisher.

        + +

        This method additionally enables specification of custom DeleteOptions, which are applied on a per- + operation basis to override global defaults.

        + +

        Exceptions: Instead of throwing a PersistenceException as other methods do, this operation will + emit the exception over the Future channel instead, or raise the exception in the event + Future.get() is called to surface it in the invoking (or dependent) code.

        +
        +
        Specified by:
        +
        delete in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​com.google.cloud.spanner.Struct,​com.google.cloud.spanner.Mutation>
        +
        Parameters:
        +
        key - Unique key referring to the record in storage that should be deleted.
        +
        baseOptions - Options to apply to this specific delete operation.
        +
        Returns:
        +
        Future value, which resolves to the deleted record's key when the operation completes.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/SpannerDriverSettings.DefaultSettings.html b/docs/java/gust/backend/driver/spanner/SpannerDriverSettings.DefaultSettings.html new file mode 100644 index 000000000..b86991632 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerDriverSettings.DefaultSettings.html @@ -0,0 +1,334 @@ + + + + + +SpannerDriverSettings.DefaultSettings + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class SpannerDriverSettings.DefaultSettings

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.spanner.SpannerDriverSettings.DefaultSettings
    • +
    +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Fields 
      Modifier and TypeFieldDescription
      static java.lang.BooleanDEFAULT_CAPITALIZED_NAMES +
      Default value: Whether to generate Spanner style names with initial capitals.
      +
      static java.lang.BooleanDEFAULT_CHECK_EXPECTED_TYPES +
      Default value: Whether to perform runtime deserialization checks (`true`, default) or not (`false`).
      +
      static java.lang.BooleanDEFAULT_ENUMS_AS_NUMBERS +
      Default value: Whether to treat enumeration instances as numbers (`true`) or strings (`false`, default).
      +
      static java.lang.BooleanDEFAULT_PRESERVE_FIELD_NAMES +
      Default value: Whether to preserve proto field names (`true`) or use JSON names (`false`, default).
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        DEFAULT_PRESERVE_FIELD_NAMES

        +
        public static final java.lang.Boolean DEFAULT_PRESERVE_FIELD_NAMES
        +
        Default value: Whether to preserve proto field names (`true`) or use JSON names (`false`, default).
        +
      • +
      + + + +
        +
      • +

        DEFAULT_CAPITALIZED_NAMES

        +
        public static final java.lang.Boolean DEFAULT_CAPITALIZED_NAMES
        +
        Default value: Whether to generate Spanner style names with initial capitals.
        +
      • +
      + + + +
        +
      • +

        DEFAULT_ENUMS_AS_NUMBERS

        +
        public static final java.lang.Boolean DEFAULT_ENUMS_AS_NUMBERS
        +
        Default value: Whether to treat enumeration instances as numbers (`true`) or strings (`false`, default).
        +
      • +
      + + + +
        +
      • +

        DEFAULT_CHECK_EXPECTED_TYPES

        +
        public static final java.lang.Boolean DEFAULT_CHECK_EXPECTED_TYPES
        +
        Default value: Whether to perform runtime deserialization checks (`true`, default) or not (`false`).
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/SpannerDriverSettings.html b/docs/java/gust/backend/driver/spanner/SpannerDriverSettings.html new file mode 100644 index 000000000..1bf44c493 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerDriverSettings.html @@ -0,0 +1,410 @@ + + + + + +SpannerDriverSettings + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface SpannerDriverSettings

+
+
+
+
    +
  • +
    +
    @Immutable
    +@ThreadSafe
    +public interface SpannerDriverSettings
    +
    Specifies settings for the Spanner driver and data storage implementation.
    +
  • +
+
+
+ +
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        preserveFieldNames

        +
        @Nonnull
        +default java.lang.Boolean preserveFieldNames()
        +
        +
        Returns:
        +
        Whether to preserve proto field names (`true`) or use JSON names (defaults to `false`).
        +
        +
      • +
      + + + +
        +
      • +

        defaultCapitalizedNames

        +
        @Nonnull
        +default java.lang.Boolean defaultCapitalizedNames()
        +
        +
        Returns:
        +
        Whether to generate Spanner style names with initial capitals (i.e. `Name` instead of `name`).
        +
        +
      • +
      + + + +
        +
      • +

        enumsAsNumbers

        +
        @Nonnull
        +default java.lang.Boolean enumsAsNumbers()
        +
        +
        Returns:
        +
        Whether to treat enumeration instances as numbers (`true`) or strings (defaults to `false`).
        +
        +
      • +
      + + + +
        +
      • +

        checkExpectedTypes

        +
        @Nonnull
        +default java.lang.Boolean checkExpectedTypes()
        +
        +
        Returns:
        +
        Whether to perform runtime deserialization checks (defaults to `true`).
        +
        +
      • +
      + + + +
        +
      • +

        defaultColumnSize

        +
        default int defaultColumnSize()
        +
        +
        Returns:
        +
        Default size to use for `STRING` or `BYTES` columns that don't otherwise specify a size.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.Builder.html b/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.Builder.html new file mode 100644 index 000000000..b3e3f77bb --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.Builder.html @@ -0,0 +1,615 @@ + + + + + +SpannerGeneratedDDL.Builder + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class SpannerGeneratedDDL.Builder

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.spanner.SpannerGeneratedDDL.Builder
    • +
    +
  • +
+
+
    +
  • +
    +
    Enclosing class:
    +
    SpannerGeneratedDDL
    +
    +
    +
    public static final class SpannerGeneratedDDL.Builder
    +extends java.lang.Object
    +
    Build properties for a generated Spanner table DDL statement, based on a given model instance as a base for + configuring the table name (via annotations / calculated defaults) and set of typed Spanner value columns. + +

    To build the actual DDL statement, fill out the builder, build it, and then ask the resulting object for the + DDL as a string.

    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        build

        +
        @Nonnull
        +public SpannerGeneratedDDL build()
        +
        Collapse the builder into an immutable DDL statement container
        +
        +
        Returns:
        +
        Immutable DDL statement container.
        +
        +
      • +
      + + + +
        +
      • +

        getModel

        +
        @Nonnull
        +public com.google.protobuf.Descriptors.Descriptor getModel()
        +
        +
        Returns:
        +
        Model descriptor this builder wraps.
        +
        +
      • +
      + + + + + + + +
        +
      • +

        getTableName

        +
        @Nonnull
        +public java.lang.String getTableName()
        +
        +
        Returns:
        +
        Generated or resolved Spanner table name.
        +
        +
      • +
      + + + +
        +
      • +

        getPrimaryKey

        +
        @Nonnull
        +public java.lang.String getPrimaryKey()
        +
        +
        Returns:
        +
        Primary key column for this model/table.
        +
        +
      • +
      + + + +
        +
      • +

        getColumns

        +
        @Nonnull
        +public java.util.List<gust.backend.driver.spanner.SpannerGeneratedDDL.ColumnSpec> getColumns()
        +
        +
        Returns:
        +
        Set of generated columns for this model in Spanner.
        +
        +
      • +
      + + + + + + + + + + + +
        +
      • +

        getOptimizerVersion

        +
        @Nonnull
        +public java.util.Optional<java.lang.Integer> getOptimizerVersion()
        +
        +
        Returns:
        +
        Optimizer version to set for this table.
        +
        +
      • +
      + + + +
        +
      • +

        getVersionRetentionPeriod

        +
        @Nonnull
        +public java.util.Optional<java.lang.String> getVersionRetentionPeriod()
        +
        +
        Returns:
        +
        Data versioning retention period to set for this table.
        +
        +
      • +
      + + + + + + + + + + + +
        +
      • +

        setTableConstraints

        +
        @Nonnull
        +public SpannerGeneratedDDL.Builder setTableConstraints​(@Nonnull
        +                                                       java.util.Optional<java.util.List<SpannerGeneratedDDL.TableConstraint>> tableConstraints)
        +
        Set, or clear, the set of table constraints added to this table.
        +
        +
        Parameters:
        +
        tableConstraints - Desired table constraints to set or clear, as applicable.
        +
        Returns:
        +
        Self, for chained calls to the builder.
        +
        +
      • +
      + + + +
        +
      • +

        setOptimizerVersion

        +
        @Nonnull
        +public SpannerGeneratedDDL.Builder setOptimizerVersion​(@Nonnull
        +                                                       java.util.Optional<java.lang.Integer> optimizerVersion)
        +
        Set, or clear, the optimizer version to apply when creating this table.
        +
        +
        Parameters:
        +
        optimizerVersion - Desired optimizer version to apply, as applicable.
        +
        Returns:
        +
        Self, for chained calls to the builder.
        +
        +
      • +
      + + + +
        +
      • +

        setVersionRetentionPeriod

        +
        @Nonnull
        +public SpannerGeneratedDDL.Builder setVersionRetentionPeriod​(@Nonnull
        +                                                             java.util.Optional<java.lang.String> versionRetentionPeriod)
        +
        Set, or clear, the data versioning retention period for this table.
        +
        +
        Parameters:
        +
        versionRetentionPeriod - Desired data versioning retention period, as applicable.
        +
        Returns:
        +
        Self, for chained calls to the builder.
        +
        +
      • +
      + + + + +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.InterleaveTarget.html b/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.InterleaveTarget.html new file mode 100644 index 000000000..ec9c0b110 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.InterleaveTarget.html @@ -0,0 +1,348 @@ + + + + + +SpannerGeneratedDDL.InterleaveTarget + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class SpannerGeneratedDDL.InterleaveTarget

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.spanner.SpannerGeneratedDDL.InterleaveTarget
    • +
    +
  • +
+
+ +
+
+ +
+
+
    +
  • + +
    + +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.PropagatedAction.html b/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.PropagatedAction.html new file mode 100644 index 000000000..fd5074419 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.PropagatedAction.html @@ -0,0 +1,392 @@ + + + + + +SpannerGeneratedDDL.PropagatedAction + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Enum SpannerGeneratedDDL.PropagatedAction

+
+
+ +
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      CASCADE +
      Cascade changes on delete or update.
      +
      NO_ACTION +
      Take no action.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static SpannerGeneratedDDL.PropagatedActionvalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static SpannerGeneratedDDL.PropagatedAction[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static SpannerGeneratedDDL.PropagatedAction[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (SpannerGeneratedDDL.PropagatedAction c : SpannerGeneratedDDL.PropagatedAction.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static SpannerGeneratedDDL.PropagatedAction valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.RenderableStatement.html b/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.RenderableStatement.html new file mode 100644 index 000000000..bec9012ed --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.RenderableStatement.html @@ -0,0 +1,269 @@ + + + + + +SpannerGeneratedDDL.RenderableStatement + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface SpannerGeneratedDDL.RenderableStatement

+
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods 
      Modifier and TypeMethodDescription
      java.lang.StringBuilderrender() +
      Render this statement into a String buffer.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        render

        +
        @Nonnull
        +java.lang.StringBuilder render()
        +
        Render this statement into a String buffer.
        +
        +
        Returns:
        +
        Rendered statement.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.SortDirection.html b/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.SortDirection.html new file mode 100644 index 000000000..7042aae15 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.SortDirection.html @@ -0,0 +1,392 @@ + + + + + +SpannerGeneratedDDL.SortDirection + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Enum SpannerGeneratedDDL.SortDirection

+
+
+ +
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      ASC +
      Sort values in the column in ascending order.
      +
      DESC +
      Sort values in the column in descending order.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static SpannerGeneratedDDL.SortDirectionvalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static SpannerGeneratedDDL.SortDirection[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static SpannerGeneratedDDL.SortDirection[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (SpannerGeneratedDDL.SortDirection c : SpannerGeneratedDDL.SortDirection.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static SpannerGeneratedDDL.SortDirection valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.TableConstraint.html b/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.TableConstraint.html new file mode 100644 index 000000000..9a24d8561 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.TableConstraint.html @@ -0,0 +1,319 @@ + + + + + +SpannerGeneratedDDL.TableConstraint + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class SpannerGeneratedDDL.TableConstraint

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.spanner.SpannerGeneratedDDL.TableConstraint
    • +
    +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static SpannerGeneratedDDL.TableConstraintnamed​(java.lang.String name, + java.lang.String expression) +
      Spawn a table constraint at the provided name, enforcing the provided expression.
      +
      java.lang.StringBuilderrender() +
      Render this statement into a String buffer.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        named

        +
        @Nonnull
        +public static SpannerGeneratedDDL.TableConstraint named​(@Nonnull
        +                                                        java.lang.String name,
        +                                                        @Nonnull
        +                                                        java.lang.String expression)
        +
        Spawn a table constraint at the provided name, enforcing the provided expression.
        +
        +
        Parameters:
        +
        name - Name of the constraint to enclose in the DDL statement.
        +
        expression - Expression to enforce as a table constraint.
        +
        Returns:
        +
        Table constraint specification.
        +
        +
      • +
      + + + + +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.html b/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.html new file mode 100644 index 000000000..f739388e7 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerGeneratedDDL.html @@ -0,0 +1,538 @@ + + + + + +SpannerGeneratedDDL + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class SpannerGeneratedDDL

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.spanner.SpannerGeneratedDDL
    • +
    +
  • +
+
+
    +
  • +
    +
    @Immutable
    +@ThreadSafe
    +public final class SpannerGeneratedDDL
    +extends java.lang.Object
    +
    Container for generated schema-driven Spanner DDL.
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static SpannerGeneratedDDL.BuildergenerateTableDDL​(com.google.protobuf.Descriptors.Descriptor model, + SpannerDriverSettings settings) +
      Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing + that model's properties.
      +
      static SpannerGeneratedDDL.BuildergenerateTableDDL​(com.google.protobuf.Message instance) +
      Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing + that model's properties.
      +
      static SpannerGeneratedDDL.BuildergenerateTableDDL​(com.google.protobuf.Message instance, + java.util.Optional<SpannerDriverSettings> settings) +
      Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing + that model's properties.
      +
      java.util.List<gust.backend.driver.spanner.SpannerGeneratedDDL.ColumnSpec>getColumns() 
      java.lang.StringBuildergetGeneratedStatement() 
      com.google.protobuf.Descriptors.DescriptorgetModel() 
      java.lang.StringgetTableName() 
      static java.util.List<gust.backend.driver.spanner.SpannerGeneratedDDL.ColumnSpec>resolveDefaultColumns​(com.google.protobuf.Descriptors.Descriptor model, + SpannerDriverSettings settings) +
      Resolve the default calculated set of Spanner columns for a given model structure.
      +
      java.lang.StringtoString() 
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        generateTableDDL

        +
        @Nonnull
        +public static SpannerGeneratedDDL.Builder generateTableDDL​(@Nonnull
        +                                                           com.google.protobuf.Message instance)
        +
        Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing + that model's properties. This method variant operates from a full model instance. + +

        This method offers no ability to control driver settings. See below if you need alternatives.

        +
        +
        Parameters:
        +
        instance - Model instance to generate a table statement for.
        +
        Returns:
        +
        Generated DDL statement object.
        +
        See Also:
        +
        For control over driver settings, optionally.
        +
        +
      • +
      + + + +
        +
      • +

        generateTableDDL

        +
        @Nonnull
        +public static SpannerGeneratedDDL.Builder generateTableDDL​(@Nonnull
        +                                                           com.google.protobuf.Message instance,
        +                                                           @Nonnull
        +                                                           java.util.Optional<SpannerDriverSettings> settings)
        +
        Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing + that model's properties. This method variant operates from a full model instance.
        +
        +
        Parameters:
        +
        instance - Model instance to generate a table statement for.
        +
        settings - Settings to employ for the driver. These must align at runtime.
        +
        Returns:
        +
        Generated DDL statement object.
        +
        +
      • +
      + + + +
        +
      • +

        generateTableDDL

        +
        @Nonnull
        +public static SpannerGeneratedDDL.Builder generateTableDDL​(@Nonnull
        +                                                           com.google.protobuf.Descriptors.Descriptor model,
        +                                                           @Nonnull
        +                                                           SpannerDriverSettings settings)
        +
        Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing + that model's properties.
        +
        +
        Parameters:
        +
        model - Model schema to generate a table statement for.
        +
        Returns:
        +
        Generated DDL statement object.
        +
        +
      • +
      + + + +
        +
      • +

        resolveDefaultColumns

        +
        @Nonnull
        +public static java.util.List<gust.backend.driver.spanner.SpannerGeneratedDDL.ColumnSpec> resolveDefaultColumns​(@Nonnull
        +                                                                                                               com.google.protobuf.Descriptors.Descriptor model,
        +                                                                                                               @Nonnull
        +                                                                                                               SpannerDriverSettings settings)
        +
        Resolve the default calculated set of Spanner columns for a given model structure.
        +
        +
        Parameters:
        +
        model - Model to traverse and generate columns for.
        +
        Returns:
        +
        Set of generated and type-resolved columns.
        +
        +
      • +
      + + + +
        +
      • +

        getModel

        +
        @Nonnull
        +public com.google.protobuf.Descriptors.Descriptor getModel()
        +
        +
        Returns:
        +
        Model for which this object generates a table create statement.
        +
        +
      • +
      + + + +
        +
      • +

        getTableName

        +
        @Nonnull
        +public java.lang.String getTableName()
        +
        +
        Returns:
        +
        Resolved name of the table to be created.
        +
        +
      • +
      + + + +
        +
      • +

        getColumns

        +
        @Nonnull
        +public java.util.List<gust.backend.driver.spanner.SpannerGeneratedDDL.ColumnSpec> getColumns()
        +
        +
        Returns:
        +
        Resolved set of Spanner columns.
        +
        +
      • +
      + + + +
        +
      • +

        getGeneratedStatement

        +
        @Nonnull
        +public java.lang.StringBuilder getGeneratedStatement()
        +
        +
        Returns:
        +
        Rendered generated DDL statement.
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/SpannerManager.Builder.html b/docs/java/gust/backend/driver/spanner/SpannerManager.Builder.html new file mode 100644 index 000000000..8755cb690 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerManager.Builder.html @@ -0,0 +1,489 @@ + + + + + +SpannerManager.Builder + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class SpannerManager.Builder

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.spanner.SpannerManager.Builder
    • +
    +
  • +
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getDatabase

        +
        @Nonnull
        +public com.google.cloud.spanner.DatabaseId getDatabase()
        +
        +
        Returns:
        +
        Spanner database which the target manager will be bound to.
        +
        +
      • +
      + + + +
        +
      • +

        getCache

        +
        @Nonnull
        +public java.util.Optional<CacheDriver<com.google.protobuf.Message,​com.google.protobuf.Message>> getCache()
        +
        +
        Returns:
        +
        Cache adapter, if any, to apply when acquiring adapters/drivers through the target manager.
        +
        +
      • +
      + + + +
        +
      • +

        getSettings

        +
        @Nonnull
        +public java.util.Optional<SpannerDriverSettingsgetSettings()
        +
        +
        Returns:
        +
        Custom driver settings to apply when acquiring adapters/drivers through the target manager.
        +
        +
      • +
      + + + +
        +
      • +

        getOptions

        +
        @Nonnull
        +public java.util.Optional<com.google.cloud.spanner.SpannerOptions.Builder> getOptions()
        +
        +
        Returns:
        +
        Base set of Spanner options to apply when spawning Spanner clients from this manager.
        +
        +
      • +
      + + + +
        +
      • +

        getExecutor

        +
        @Nonnull
        +public java.util.Optional<com.google.common.util.concurrent.ListeningScheduledExecutorService> getExecutor()
        +
        +
        Returns:
        +
        Custom executor to apply when acquiring adapters/drivers through the target manager.
        +
        +
      • +
      + + + +
        +
      • +

        setCache

        +
        @Nonnull
        +public SpannerManager.Builder setCache​(@Nonnull
        +                                       java.util.Optional<CacheDriver<com.google.protobuf.Message,​com.google.protobuf.Message>> cache)
        +
        Set the cache for the target configured Spanner manager.
        +
        +
        Parameters:
        +
        cache - Cache to employ, or none if Optional.empty().
        +
        Returns:
        +
        Self, for chainability.
        +
        +
      • +
      + + + +
        +
      • +

        setSettings

        +
        @Nonnull
        +public SpannerManager.Builder setSettings​(@Nonnull
        +                                          java.util.Optional<SpannerDriverSettings> settings)
        +
        Set the main driver settings to use when spawning adapters in the target configured Spanner manager.
        +
        +
        Parameters:
        +
        settings - Driver settings to apply.
        +
        Returns:
        +
        Self, for chainability.
        +
        +
      • +
      + + + +
        +
      • +

        setOptions

        +
        @Nonnull
        +public SpannerManager.Builder setOptions​(@Nonnull
        +                                         java.util.Optional<com.google.cloud.spanner.SpannerOptions.Builder> options)
        +
        Set the base package of Spanner client options to use when spawning new clients via the target configured + Spanner manager.
        +
        +
        Parameters:
        +
        options - Spanner client options to apply, or none if Optional.empty().
        +
        Returns:
        +
        Self, for chainability.
        +
        +
      • +
      + + + +
        +
      • +

        setExecutor

        +
        @Nonnull
        +public SpannerManager.Builder setExecutor​(@Nonnull
        +                                          java.util.Optional<com.google.common.util.concurrent.ListeningScheduledExecutorService> executor)
        +
        Set the executor used by adapters and drivers spawned by this manager.
        +
        +
        Parameters:
        +
        executor - Executor to use when spawning adapters and drivers.
        +
        Returns:
        +
        Self, for chainability.
        +
        +
      • +
      + + + + +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/SpannerManager.ConfiguredSpannerManager.html b/docs/java/gust/backend/driver/spanner/SpannerManager.ConfiguredSpannerManager.html new file mode 100644 index 000000000..137858b9d --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerManager.ConfiguredSpannerManager.html @@ -0,0 +1,474 @@ + + + + + +SpannerManager.ConfiguredSpannerManager + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class SpannerManager.ConfiguredSpannerManager

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.spanner.SpannerManager.ConfiguredSpannerManager
    • +
    +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      <Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message>
      SpannerAdapter<Key,​Model>
      adapter​(Key keyInstance, + Model modelInstance) +
      Acquire a typed adapter instance specialized to the provided key and model types, which should derive from + schema-driven Message classes.
      +
      java.util.Collection<SpannerAdapter>allAdapters() +
      Returns the full set of known configured Spanner managers, JVM-wide.
      +
      voidclose() +
      Close all active Spanner connections tracked or controlled by this configured manager.
      +
      SpannerAdapter<com.google.protobuf.Message,​com.google.protobuf.Message>generic() +
      Acquire a generic adapter instance designed to work with all Message-inheriting model types.
      +
      java.util.Optional<CacheDriver<com.google.protobuf.Message,​com.google.protobuf.Message>>getCache() 
      booleangetClosed() 
      com.google.cloud.spanner.DatabaseIdgetDatabase() 
      SpannerDriverSettingsgetSettings() 
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        generic

        +
        @Factory
        +@Nonnull
        +public SpannerAdapter<com.google.protobuf.Message,​com.google.protobuf.Message> generic()
        +
        Acquire a generic adapter instance designed to work with all Message-inheriting model types. + +

        Adapter instances and backing drivers acquired via this route are not guaranteed to be new, which in most + a performance benefit with negligible costs. Since adapters and drivers are required to be threadsafe, they + can be re-used safely with no internal state involved.

        +
        +
        Returns:
        +
        Generic Spanner adapter instance.
        +
        See Also:
        +
        adapter(Message, Message)
        +
        +
      • +
      + + + + + +
        +
      • +

        adapter

        +
        @Factory
        +@Nonnull
        +public <Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message> SpannerAdapter<Key,​Model> adapter​(@Nonnull
        +                                                                                                                                         Key keyInstance,
        +                                                                                                                                         @Nonnull
        +                                                                                                                                         Model modelInstance)
        +
        Acquire a typed adapter instance specialized to the provided key and model types, which should derive from + schema-driven Message classes. + +

        Adapters and backing drivers acquired via this route are not guaranteed to be new, which in most cases is + a performance benefit with negligible costs. Since adapters and drivers are required to be threadsafe, they + can be re-used safely with no internal state involved.

        + +

        Alternatively, drivers/adapters can also be acquired directly, via methods like + SpannerAdapter.acquire(Message, Message, DatabaseId) and friends.

        +
        +
        Type Parameters:
        +
        Key - Key type to which the adapter will be specialized.
        +
        Model - Model type to which the adapter will be specialized.
        +
        Parameters:
        +
        keyInstance - Model key instance for which a specialized adapter should be returned.
        +
        modelInstance - Model object instance for which a specialized adapter should be returned.
        +
        Returns:
        +
        New or recycled model adapter instance for the provided key and model types.
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If the provided key or model instance is not duly marked as a key.
        +
        java.lang.IllegalStateException - If the provided key or model instance is not duly marked with a table name.
        +
        +
      • +
      + + + +
        +
      • +

        getDatabase

        +
        @Nonnull
        +public com.google.cloud.spanner.DatabaseId getDatabase()
        +
        +
        Returns:
        +
        Database bound to this manager.
        +
        +
      • +
      + + + +
        +
      • +

        getClosed

        +
        public boolean getClosed()
        +
        +
        Returns:
        +
        Closed/open state of this manager.
        +
        +
      • +
      + + + +
        +
      • +

        allAdapters

        +
        @Nonnull
        +public java.util.Collection<SpannerAdapterallAdapters()
        +
        Returns the full set of known configured Spanner managers, JVM-wide. This is mostly useful as a utility to + shut down connections globally when needed (for instance, during testing).
        +
        +
        Returns:
        +
        Unmodifiable list of weak references to all known-active managers.
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        public void close()
        +
        Close all active Spanner connections tracked or controlled by this configured manager.
        +
        +
        Specified by:
        +
        close in interface java.lang.AutoCloseable
        +
        Specified by:
        +
        close in interface java.io.Closeable
        +
        Throws:
        +
        java.lang.RuntimeException - If the underlying connections raise IO exceptions.
        +
        +
      • +
      + + + + + + + +
        +
      • +

        getCache

        +
        @Nonnull
        +public java.util.Optional<CacheDriver<com.google.protobuf.Message,​com.google.protobuf.Message>> getCache()
        +
        +
        Returns:
        +
        Cache applied to reads, if any.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/SpannerManager.html b/docs/java/gust/backend/driver/spanner/SpannerManager.html new file mode 100644 index 000000000..9aaabe651 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerManager.html @@ -0,0 +1,411 @@ + + + + + +SpannerManager + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class SpannerManager

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.spanner.SpannerManager
    • +
    +
  • +
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        allManagers

        +
        @Nonnull
        +public static java.util.Collection<java.lang.ref.WeakReference<SpannerManager.ConfiguredSpannerManager>> allManagers()
        +
        Returns the full set of known configured Spanner managers, JVM-wide. This is mostly useful as a utility to shut + down connections globally when needed (for instance, during testing).
        +
        +
        Returns:
        +
        Unmodifiable list of weak references to all known-active managers.
        +
        +
      • +
      + + + +
        +
      • +

        acquire

        +
        @Factory
        +@Nonnull
        +public static SpannerManager acquire()
        +
        Acquire a singleton instance of the Spanner manager, which can be used safely across threads to interact with + Google Cloud Spanner, driven by Message-generated models. + +

        The manager can then be used to request instances of SpannerAdapter specialized to a given model. + Adapter instances acquired in this way are not guaranteed to be new, and are safe to use across threads.

        +
        +
        Returns:
        +
        Singleton instance of the Spanner manager.
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        public void close()
        +
        Close all active Spanner connections tracked or controlled by this manager.
        +
        +
        Specified by:
        +
        close in interface java.lang.AutoCloseable
        +
        Specified by:
        +
        close in interface java.io.Closeable
        +
        Throws:
        +
        java.lang.RuntimeException - If the underlying connections raise IO exceptions.
        +
        +
      • +
      + + + +
        +
      • +

        configureForDatabase

        +
        @Nonnull
        +public SpannerManager.Builder configureForDatabase​(@Nonnull
        +                                                   com.google.cloud.spanner.DatabaseId database)
        +
        Configure a vanilla Spanner manager instance for a given database.
        +
        +
        Parameters:
        +
        database - Spanner database.
        +
        Returns:
        +
        Spanner manager builder.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/SpannerMutationSerializer.html b/docs/java/gust/backend/driver/spanner/SpannerMutationSerializer.html new file mode 100644 index 000000000..b7df64180 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerMutationSerializer.html @@ -0,0 +1,322 @@ + + + + + +SpannerMutationSerializer + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class SpannerMutationSerializer<Model extends com.google.protobuf.Message>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.spanner.SpannerMutationSerializer<Model>
    • +
    +
  • +
+
+
    +
  • +
    +
    Type Parameters:
    +
    Model - Typed Message which implements a concrete model object structure, as defined and annotated by + the core Gust annotations.
    +
    +
    +
    All Implemented Interfaces:
    +
    ModelSerializer<Model,​com.google.cloud.spanner.Mutation>
    +
    +
    +
    public final class SpannerMutationSerializer<Model extends com.google.protobuf.Message>
    +extends java.lang.Object
    +implements ModelSerializer<Model,​com.google.cloud.spanner.Mutation>
    +
    Implements a specialized serializer, capable of converting generated Message-derived objects into Spanner + Mutation records during write operations.
    +
    +
    See Also:
    +
    For an equivalent specialized de-serializer, working atop Spanner s.
    +
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + + + +
        +
      • +

        deflate

        +
        @Nonnull
        +public com.google.cloud.spanner.Mutation deflate​(@Nonnull
        +                                                 Model input)
        +                                          throws ModelDeflateException
        +
        Description copied from interface: ModelSerializer
        +
        Serialize a model instance from the provided object type to the specified output type, throwing exceptions + verbosely if we are unable to correctly, verifiably, and properly export the record.
        +
        +
        Specified by:
        +
        deflate in interface ModelSerializer<Model extends com.google.protobuf.Message,​com.google.cloud.spanner.Mutation>
        +
        Parameters:
        +
        input - Input record object to serialize.
        +
        Returns:
        +
        Serialized record data, of the specified output type.
        +
        Throws:
        +
        ModelDeflateException - If the model fails to export or serialize for any reason.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/SpannerStructDeserializer.html b/docs/java/gust/backend/driver/spanner/SpannerStructDeserializer.html new file mode 100644 index 000000000..8bcfa6984 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerStructDeserializer.html @@ -0,0 +1,355 @@ + + + + + +SpannerStructDeserializer + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class SpannerStructDeserializer<Model extends com.google.protobuf.Message>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.spanner.SpannerStructDeserializer<Model>
    • +
    +
  • +
+
+
    +
  • +
    +
    Type Parameters:
    +
    Model - Typed Message which implements a concrete model object structure, as defined and annotated by + the core Gust annotations.
    +
    +
    +
    All Implemented Interfaces:
    +
    ModelDeserializer<com.google.cloud.spanner.Struct,​Model>
    +
    +
    +
    @Immutable
    +@ThreadSafe
    +public final class SpannerStructDeserializer<Model extends com.google.protobuf.Message>
    +extends java.lang.Object
    +implements ModelDeserializer<com.google.cloud.spanner.Struct,​Model>
    +
    Implements a specialized de-serializer, capable of converting runtime-inhabited Spanner Struct-records into + typed Message-derived objects.
    +
    +
    See Also:
    +
    For an equivalent specialized serializer, working atop Spanner s.
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static <M extends com.google.protobuf.Message>
      SpannerStructDeserializer<M>
      forModel​(M instance, + SpannerDriverSettings driverSettings) +
      Construct a Struct deserializer for the provided instance.
      +
      Modelinflate​(com.google.cloud.spanner.Struct rowStruct) +
      De-serialize a model instance from the provided input type, throwing exceptions verbosely if we are unable to + correctly, verifiably, and properly load the record.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + + + +
        +
      • +

        forModel

        +
        public static <M extends com.google.protobuf.Message> SpannerStructDeserializer<M> forModel​(@Nonnull
        +                                                                                            M instance,
        +                                                                                            @Nonnull
        +                                                                                            SpannerDriverSettings driverSettings)
        +
        Construct a Struct deserializer for the provided instance.
        +
        +
        Type Parameters:
        +
        M - Model type to deserialize.
        +
        Parameters:
        +
        instance - Model instance to acquire a data deserializer for.
        +
        driverSettings - Settings for the Spanner driver itself.
        +
        Returns:
        +
        Snapshot deserializer instance.
        +
        +
      • +
      + + + +
        +
      • +

        inflate

        +
        @Nonnull
        +public Model inflate​(@Nonnull
        +                     com.google.cloud.spanner.Struct rowStruct)
        +              throws ModelInflateException
        +
        Description copied from interface: ModelDeserializer
        +
        De-serialize a model instance from the provided input type, throwing exceptions verbosely if we are unable to + correctly, verifiably, and properly load the record.
        +
        +
        Specified by:
        +
        inflate in interface ModelDeserializer<com.google.cloud.spanner.Struct,​Model extends com.google.protobuf.Message>
        +
        Parameters:
        +
        rowStruct - Input data or object from which to load the model instance.
        +
        Returns:
        +
        De-serialized and inflated model instance. Always a Message.
        +
        Throws:
        +
        ModelInflateException - If the model fails to load for any reason.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/SpannerTemporalConverter.html b/docs/java/gust/backend/driver/spanner/SpannerTemporalConverter.html new file mode 100644 index 000000000..4d26052a1 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerTemporalConverter.html @@ -0,0 +1,355 @@ + + + + + +SpannerTemporalConverter + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class SpannerTemporalConverter

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.spanner.SpannerTemporalConverter
    • +
    +
  • +
+
+
    +
  • +
    +
    @ThreadSafe
    +public final class SpannerTemporalConverter
    +extends java.lang.Object
    +
    Serialize back and forth between Google Cloud / Protocol Buffer standard TIMESTAMP and DATE records.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static com.google.cloud.DatecloudDateFromProto​(com.google.type.Date date) +
      Convert a regular/standard Protocol Buffers date into a Google Cloud date without loss of resolution.
      +
      static com.google.cloud.TimestampcloudTimestampFromProto​(com.google.protobuf.Timestamp timestamp) +
      Convert a regular/standard Protocol Buffers timestamp into a Google Cloud timestamp without loss of resolution.
      +
      static com.google.type.DateprotoDateFromCloud​(com.google.cloud.Date date) +
      Convert a Google Cloud date into a standard Protocol Buffers date without loss of resolution.
      +
      static com.google.protobuf.TimestampprotoTimestampFromCloud​(com.google.cloud.Timestamp timestamp) +
      Convert a Google Cloud timestamp into a standard Protocol Buffers timestamp without loss of resolution.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        cloudTimestampFromProto

        +
        @Nonnull
        +public static com.google.cloud.Timestamp cloudTimestampFromProto​(com.google.protobuf.Timestamp timestamp)
        +
        Convert a regular/standard Protocol Buffers timestamp into a Google Cloud timestamp without loss of resolution.
        +
        +
        Parameters:
        +
        timestamp - Standard timestamp to convert.
        +
        Returns:
        +
        Cloud timestamp value.
        +
        +
      • +
      + + + +
        +
      • +

        protoTimestampFromCloud

        +
        @Nonnull
        +public static com.google.protobuf.Timestamp protoTimestampFromCloud​(com.google.cloud.Timestamp timestamp)
        +
        Convert a Google Cloud timestamp into a standard Protocol Buffers timestamp without loss of resolution.
        +
        +
        Parameters:
        +
        timestamp - Cloud timestamp to convert.
        +
        Returns:
        +
        Protocol buffers timestamp value.
        +
        +
      • +
      + + + +
        +
      • +

        cloudDateFromProto

        +
        @Nonnull
        +public static com.google.cloud.Date cloudDateFromProto​(com.google.type.Date date)
        +
        Convert a regular/standard Protocol Buffers date into a Google Cloud date without loss of resolution.
        +
        +
        Parameters:
        +
        date - Standard date to convert.
        +
        Returns:
        +
        Cloud date value.
        +
        +
      • +
      + + + +
        +
      • +

        protoDateFromCloud

        +
        @Nonnull
        +public static com.google.type.Date protoDateFromCloud​(com.google.cloud.Date date)
        +
        Convert a Google Cloud date into a standard Protocol Buffers date without loss of resolution.
        +
        +
        Parameters:
        +
        date - Cloud date to convert.
        +
        Returns:
        +
        Protocol buffers date value.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/SpannerTransportConfig.html b/docs/java/gust/backend/driver/spanner/SpannerTransportConfig.html new file mode 100644 index 000000000..e046125dc --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerTransportConfig.html @@ -0,0 +1,229 @@ + + + + + +SpannerTransportConfig + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class SpannerTransportConfig

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.spanner.SpannerTransportConfig
    • +
    +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/SpannerUtil.html b/docs/java/gust/backend/driver/spanner/SpannerUtil.html new file mode 100644 index 000000000..839c396c8 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/SpannerUtil.html @@ -0,0 +1,1212 @@ + + + + + +SpannerUtil + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class SpannerUtil

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.driver.spanner.SpannerUtil
    • +
    +
  • +
+
+
    +
  • +
    +
    public final class SpannerUtil
    +extends java.lang.Object
    +
    Provides utilities related to operations with Spanner, including tools for resolving column names and types from + models and annotations, and producing default sets of columns for DDL statements.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static java.util.List<java.lang.String>calculateDefaultFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + SpannerDriverSettings driverSettings) +
      Calculate a default projection of Spanner columns, as configured on the provided default model instance.
      +
      static java.util.stream.Stream<Pair<java.lang.String,​java.lang.String>>calculateDefaultFieldStream​(com.google.protobuf.Descriptors.Descriptor descriptor, + SpannerDriverSettings driverSettings) +
      Calculate a default projection of Spanner columns, as configured on the provided default model instance.
      +
      static java.util.Optional<tools.elide.core.TableFieldOptions>columnOpts​(com.google.protobuf.Descriptors.FieldDescriptor field) +
      Given a resolved Protocol Buffer Descriptors.FieldDescriptor which is considered eligible for interaction + with Spanner, resolve any present column-generic options and annotations via TableFieldOptions.
      +
      static java.util.Optional<tools.elide.core.TableFieldOptions>columnOpts​(ModelMetadata.FieldPointer fieldPointer) +
      Given a pre-resolved ModelMetadata.FieldPointer, resolve any present TableFieldOptions.
      +
      static java.util.Optional<tools.elide.core.FieldPersistenceOptions>fieldOpts​(com.google.protobuf.Descriptors.FieldDescriptor field) +
      Given a resolved Protocol Buffer Descriptors.FieldDescriptor which is considered eligible for interaction + with Spanner, resolve any present field-generic options and annotations via FieldPersistenceOptions.
      +
      static java.util.Optional<tools.elide.core.FieldPersistenceOptions>fieldOpts​(ModelMetadata.FieldPointer fieldPointer) +
      Given a pre-resolved ModelMetadata.FieldPointer, resolve any present generic FieldPersistenceOptions.
      +
      static java.util.Collection<com.google.cloud.spanner.Type.StructField>generateStruct​(com.google.protobuf.Descriptors.Descriptor descriptor, + SpannerDriverSettings driverSettings) +
      Calculate a default projection of Spanner columns, as configured on the provided default model instance.
      +
      static com.google.cloud.spanner.TypemaybeWrapType​(ModelMetadata.FieldPointer field, + com.google.cloud.spanner.Type inner) +
      If a concrete type is marked as repeated, wrap it in an array type.
      +
      static java.util.function.Predicate<ModelMetadata.FieldPointer>onlySpannerEligibleFields​(SpannerDriverSettings settings) +
      Return a Predicate implementation which operates on ModelMetadata.FieldPointer objects to determine eligibility + for interaction with Spanner.
      +
      static java.util.function.Predicate<ModelMetadata.FieldPointer>onlySpannerEligibleFields​(java.util.SortedSet<java.lang.String> eligibleFields, + SpannerDriverSettings settings) +
      Return a Predicate implementation which determines field eligibility with regard to interaction with + Cloud Spanner, optionally considering the provided set of circumstantial higher-order eligible fields (for + instance, in the case of a known property projection).
      +
      static intresolveColumnIndex​(com.google.cloud.spanner.Struct source, + ModelMetadata.FieldPointer fieldPointer, + java.lang.String name) +
      Given a Spanner row result expressed as a Struct and a ModelMetadata.FieldPointer which is expected to be + present, with a pre-resolved column name, return the numeric column index.
      +
      static java.lang.StringresolveColumnName​(com.google.protobuf.Descriptors.FieldDescriptor field) +
      Given a resolved field pointer resolve the expected/configured column name in Spanner for a given typed model + field.
      +
      static java.lang.StringresolveColumnName​(com.google.protobuf.Descriptors.FieldDescriptor field, + SpannerDriverSettings settings) +
      Given a resolved field pointer resolve the expected/configured column name in Spanner for a given typed model + field.
      +
      static java.lang.StringresolveColumnName​(com.google.protobuf.Descriptors.FieldDescriptor field, + java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts, + java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts, + SpannerDriverSettings settings) +
      Given a resolved Protocol Buffer field descriptor and set of annotations, resolve the expected/configured column + name in Spanner for a given typed model field.
      +
      static java.lang.StringresolveColumnName​(ModelMetadata.FieldPointer fieldPointer, + java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts, + java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts, + SpannerDriverSettings settings) +
      Given a resolved field pointer and set of annotations, resolve the expected/configured column name in Spanner for + a given typed model field.
      +
      static intresolveColumnSize​(com.google.protobuf.Descriptors.FieldDescriptor field, + java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts, + java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts, + SpannerDriverSettings settings) +
      Given a model field pointer which translates to a `STRING` or `BYTES` column in Spanner, determine the size that + should be used when declaring the string column.
      +
      static com.google.cloud.spanner.TyperesolveColumnType​(ModelMetadata.FieldPointer fieldPointer, + java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts, + java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts, + SpannerDriverSettings settings) +
      Given a resolved and eligible ModelMetadata.FieldPointer for a model field which should interact with Spanner, resolve + an expected Spanner Type, including any nested structure or complex objects, as mediated and regulated by + annotations on the model field.
      +
      static com.google.cloud.spanner.ValueresolveColumnValue​(com.google.cloud.spanner.Struct source, + ModelMetadata.FieldPointer fieldPointer, + java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts, + java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts, + SpannerDriverSettings driverSettings) +
      Given a Spanner row result expressed as a Struct and a ModelMetadata.FieldPointer which is expected to be + present, resolve any present Value.
      +
      static com.google.cloud.spanner.TyperesolveDefaultType​(ModelMetadata.FieldPointer pointer, + com.google.protobuf.Descriptors.FieldDescriptor.Type protoType, + SpannerDriverSettings settings) +
      Resolve a default Spanner type for the provided field `pointer`.
      +
      static com.google.cloud.spanner.TyperesolveDefaultType​(ModelMetadata.FieldPointer pointer, + SpannerDriverSettings settings) +
      Resolve a default Spanner type for the provided field `pointer`.
      +
      static java.lang.StringresolveKeyColumn​(ModelMetadata.FieldPointer idField, + SpannerDriverSettings driverSettings) +
      For a given key field pointer, resolve the column name which should be used for the primary key in Spanner, + according to the annotation structure present on the key.
      +
      static com.google.cloud.spanner.TyperesolveKeyType​(ModelMetadata.FieldPointer idField) +
      For a given key field pointer, resolve the column type which should be used for the primary key in Spanner, + according to the annotation structure present on the key.
      +
      static java.lang.StringresolveTableName​(com.google.protobuf.Descriptors.Descriptor message) +
      Given a schema-driven model or key object, determine the table name that should be used in Spanner.
      +
      static java.lang.StringresolveTableName​(com.google.protobuf.Message message) +
      Given a schema-driven model or key object, determine the table name that should be used in Spanner.
      +
      static com.google.cloud.spanner.TyperesolveType​(ModelMetadata.FieldPointer pointer, + tools.elide.core.SpannerOptions.SpannerType spannerType) +
      Resolve a normalized Spanner type for the provided field `pointer`, with the explicit provided `spannerType`.
      +
      static java.util.Optional<tools.elide.core.SpannerFieldOptions>spannerOpts​(com.google.protobuf.Descriptors.FieldDescriptor field) +
      Given a resolved Protocol Buffer Descriptors.FieldDescriptor which is considered eligible for interaction + with Spanner, resolve any present Spanner-specific options and annotations via SpannerFieldOptions.
      +
      static java.util.Optional<tools.elide.core.SpannerFieldOptions>spannerOpts​(ModelMetadata.FieldPointer fieldPointer) +
      Given a pre-resolved ModelMetadata.FieldPointer, resolve any present SpannerFieldOptions.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        resolveKeyColumn

        +
        @Nonnull
        +public static java.lang.String resolveKeyColumn​(@Nonnull
        +                                                ModelMetadata.FieldPointer idField,
        +                                                @Nonnull
        +                                                SpannerDriverSettings driverSettings)
        +
        For a given key field pointer, resolve the column name which should be used for the primary key in Spanner, + according to the annotation structure present on the key.
        +
        +
        Parameters:
        +
        idField - Resolved field pointer to a given model's ID field.
        +
        driverSettings - Settings for the Spanner driver.
        +
        Returns:
        +
        Name of the column we should use for the primary key.
        +
        See Also:
        +
        To resolve the primary key column type.
        +
        +
      • +
      + + + +
        +
      • +

        resolveTableName

        +
        @Nonnull
        +public static java.lang.String resolveTableName​(@Nonnull
        +                                                com.google.protobuf.Message message)
        +
        Given a schema-driven model or key object, determine the table name that should be used in Spanner. All keys and + objects used with Spanner must have such annotations or use generated defaults. This method variant operates from + a full message instance.
        +
        +
        Parameters:
        +
        message - Message type to resolve a table name for.
        +
        Returns:
        +
        Resolved table name, from annotations, or calculated as a default.
        +
        See Also:
        +
        For the wrapped version of this message.
        +
        +
      • +
      + + + +
        +
      • +

        resolveTableName

        +
        @Nonnull
        +public static java.lang.String resolveTableName​(@Nonnull
        +                                                com.google.protobuf.Descriptors.Descriptor message)
        +
        Given a schema-driven model or key object, determine the table name that should be used in Spanner. All keys and + objects used with Spanner must have such annotations or use generated defaults.
        +
        +
        Parameters:
        +
        message - Message type to resolve a table name for.
        +
        Returns:
        +
        Resolved table name, from annotations, or calculated as a default.
        +
        +
      • +
      + + + +
        +
      • +

        resolveKeyType

        +
        @Nonnull
        +public static com.google.cloud.spanner.Type resolveKeyType​(@Nonnull
        +                                                           ModelMetadata.FieldPointer idField)
        +
        For a given key field pointer, resolve the column type which should be used for the primary key in Spanner, + according to the annotation structure present on the key.
        +
        +
        Parameters:
        +
        idField - Resolved field pointer to a given model key's ID field.
        +
        Returns:
        +
        Spanner column type to use for this model's ID.
        +
        See Also:
        +
        To resolve the primary key column name.
        +
        +
      • +
      + + + +
        +
      • +

        resolveColumnSize

        +
        public static int resolveColumnSize​(@Nonnull
        +                                    com.google.protobuf.Descriptors.FieldDescriptor field,
        +                                    @Nonnull
        +                                    java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts,
        +                                    @Nonnull
        +                                    java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts,
        +                                    @Nonnull
        +                                    SpannerDriverSettings settings)
        +
        Given a model field pointer which translates to a `STRING` or `BYTES` column in Spanner, determine the size that + should be used when declaring the string column. + +

        If an explicit column size is specified via model annotations, that prevails. If not, the default size value + is used.

        +
        +
        Parameters:
        +
        spannerOpts - Spanner-specific options on the field.
        +
        columnOpts - Column-generic options on the field.
        +
        settings - Settings for the Spanner driver.
        +
        Returns:
        +
        Expected name of the field when expressed as a column in Spanner.
        +
        +
      • +
      + + + +
        +
      • +

        resolveColumnName

        +
        @Nonnull
        +public static java.lang.String resolveColumnName​(@Nonnull
        +                                                 com.google.protobuf.Descriptors.FieldDescriptor field)
        +
        Given a resolved field pointer resolve the expected/configured column name in Spanner for a given typed model + field. If no specialized Spanner or table column annotations are present, fallback to a calculated default name.
        +
        +
        Parameters:
        +
        field - Pre-resolved model field descriptor.
        +
        Returns:
        +
        Expected name of the field when expressed as a column in Spanner.
        +
        See Also:
        +
        For the full + un-sugared version of this method.
        +
        +
      • +
      + + + +
        +
      • +

        resolveColumnName

        +
        @Nonnull
        +public static java.lang.String resolveColumnName​(@Nonnull
        +                                                 com.google.protobuf.Descriptors.FieldDescriptor field,
        +                                                 @Nonnull
        +                                                 SpannerDriverSettings settings)
        +
        Given a resolved field pointer resolve the expected/configured column name in Spanner for a given typed model + field. If no specialized Spanner or table column annotations are present, fallback to a calculated default name. + Apply any active driver settings as well.
        +
        +
        Parameters:
        +
        field - Pre-resolved model field descriptor.
        +
        settings - Settings for the Spanner driver.
        +
        Returns:
        +
        Expected name of the field when expressed as a column in Spanner.
        +
        See Also:
        +
        For the full + un-sugared version of this method.
        +
        +
      • +
      + + + +
        +
      • +

        resolveColumnName

        +
        @Nonnull
        +public static java.lang.String resolveColumnName​(@Nonnull
        +                                                 ModelMetadata.FieldPointer fieldPointer,
        +                                                 @Nonnull
        +                                                 java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts,
        +                                                 @Nonnull
        +                                                 java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts,
        +                                                 @Nonnull
        +                                                 SpannerDriverSettings settings)
        +
        Given a resolved field pointer and set of annotations, resolve the expected/configured column name in Spanner for + a given typed model field. If no specialized Spanner or table column annotations are present, fallback to a + calculated default name.
        +
        +
        Parameters:
        +
        fieldPointer - Pre-resolved model field pointer.
        +
        spannerOpts - Spanner-specific options on the field.
        +
        columnOpts - Column-generic options on the field.
        +
        settings - Settings for the Spanner driver.
        +
        Returns:
        +
        Expected name of the field when expressed as a column in Spanner.
        +
        See Also:
        +
        For the full + un-sugared version of this method.
        +
        +
      • +
      + + + +
        +
      • +

        resolveColumnName

        +
        @Nonnull
        +public static java.lang.String resolveColumnName​(@Nonnull
        +                                                 com.google.protobuf.Descriptors.FieldDescriptor field,
        +                                                 @Nonnull
        +                                                 java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts,
        +                                                 @Nonnull
        +                                                 java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts,
        +                                                 @Nonnull
        +                                                 SpannerDriverSettings settings)
        +
        Given a resolved Protocol Buffer field descriptor and set of annotations, resolve the expected/configured column + name in Spanner for a given typed model field. If no specialized Spanner or table column annotations are present, + fallback to a calculated default name. + +

        SpannerFieldOptions always outweigh TableFieldOptions. If two similar or congruent properties + are set between options, generic options are applied first, and then specialized options override.

        + +

        If SpannerDriverSettings.preserveFieldNames() is activated when this method is called, default names + will use the literal field name from the Protocol Buffer definition. Otherwise, JSON-style names are calculated + and used as default names.

        + +

        Similarly, if SpannerDriverSettings.defaultCapitalizedNames() is activated when this method is called, + default names will use JSON-style naming but with initial capitals. For example, `name` turns into `Name` and + `contact_info` turns into `ContactInfo`. In all cases, explicit property names from specialized or generic + annotations prevail, then SpannerDriverSettings.preserveFieldNames() prevails, then the default form of + naming with Spanner capitalized names active.

        +
        +
        Parameters:
        +
        field - Protocol Buffer field descriptor for which we should resolve a Spanner column name.
        +
        spannerOpts - Spanner options applied to this field as annotations.
        +
        columnOpts - Generic table column settings applied to this field as annotations.
        +
        settings - Settings for the Spanner driver.
        +
        Returns:
        +
        Resolved column name, from explicit annotations, or by way of default calculation, as described above.
        +
        See Also:
        +
        For a version of this method + which operates on objects.
        +
        +
      • +
      + + + +
        +
      • +

        resolveColumnIndex

        +
        public static int resolveColumnIndex​(@Nonnull
        +                                     com.google.cloud.spanner.Struct source,
        +                                     @Nonnull
        +                                     ModelMetadata.FieldPointer fieldPointer,
        +                                     @Nonnull
        +                                     java.lang.String name)
        +
        Given a Spanner row result expressed as a Struct and a ModelMetadata.FieldPointer which is expected to be + present, with a pre-resolved column name, return the numeric column index.
        +
        +
        Parameters:
        +
        source - Row result from Spanner which we should resolve the column index from.
        +
        fieldPointer - Pointer to the field for which we are resolving an index. Pre-resolved.
        +
        name - Translated name of the column for which we are resolving an index. Pre-resolved.
        +
        Returns:
        +
        Integer index for the column in the provided row result.
        +
        +
      • +
      + + + +
        +
      • +

        resolveColumnValue

        +
        @Nonnull
        +public static com.google.cloud.spanner.Value resolveColumnValue​(@Nonnull
        +                                                                com.google.cloud.spanner.Struct source,
        +                                                                @Nonnull
        +                                                                ModelMetadata.FieldPointer fieldPointer,
        +                                                                @Nonnull
        +                                                                java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts,
        +                                                                @Nonnull
        +                                                                java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts,
        +                                                                @Nonnull
        +                                                                SpannerDriverSettings driverSettings)
        +
        Given a Spanner row result expressed as a Struct and a ModelMetadata.FieldPointer which is expected to be + present, resolve any present Value. + +

        This method additionally resolves the expected column name for the provided field.

        +
        +
        Parameters:
        +
        source - Row result from Spanner from which we should resolve any present value.
        +
        fieldPointer - Pointer to the model field for which we are resolving a value.
        +
        spannerOpts - Spanner-specific options and annotations present on the field.
        +
        columnOpts - Column-generic options and annotations present on the field.
        +
        driverSettings - Settings for the Spanner driver.
        +
        Returns:
        +
        Resolved Spanner value, as applicable.
        +
        See Also:
        +
        For an explanation of model + field column name calculations and annotation behavior.
        +
        +
      • +
      + + + +
        +
      • +

        resolveColumnType

        +
        @Nonnull
        +public static com.google.cloud.spanner.Type resolveColumnType​(@Nonnull
        +                                                              ModelMetadata.FieldPointer fieldPointer,
        +                                                              @Nonnull
        +                                                              java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts,
        +                                                              @Nonnull
        +                                                              java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts,
        +                                                              @Nonnull
        +                                                              SpannerDriverSettings settings)
        +
        Given a resolved and eligible ModelMetadata.FieldPointer for a model field which should interact with Spanner, resolve + an expected Spanner Type, including any nested structure or complex objects, as mediated and regulated by + annotations on the model field. + +

        If SpannerFieldOptions.getType() returns a non-default value, it prevails first, with + TableFieldOptions.getSptype() after that. If no explicit type is resolvable from the field definition, + a default type is generated (see method references for more information).

        +
        +
        Parameters:
        +
        fieldPointer - Pointer to the model field for which we should resolve a Spanner column type.
        +
        spannerOpts - Spanner-specific options or annotations present on the field definition, as applicable.
        +
        columnOpts - Column-generic options or annotations present on the field definition, as applicable.
        +
        settings - Active settings for the Spanner driver.
        +
        Returns:
        +
        Expected Spanner column type corresponding to the provided model field, considering all annotations.
        +
        See Also:
        +
        Fallback behavior if no explicit type is specified.
        +
        +
      • +
      + + + +
        +
      • +

        calculateDefaultFieldStream

        +
        @Nonnull
        +public static java.util.stream.Stream<Pair<java.lang.String,​java.lang.String>> calculateDefaultFieldStream​(@Nonnull
        +                                                                                                                 com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                                                                                                 @Nonnull
        +                                                                                                                 SpannerDriverSettings driverSettings)
        +
        Calculate a default projection of Spanner columns, as configured on the provided default model instance. Results + are returned as a stream of fields paired to their column counterparts.
        +
        +
        Parameters:
        +
        descriptor - Default model schema to generate a default set of Spanner columns from.
        +
        driverSettings - Settings for the Spanner driver.
        +
        Returns:
        +
        Default list of Spanner columns.
        +
        +
      • +
      + + + +
        +
      • +

        calculateDefaultFields

        +
        @Nonnull
        +public static java.util.List<java.lang.String> calculateDefaultFields​(@Nonnull
        +                                                                      com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                                                      @Nonnull
        +                                                                      SpannerDriverSettings driverSettings)
        +
        Calculate a default projection of Spanner columns, as configured on the provided default model instance.
        +
        +
        Parameters:
        +
        descriptor - Default model schema to generate a default set of Spanner columns from.
        +
        driverSettings - Settings for the Spanner driver.
        +
        Returns:
        +
        Default list of Spanner columns.
        +
        +
      • +
      + + + + + + + +
        +
      • +

        onlySpannerEligibleFields

        +
        @Nonnull
        +public static java.util.function.Predicate<ModelMetadata.FieldPointeronlySpannerEligibleFields​(@Nonnull
        +                                                                                                 java.util.SortedSet<java.lang.String> eligibleFields,
        +                                                                                                 @Nonnull
        +                                                                                                 SpannerDriverSettings settings)
        +
        Return a Predicate implementation which determines field eligibility with regard to interaction with + Cloud Spanner, optionally considering the provided set of circumstantial higher-order eligible fields (for + instance, in the case of a known property projection). + +

        Field eligibility is determined by the following criteria: +

          +
        • Model fields MUST be present on the Message schema to interact with Spanner.
        • +
        • Model fields MUST NOT be annotated with TableFieldOptions.getIgnore().
        • +
        • Model fields MUST NOT be annotated with SpannerFieldOptions.getIgnore().
        • +
        • Model fields MUST NOT be annotated with FieldVisibility.INTERNAL.
        • +
        • If provided and non-empty, model fields MUST be present in the set of
          eligibleFields
          + provided to this method.
        • +

        +
        +
        Parameters:
        +
        eligibleFields - Set of higher-order eligible fields, if applicable. Only considered if non-empty.
        +
        settings - Settings for the Spanner driver.
        +
        Returns:
        +
        Predicate which filters ModelMetadata.FieldPointer objects according to the provided settings.
        +
        See Also:
        +
        For circumstances with no known eligible fields.
        +
        +
      • +
      + + + + + + + +
        +
      • +

        maybeWrapType

        +
        @Nonnull
        +public static com.google.cloud.spanner.Type maybeWrapType​(@Nonnull
        +                                                          ModelMetadata.FieldPointer field,
        +                                                          @Nonnull
        +                                                          com.google.cloud.spanner.Type inner)
        +
        If a concrete type is marked as repeated, wrap it in an array type. Otherwise, just return the type.
        +
        +
        Parameters:
        +
        field - Field pointer for the model field.
        +
        inner - Inner type for the maybe-repeated field.
        +
        Returns:
        +
        Either the array-wrapped type or the concrete individual type.
        +
        +
      • +
      + + + +
        +
      • +

        resolveType

        +
        @Nonnull
        +public static com.google.cloud.spanner.Type resolveType​(@Nonnull
        +                                                        ModelMetadata.FieldPointer pointer,
        +                                                        @Nonnull
        +                                                        tools.elide.core.SpannerOptions.SpannerType spannerType)
        +
        Resolve a normalized Spanner type for the provided field `pointer`, with the explicit provided `spannerType`. + With an explicit type, our job is just to make sure the model configuration is cohesive.
        +
        +
        Parameters:
        +
        pointer - Resolved field pointer.
        +
        spannerType - Explicit Spanner type.
        +
        Returns:
        +
        Resolved normalized Spanner type.
        +
        +
      • +
      + + + +
        +
      • +

        resolveDefaultType

        +
        @Nonnull
        +public static com.google.cloud.spanner.Type resolveDefaultType​(@Nonnull
        +                                                               ModelMetadata.FieldPointer pointer,
        +                                                               @Nonnull
        +                                                               SpannerDriverSettings settings)
        +
        Resolve a default Spanner type for the provided field `pointer`. This selects a sensible default when no + explicit type annotations are present for a Spanner column's type.
        +
        +
        Parameters:
        +
        pointer - Field pointer to resolve a Spanner type for.
        +
        settings - Settings for the Spanner driver.
        +
        Returns:
        +
        Resolved normalized Spanner type.
        +
        +
      • +
      + + + +
        +
      • +

        resolveDefaultType

        +
        @Nonnull
        +public static com.google.cloud.spanner.Type resolveDefaultType​(@Nonnull
        +                                                               ModelMetadata.FieldPointer pointer,
        +                                                               @Nonnull
        +                                                               com.google.protobuf.Descriptors.FieldDescriptor.Type protoType,
        +                                                               @Nonnull
        +                                                               SpannerDriverSettings settings)
        +
        Resolve a default Spanner type for the provided field `pointer`. This selects a sensible default when no + explicit type annotations are present for a Spanner column's type.
        +
        +
        Parameters:
        +
        pointer - Field pointer to resolve a Spanner type for.
        +
        protoType - Protocol Buffer type to resolve.
        +
        settings - Settings for the Spanner driver.
        +
        Returns:
        +
        Resolved normalized Spanner type.
        +
        +
      • +
      + + + + + + + +
        +
      • +

        columnOpts

        +
        @Nonnull
        +public static java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts​(@Nonnull
        +                                                                                com.google.protobuf.Descriptors.FieldDescriptor field)
        +
        Given a resolved Protocol Buffer Descriptors.FieldDescriptor which is considered eligible for interaction + with Spanner, resolve any present column-generic options and annotations via TableFieldOptions. + +

        Column-generic options apply to engines which operate in a columnar manner. This includes Spanner, but also + includes engines like BigQuery and SQL-based systems. To allow adaptation to those systems without curtailing + control of table and field naming, the Spanner driver respects TableFieldOptions but defers to any + present SpannerFieldOptions.

        +
        +
        Parameters:
        +
        field - Field descriptor for which we should resolve any present TableFieldOptions.
        +
        Returns:
        +
        Any present column-generic field options or annotations, or Optional.empty().
        +
        See Also:
        +
        For a version of this method which operates on ., +For the equivalent version of this method that returns Spanner- + specific field options.
        +
        +
      • +
      + + + + + + + +
        +
      • +

        fieldOpts

        +
        @Nonnull
        +public static java.util.Optional<tools.elide.core.FieldPersistenceOptions> fieldOpts​(@Nonnull
        +                                                                                     com.google.protobuf.Descriptors.FieldDescriptor field)
        +
        Given a resolved Protocol Buffer Descriptors.FieldDescriptor which is considered eligible for interaction + with Spanner, resolve any present field-generic options and annotations via FieldPersistenceOptions. + +

        To adapt to other persistence engines, model fields may be annotated with FieldPersistenceOptions. In + all cases, present FieldPersistenceOptions yield with regard to matchin Spanner-specific fields.

        +
        +
        Parameters:
        +
        field - Field descriptor for which we should resolve any present FieldPersistenceOptions.
        +
        Returns:
        +
        Any present field-generic field options or annotations, or Optional.empty().
        +
        See Also:
        +
        Equivalent method for Spanner options, +equivalent method for column generic options
        +
        +
      • +
      + + + + + + + +
        +
      • +

        spannerOpts

        +
        @Nonnull
        +public static java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts​(@Nonnull
        +                                                                                   com.google.protobuf.Descriptors.FieldDescriptor field)
        +
        Given a resolved Protocol Buffer Descriptors.FieldDescriptor which is considered eligible for interaction + with Spanner, resolve any present Spanner-specific options and annotations via SpannerFieldOptions. + +

        To adapt to other columnar-style engines, model fields may be annotated with TableFieldOptions. In all + cases, present SpannerFieldOptions override with regard to Spanner Driver behavior.

        +
        +
        Parameters:
        +
        field - Field descriptor for which we should resolve any present SpannerFieldOptions.
        +
        Returns:
        +
        Any present column-generic field options or annotations, or Optional.empty().
        +
        See Also:
        +
        For a version of this method which operates on ., +For the equivalent version of this method that returns column- + generic field options.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerAdapter.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerAdapter.html new file mode 100644 index 000000000..65c69d298 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerAdapter.html @@ -0,0 +1,303 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerAdapter + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerAdapter

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use SpannerAdapter 
    PackageDescription
    gust.backend.driver.spanner +
    Provides a DatabaseDriver implementation for integration with Google Cloud Spanner.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of SpannerAdapter in gust.backend.driver.spanner

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.driver.spanner that return SpannerAdapter 
      Modifier and TypeMethodDescription
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      SpannerAdapter<K,​M>
      SpannerAdapter.acquire​(com.google.cloud.spanner.SpannerOptions.Builder baseOptions, + com.google.cloud.spanner.DatabaseId defaultDatabase, + com.google.api.gax.rpc.TransportChannelProvider spannerChannel, + java.util.Optional<com.google.api.gax.core.CredentialsProvider> credentialsProvider, + java.util.Optional<com.google.cloud.spanner.SpannerOptions.CallCredentialsProvider> callCredentialProvider, + com.google.cloud.grpc.GrpcTransportOptions transportOptions, + com.google.common.util.concurrent.ListeningScheduledExecutorService executorService, + K keyInstance, + M messageInstance, + SpannerDriverSettings driverSettings, + java.util.Optional<CacheDriver<K,​M>> cacheDriver) +
      Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      SpannerAdapter<K,​M>
      SpannerAdapter.acquire​(K keyInstance, + M messageInstance, + com.google.cloud.spanner.DatabaseId defaultDatabase) +
      Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      SpannerAdapter<K,​M>
      SpannerAdapter.acquire​(K keyInstance, + M messageInstance, + com.google.cloud.spanner.DatabaseId defaultDatabase, + com.google.cloud.spanner.SpannerOptions.Builder baseOptions, + com.google.common.util.concurrent.ListeningScheduledExecutorService executorService) +
      Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      SpannerAdapter<K,​M>
      SpannerAdapter.acquire​(K keyInstance, + M messageInstance, + com.google.cloud.spanner.DatabaseId defaultDatabase, + com.google.common.util.concurrent.ListeningScheduledExecutorService executorService) +
      Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      SpannerAdapter<K,​M>
      SpannerAdapter.acquire​(K keyInstance, + M messageInstance, + com.google.cloud.spanner.DatabaseId defaultDatabase, + java.util.Optional<com.google.common.util.concurrent.ListeningScheduledExecutorService> executorService, + java.util.Optional<SpannerDriverSettings> driverSettings, + java.util.Optional<com.google.cloud.spanner.SpannerOptions.Builder> baseOptions, + java.util.Optional<CacheDriver<K,​M>> cacheDriver) +
      Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId.
      +
      <Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message>
      SpannerAdapter<Key,​Model>
      SpannerManager.ConfiguredSpannerManager.adapter​(Key keyInstance, + Model modelInstance) +
      Acquire a typed adapter instance specialized to the provided key and model types, which should derive from + schema-driven Message classes.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      SpannerAdapter<K,​M>
      SpannerAdapter.forModel​(SpannerDriver<K,​M> driver) +
      Create or resolve a SpannerAdapter for the pre-fabricated SpannerDriver.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      SpannerAdapter<K,​M>
      SpannerAdapter.forModel​(SpannerDriver<K,​M> driver, + java.util.Optional<CacheDriver<K,​M>> cacheDriver) +
      Create or resolve a SpannerAdapter for the pre-fabricated SpannerDriver, optionally using the + provided CacheDriver, if present.
      +
      SpannerAdapter<com.google.protobuf.Message,​com.google.protobuf.Message>SpannerManager.ConfiguredSpannerManager.generic() +
      Acquire a generic adapter instance designed to work with all Message-inheriting model types.
      +
      + + + + + + + + + + + + + + +
      Methods in gust.backend.driver.spanner that return types with arguments of type SpannerAdapter 
      Modifier and TypeMethodDescription
      java.util.Collection<SpannerAdapter>SpannerManager.ConfiguredSpannerManager.allAdapters() +
      Returns the full set of known configured Spanner managers, JVM-wide.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerCodec.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerCodec.html new file mode 100644 index 000000000..3f9b1b8a2 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerCodec.html @@ -0,0 +1,215 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerCodec + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerCodec

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use SpannerCodec 
    PackageDescription
    gust.backend.driver.spanner +
    Provides a DatabaseDriver implementation for integration with Google Cloud Spanner.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of SpannerCodec in gust.backend.driver.spanner

      + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.driver.spanner that return SpannerCodec 
      Modifier and TypeMethodDescription
      static <M extends com.google.protobuf.Message>
      SpannerCodec<M>
      SpannerCodec.forModel​(M instance) +
      Create a Spanner message codec which adapts the provided builder to a Spanner Mutation and back with + default codecs and settings.
      +
      static <M extends com.google.protobuf.Message>
      SpannerCodec<M>
      SpannerCodec.forModel​(M instance, + SpannerDriverSettings driverSettings) +
      Create a Spanner message codec which adapts the provided builder to a Spanner Mutation and back with + default codecs and custom settings.
      +
      static <M extends com.google.protobuf.Message>
      SpannerCodec<M>
      SpannerCodec.forModel​(M instance, + ModelSerializer<M,​com.google.cloud.spanner.Mutation> serializer, + ModelDeserializer<com.google.cloud.spanner.Struct,​M> deserializer) +
      Create a Spanner message codec which adapts the provided builder to a Spanner Mutation and back.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerDriver.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerDriver.html new file mode 100644 index 000000000..ab838e953 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerDriver.html @@ -0,0 +1,205 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerDriver + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerDriver

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerDriverSettings.DefaultSettings.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerDriverSettings.DefaultSettings.html new file mode 100644 index 000000000..9fc2ba6f3 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerDriverSettings.DefaultSettings.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerDriverSettings.DefaultSettings + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerDriverSettings.DefaultSettings

+
+
No usage of gust.backend.driver.spanner.SpannerDriverSettings.DefaultSettings
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerDriverSettings.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerDriverSettings.html new file mode 100644 index 000000000..0579c248d --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerDriverSettings.html @@ -0,0 +1,453 @@ + + + + + +Uses of Interface gust.backend.driver.spanner.SpannerDriverSettings + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.driver.spanner.SpannerDriverSettings

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use SpannerDriverSettings 
    PackageDescription
    gust.backend.driver.spanner +
    Provides a DatabaseDriver implementation for integration with Google Cloud Spanner.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of SpannerDriverSettings in gust.backend.driver.spanner

      + + + + + + + + + + + + + + +
      Fields in gust.backend.driver.spanner declared as SpannerDriverSettings 
      Modifier and TypeFieldDescription
      static SpannerDriverSettingsSpannerDriverSettings.DEFAULTS +
      Default set of configured settings for the Spanner driver.
      +
      + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.driver.spanner that return SpannerDriverSettings 
      Modifier and TypeMethodDescription
      SpannerDriverSettingsSpannerGeneratedDDL.Builder.getSettings() 
      SpannerDriverSettingsSpannerManager.ConfiguredSpannerManager.getSettings() 
      + + + + + + + + + + + + + + +
      Methods in gust.backend.driver.spanner that return types with arguments of type SpannerDriverSettings 
      Modifier and TypeMethodDescription
      java.util.Optional<SpannerDriverSettings>SpannerManager.Builder.getSettings() 
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.driver.spanner with parameters of type SpannerDriverSettings 
      Modifier and TypeMethodDescription
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      SpannerAdapter<K,​M>
      SpannerAdapter.acquire​(com.google.cloud.spanner.SpannerOptions.Builder baseOptions, + com.google.cloud.spanner.DatabaseId defaultDatabase, + com.google.api.gax.rpc.TransportChannelProvider spannerChannel, + java.util.Optional<com.google.api.gax.core.CredentialsProvider> credentialsProvider, + java.util.Optional<com.google.cloud.spanner.SpannerOptions.CallCredentialsProvider> callCredentialProvider, + com.google.cloud.grpc.GrpcTransportOptions transportOptions, + com.google.common.util.concurrent.ListeningScheduledExecutorService executorService, + K keyInstance, + M messageInstance, + SpannerDriverSettings driverSettings, + java.util.Optional<CacheDriver<K,​M>> cacheDriver) +
      Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId.
      +
      static java.util.List<java.lang.String>SpannerUtil.calculateDefaultFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + SpannerDriverSettings driverSettings) +
      Calculate a default projection of Spanner columns, as configured on the provided default model instance.
      +
      static java.util.stream.Stream<Pair<java.lang.String,​java.lang.String>>SpannerUtil.calculateDefaultFieldStream​(com.google.protobuf.Descriptors.Descriptor descriptor, + SpannerDriverSettings driverSettings) +
      Calculate a default projection of Spanner columns, as configured on the provided default model instance.
      +
      static <M extends com.google.protobuf.Message>
      SpannerCodec<M>
      SpannerCodec.forModel​(M instance, + SpannerDriverSettings driverSettings) +
      Create a Spanner message codec which adapts the provided builder to a Spanner Mutation and back with + default codecs and custom settings.
      +
      static <M extends com.google.protobuf.Message>
      SpannerStructDeserializer<M>
      SpannerStructDeserializer.forModel​(M instance, + SpannerDriverSettings driverSettings) +
      Construct a Struct deserializer for the provided instance.
      +
      static java.util.Collection<com.google.cloud.spanner.Type.StructField>SpannerUtil.generateStruct​(com.google.protobuf.Descriptors.Descriptor descriptor, + SpannerDriverSettings driverSettings) +
      Calculate a default projection of Spanner columns, as configured on the provided default model instance.
      +
      static SpannerGeneratedDDL.BuilderSpannerGeneratedDDL.generateTableDDL​(com.google.protobuf.Descriptors.Descriptor model, + SpannerDriverSettings settings) +
      Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing + that model's properties.
      +
      static java.util.function.Predicate<ModelMetadata.FieldPointer>SpannerUtil.onlySpannerEligibleFields​(SpannerDriverSettings settings) +
      Return a Predicate implementation which operates on ModelMetadata.FieldPointer objects to determine eligibility + for interaction with Spanner.
      +
      static java.util.function.Predicate<ModelMetadata.FieldPointer>SpannerUtil.onlySpannerEligibleFields​(java.util.SortedSet<java.lang.String> eligibleFields, + SpannerDriverSettings settings) +
      Return a Predicate implementation which determines field eligibility with regard to interaction with + Cloud Spanner, optionally considering the provided set of circumstantial higher-order eligible fields (for + instance, in the case of a known property projection).
      +
      static java.lang.StringSpannerUtil.resolveColumnName​(com.google.protobuf.Descriptors.FieldDescriptor field, + SpannerDriverSettings settings) +
      Given a resolved field pointer resolve the expected/configured column name in Spanner for a given typed model + field.
      +
      static java.lang.StringSpannerUtil.resolveColumnName​(com.google.protobuf.Descriptors.FieldDescriptor field, + java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts, + java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts, + SpannerDriverSettings settings) +
      Given a resolved Protocol Buffer field descriptor and set of annotations, resolve the expected/configured column + name in Spanner for a given typed model field.
      +
      static java.lang.StringSpannerUtil.resolveColumnName​(ModelMetadata.FieldPointer fieldPointer, + java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts, + java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts, + SpannerDriverSettings settings) +
      Given a resolved field pointer and set of annotations, resolve the expected/configured column name in Spanner for + a given typed model field.
      +
      static intSpannerUtil.resolveColumnSize​(com.google.protobuf.Descriptors.FieldDescriptor field, + java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts, + java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts, + SpannerDriverSettings settings) +
      Given a model field pointer which translates to a `STRING` or `BYTES` column in Spanner, determine the size that + should be used when declaring the string column.
      +
      static com.google.cloud.spanner.TypeSpannerUtil.resolveColumnType​(ModelMetadata.FieldPointer fieldPointer, + java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts, + java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts, + SpannerDriverSettings settings) +
      Given a resolved and eligible ModelMetadata.FieldPointer for a model field which should interact with Spanner, resolve + an expected Spanner Type, including any nested structure or complex objects, as mediated and regulated by + annotations on the model field.
      +
      static com.google.cloud.spanner.ValueSpannerUtil.resolveColumnValue​(com.google.cloud.spanner.Struct source, + ModelMetadata.FieldPointer fieldPointer, + java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts, + java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts, + SpannerDriverSettings driverSettings) +
      Given a Spanner row result expressed as a Struct and a ModelMetadata.FieldPointer which is expected to be + present, resolve any present Value.
      +
      static java.util.List<gust.backend.driver.spanner.SpannerGeneratedDDL.ColumnSpec>SpannerGeneratedDDL.resolveDefaultColumns​(com.google.protobuf.Descriptors.Descriptor model, + SpannerDriverSettings settings) +
      Resolve the default calculated set of Spanner columns for a given model structure.
      +
      static com.google.cloud.spanner.TypeSpannerUtil.resolveDefaultType​(ModelMetadata.FieldPointer pointer, + com.google.protobuf.Descriptors.FieldDescriptor.Type protoType, + SpannerDriverSettings settings) +
      Resolve a default Spanner type for the provided field `pointer`.
      +
      static com.google.cloud.spanner.TypeSpannerUtil.resolveDefaultType​(ModelMetadata.FieldPointer pointer, + SpannerDriverSettings settings) +
      Resolve a default Spanner type for the provided field `pointer`.
      +
      static java.lang.StringSpannerUtil.resolveKeyColumn​(ModelMetadata.FieldPointer idField, + SpannerDriverSettings driverSettings) +
      For a given key field pointer, resolve the column name which should be used for the primary key in Spanner, + according to the annotation structure present on the key.
      +
      + + + + + + + + + + + + + + + + + + + +
      Method parameters in gust.backend.driver.spanner with type arguments of type SpannerDriverSettings 
      Modifier and TypeMethodDescription
      static SpannerGeneratedDDL.BuilderSpannerGeneratedDDL.generateTableDDL​(com.google.protobuf.Message instance, + java.util.Optional<SpannerDriverSettings> settings) +
      Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing + that model's properties.
      +
      SpannerManager.BuilderSpannerManager.Builder.setSettings​(java.util.Optional<SpannerDriverSettings> settings) +
      Set the main driver settings to use when spawning adapters in the target configured Spanner manager.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.Builder.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.Builder.html new file mode 100644 index 000000000..d1938b12f --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.Builder.html @@ -0,0 +1,250 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerGeneratedDDL.Builder + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerGeneratedDDL.Builder

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.InterleaveTarget.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.InterleaveTarget.html new file mode 100644 index 000000000..637e4a74b --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.InterleaveTarget.html @@ -0,0 +1,237 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerGeneratedDDL.InterleaveTarget + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerGeneratedDDL.InterleaveTarget

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.PropagatedAction.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.PropagatedAction.html new file mode 100644 index 000000000..a489e2f3c --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.PropagatedAction.html @@ -0,0 +1,223 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerGeneratedDDL.PropagatedAction + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerGeneratedDDL.PropagatedAction

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.RenderableStatement.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.RenderableStatement.html new file mode 100644 index 000000000..8f009c684 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.RenderableStatement.html @@ -0,0 +1,203 @@ + + + + + +Uses of Interface gust.backend.driver.spanner.SpannerGeneratedDDL.RenderableStatement + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.driver.spanner.SpannerGeneratedDDL.RenderableStatement

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.SortDirection.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.SortDirection.html new file mode 100644 index 000000000..2ba7bf515 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.SortDirection.html @@ -0,0 +1,226 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerGeneratedDDL.SortDirection + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerGeneratedDDL.SortDirection

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.TableConstraint.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.TableConstraint.html new file mode 100644 index 000000000..5f14ff86d --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.TableConstraint.html @@ -0,0 +1,229 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerGeneratedDDL.TableConstraint + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerGeneratedDDL.TableConstraint

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.html new file mode 100644 index 000000000..d7904a817 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerGeneratedDDL.html @@ -0,0 +1,196 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerGeneratedDDL + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerGeneratedDDL

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerManager.Builder.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerManager.Builder.html new file mode 100644 index 000000000..c59caa90f --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerManager.Builder.html @@ -0,0 +1,225 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerManager.Builder + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerManager.Builder

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerManager.ConfiguredSpannerManager.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerManager.ConfiguredSpannerManager.html new file mode 100644 index 000000000..99984f307 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerManager.ConfiguredSpannerManager.html @@ -0,0 +1,214 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerManager.ConfiguredSpannerManager + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerManager.ConfiguredSpannerManager

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerManager.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerManager.html new file mode 100644 index 000000000..77504fff7 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerManager.html @@ -0,0 +1,197 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerManager + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerManager

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerMutationSerializer.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerMutationSerializer.html new file mode 100644 index 000000000..f80fea26e --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerMutationSerializer.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerMutationSerializer + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerMutationSerializer

+
+
No usage of gust.backend.driver.spanner.SpannerMutationSerializer
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerStructDeserializer.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerStructDeserializer.html new file mode 100644 index 000000000..f9b165de4 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerStructDeserializer.html @@ -0,0 +1,197 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerStructDeserializer + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerStructDeserializer

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerTemporalConverter.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerTemporalConverter.html new file mode 100644 index 000000000..1eaa28bfe --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerTemporalConverter.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerTemporalConverter + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerTemporalConverter

+
+
No usage of gust.backend.driver.spanner.SpannerTemporalConverter
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerTransportConfig.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerTransportConfig.html new file mode 100644 index 000000000..7f6cb6c87 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerTransportConfig.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerTransportConfig + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerTransportConfig

+
+
No usage of gust.backend.driver.spanner.SpannerTransportConfig
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/class-use/SpannerUtil.html b/docs/java/gust/backend/driver/spanner/class-use/SpannerUtil.html new file mode 100644 index 000000000..65a1f6150 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/class-use/SpannerUtil.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.driver.spanner.SpannerUtil + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.driver.spanner.SpannerUtil

+
+
No usage of gust.backend.driver.spanner.SpannerUtil
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/package-summary.html b/docs/java/gust/backend/driver/spanner/package-summary.html new file mode 100644 index 000000000..c647bbb56 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/package-summary.html @@ -0,0 +1,306 @@ + + + + + +gust.backend.driver.spanner + + + + + + + + + + + + + + +
+ +
+
+
+

Package gust.backend.driver.spanner

+
+
+
+ + +
Provides a DatabaseDriver implementation for integration with Google Cloud Spanner.
+
+
    +
  • + + + + + + + + + + + + + + + + +
    Interface Summary 
    InterfaceDescription
    SpannerDriverSettings +
    Specifies settings for the Spanner driver and data storage implementation.
    +
    SpannerGeneratedDDL.RenderableStatement +
    Represents a DDL statement structure in code that can be rendered down to a string.
    +
    +
  • +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Class Summary 
    ClassDescription
    SpannerAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message> +
    Implementation of a DatabaseAdapter backed by Google Cloud Spanner, capable of marshalling arbitrary + schema-generated Message objects back and forth from structured columnar storage.
    +
    SpannerCodec<Model extends com.google.protobuf.Message> +
    Implements a ModelCodec for Spanner types used as intermediaries during database operations.
    +
    SpannerDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message> +
    Provides a DatabaseDriver implementation which enables seamless Protocol Buffer persistence with Google Cloud + Spanner, for any Message-derived (schema-driven) business model in a given Gust app's ecosystem.
    +
    SpannerDriverSettings.DefaultSettings +
    Concrete hard-coded driver defaults.
    +
    SpannerGeneratedDDL +
    Container for generated schema-driven Spanner DDL.
    +
    SpannerGeneratedDDL.Builder +
    Build properties for a generated Spanner table DDL statement, based on a given model instance as a base for + configuring the table name (via annotations / calculated defaults) and set of typed Spanner value columns.
    +
    SpannerGeneratedDDL.InterleaveTarget +
    Specify a parent table against which this table is interleaved.
    +
    SpannerGeneratedDDL.TableConstraint +
    Specifies a generic table constraint to include in a DDL statement.
    +
    SpannerManager +
    Main adapter manager interface for interaction between Gust/Elide apps and Google Cloud Spanner, enabling seamless + persistence for generated Message-driven models.
    +
    SpannerMutationSerializer<Model extends com.google.protobuf.Message> +
    Implements a specialized serializer, capable of converting generated Message-derived objects into Spanner + Mutation records during write operations.
    +
    SpannerStructDeserializer<Model extends com.google.protobuf.Message> +
    Implements a specialized de-serializer, capable of converting runtime-inhabited Spanner Struct-records into + typed Message-derived objects.
    +
    SpannerTemporalConverter +
    Serialize back and forth between Google Cloud / Protocol Buffer standard TIMESTAMP and DATE records.
    +
    SpannerTransportConfig +
    Configures gRPC transport channels for use with Google Cloud Spanner, either in production or emulated circumstances + for unit and integration testing.
    +
    SpannerUtil +
    Provides utilities related to operations with Spanner, including tools for resolving column names and types from + models and annotations, and producing default sets of columns for DDL statements.
    +
    +
  • +
  • + + + + + + + + + + + + + + + + +
    Enum Summary 
    EnumDescription
    SpannerGeneratedDDL.PropagatedAction +
    Specifies options for reference action propagation (i.e.
    +
    SpannerGeneratedDDL.SortDirection +
    Sort direction settings which can apply to columns.
    +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/package-tree.html b/docs/java/gust/backend/driver/spanner/package-tree.html new file mode 100644 index 000000000..8c5a3a301 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/package-tree.html @@ -0,0 +1,200 @@ + + + + + +gust.backend.driver.spanner Class Hierarchy + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package gust.backend.driver.spanner

+Package Hierarchies: + +
+
+
+

Class Hierarchy

+ +
+
+

Interface Hierarchy

+ +
+
+

Enum Hierarchy

+ +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/driver/spanner/package-use.html b/docs/java/gust/backend/driver/spanner/package-use.html new file mode 100644 index 000000000..948973371 --- /dev/null +++ b/docs/java/gust/backend/driver/spanner/package-use.html @@ -0,0 +1,277 @@ + + + + + +Uses of Package gust.backend.driver.spanner + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Package
gust.backend.driver.spanner

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/CacheDriver.html b/docs/java/gust/backend/model/CacheDriver.html new file mode 100644 index 000000000..882bdc937 --- /dev/null +++ b/docs/java/gust/backend/model/CacheDriver.html @@ -0,0 +1,420 @@ + + + + + +CacheDriver + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface CacheDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message>

+
+
+
+
    +
  • +
    +
    All Known Implementing Classes:
    +
    InMemoryCache
    +
    +
    +
    public interface CacheDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message>
    +
    Describes the surface of a cache driver, which is a partner object to a PersistenceDriver specifically + tailored to deal with caching engines. Cache drivers may be used with any ModelAdapter implementation for + transparent read-through caching support. + +

    Caches implemented in this manner are expected to adhere to options defined on CacheOptions, particularly + with regard to eviction and timeouts. Specific implementations may extend that interface to define custom options, + which may be provided to the implementation at runtime either via stubbed options parameters or app config.

    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods Default Methods 
      Modifier and TypeMethodDescription
      default ReactiveFutureevict​(java.lang.Iterable<Key> keys, + com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Force-evict the set of cached records specified by keys, in the cache managed by this driver.
      +
      ReactiveFuture<Key>evict​(Key key, + com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Force-evict any cached record at the provided key in the cache managed by this driver.
      +
      ReactiveFuture<java.util.Optional<Model>>fetch​(Key key, + FetchOptions options, + com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Attempt to resolve a known model, addressed by key, from the cache powered/backed by this driver, according + to options and making use of executor.
      +
      ReactiveFutureflush​(com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Flush the entire cache managed by this driver.
      +
      ReactiveFutureput​(Key key, + Model model, + com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Write a record (model) at key into the cache, overwriting any model currently stored at the same + key, if applicable.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        flush

        +
        @Nonnull
        +ReactiveFuture flush​(@Nonnull
        +                     com.google.common.util.concurrent.ListeningScheduledExecutorService executor)
        +
        Flush the entire cache managed by this driver. This should drop all keys related to model instance caching that are + currently held by the cache.
        +
        +
        Parameters:
        +
        executor - Executor to use for any async operations.
        +
        Returns:
        +
        Future, which simply completes when the flush is done.
        +
        +
      • +
      + + + + + +
        +
      • +

        put

        +
        @Nonnull
        +ReactiveFuture put​(@Nonnull
        +                   Key key,
        +                   @Nonnull
        +                   Model model,
        +                   @Nonnull
        +                   com.google.common.util.concurrent.ListeningScheduledExecutorService executor)
        +
        Write a record (model) at key into the cache, overwriting any model currently stored at the same + key, if applicable. The resulting future completes with no value, when the cache write has finished, to let the + framework know the cache is done following up.
        +
        +
        Parameters:
        +
        key - Key for the record we should inject into the cache.
        +
        model - Record data to inject into the cache.
        +
        executor - Executor to use for any async operations.
        +
        Returns:
        +
        Future, which simply completes when the write is done.
        +
        +
      • +
      + + + + + +
        +
      • +

        evict

        +
        @Nonnull
        +ReactiveFuture<Keyevict​(@Nonnull
        +                          Key key,
        +                          @Nonnull
        +                          com.google.common.util.concurrent.ListeningScheduledExecutorService executor)
        +
        Force-evict any cached record at the provided key in the cache managed by this driver. This operation is + expected to succeed in all cases and perform its work in an idempotent manner.
        +
        +
        Parameters:
        +
        key - Key for the record to force-evict from the cache.
        +
        executor - Executor to use for any async operations.
        +
        Returns:
        +
        Future, which resolves to the evicted key when the operation completes.
        +
        +
      • +
      + + + +
        +
      • +

        evict

        +
        @Nonnull
        +default ReactiveFuture evict​(@Nonnull
        +                             java.lang.Iterable<Key> keys,
        +                             @Nonnull
        +                             com.google.common.util.concurrent.ListeningScheduledExecutorService executor)
        +
        Force-evict the set of cached records specified by keys, in the cache managed by this driver. This + operation is expected to succeed in all cases and perform its work in an idempotent manner, similar to the single- + key version of this method (see: evict(Message, ListeningScheduledExecutorService)).
        +
        +
        Parameters:
        +
        keys - Set of keys to evict from the cache.
        +
        executor - Executor to sue for any async operations.
        +
        Returns:
        +
        Future, which simply completes when the bulk-evict operation is done.
        +
        +
      • +
      + + + + + +
        +
      • +

        fetch

        +
        @Nonnull
        +ReactiveFuture<java.util.Optional<Model>> fetch​(@Nonnull
        +                                                Key key,
        +                                                @Nonnull
        +                                                FetchOptions options,
        +                                                @Nonnull
        +                                                com.google.common.util.concurrent.ListeningScheduledExecutorService executor)
        +
        Attempt to resolve a known model, addressed by key, from the cache powered/backed by this driver, according + to options and making use of executor. + +

        If no value is available in the cache, Optional.empty() must be returned, which triggers a call to the + driver to resolve the record. If the record can be fetched originally, it will later be added to the cache by a + separate call to put(Message, Message, ListeningScheduledExecutorService).

        +
        +
        Parameters:
        +
        key - Key for the record which we should look for in the cache.
        +
        options - Options to apply to the fetch routine taking place.
        +
        executor - Executor to use for async tasks. Provided by the driver or adapter.
        +
        Returns:
        +
        Future value, which resolves either to Optional.empty() or a wrapped result value.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/CacheOptions.EvictionMode.html b/docs/java/gust/backend/model/CacheOptions.EvictionMode.html new file mode 100644 index 000000000..05a2704d1 --- /dev/null +++ b/docs/java/gust/backend/model/CacheOptions.EvictionMode.html @@ -0,0 +1,447 @@ + + + + + +CacheOptions.EvictionMode + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Enum CacheOptions.EvictionMode

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      LFU +
      Least-Frequently-Used mode for cache eviction.
      +
      LRU +
      Least-Recently-Used mode for cache eviction.
      +
      TTL +
      Flag to enable TTL enforcement.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      java.lang.StringgetLabel() 
      java.lang.StringtoString() 
      static CacheOptions.EvictionModevalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static CacheOptions.EvictionMode[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static CacheOptions.EvictionMode[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (CacheOptions.EvictionMode c : CacheOptions.EvictionMode.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static CacheOptions.EvictionMode valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      + + + + + + + +
        +
      • +

        getLabel

        +
        @Nonnull
        +public java.lang.String getLabel()
        +
        +
        Returns:
        +
        Human-readable label for this eviction mode.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/model/CacheOptions.html b/docs/java/gust/backend/model/CacheOptions.html new file mode 100644 index 000000000..1a0bb5a25 --- /dev/null +++ b/docs/java/gust/backend/model/CacheOptions.html @@ -0,0 +1,410 @@ + + + + + +CacheOptions + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface CacheOptions

+
+
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        enableCache

        +
        @Nonnull
        +default java.lang.Boolean enableCache()
        +
        +
        Returns:
        +
        Whether the cache should be enabled, if installed. Defaults to `true`.
        +
        +
      • +
      + + + +
        +
      • +

        cacheTimeout

        +
        @Nonnull
        +default java.util.Optional<java.lang.Long> cacheTimeout()
        +
        +
        Returns:
        +
        Value to apply to the cache timeout. If left unspecified, the global default is used.
        +
        +
      • +
      + + + +
        +
      • +

        cacheTimeoutUnit

        +
        @Nonnull
        +default java.util.concurrent.TimeUnit cacheTimeoutUnit()
        +
        +
        Returns:
        +
        Unit to apply to the cache timeout. If left unspecified, the global default is used.
        +
        +
      • +
      + + + +
        +
      • +

        cacheDefaultTTL

        +
        @Nonnull
        +default java.util.Optional<java.lang.Long> cacheDefaultTTL()
        +
        +
        Returns:
        +
        Default amount of time to let things remain in the cache.
        +
        +
      • +
      + + + +
        +
      • +

        cacheDefaultTTLUnit

        +
        @Nonnull
        +default java.util.concurrent.TimeUnit cacheDefaultTTLUnit()
        +
        +
        Returns:
        +
        Unit to apply to the default cache lifetime (TTL) value.
        +
        +
      • +
      + + + + +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/CollapsedMessage.Operation.html b/docs/java/gust/backend/model/CollapsedMessage.Operation.html new file mode 100644 index 000000000..f7ea4110f --- /dev/null +++ b/docs/java/gust/backend/model/CollapsedMessage.Operation.html @@ -0,0 +1,314 @@ + + + + + +CollapsedMessage.Operation + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface CollapsedMessage.Operation

+
+
+
+ +
+
+
    +
  • + +
    + +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getPath

        +
        @Nonnull
        +java.lang.String getPath()
        +
        +
        Returns:
        +
        Path for this write.
        +
        +
      • +
      + + + + + + + +
        +
      • +

        execute

        +
        void execute​(@Nullable
        +             java.lang.String prefix,
        +             @Nonnull
        +             WriteProxy<java.lang.Object> proxy,
        +             @Nonnull
        +             java.util.Optional<gust.backend.model.CollapsedMessage.Parent> scope)
        +
        Execute the underlying operation against the provided write proxy. If there is an additional runtime path + prefix, to apply, it is supplied here. Calling this method is sufficient to see change in underlying storage.
        +
        +
        Parameters:
        +
        prefix - Additional path prefix to apply before executing the operation.
        +
        proxy - Write proxy to send our write invocations to.
        +
        scope - Scope of the write, which dictates parent context.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/CollapsedMessage.html b/docs/java/gust/backend/model/CollapsedMessage.html new file mode 100644 index 000000000..e960f098d --- /dev/null +++ b/docs/java/gust/backend/model/CollapsedMessage.html @@ -0,0 +1,339 @@ + + + + + +CollapsedMessage + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class CollapsedMessage

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.model.CollapsedMessage
    • +
    +
  • +
+
+
    +
  • +
    +
    public final class CollapsedMessage
    +extends java.lang.Object
    +
    Describes a generic, collapsed message (i.e. serialized for storage). When using the universal model layer (based on + Protocol Buffer messages), objects can be seamlessly serialized or deserialized to/from key value storage based on + this layer / set of objects. + +

    In particular, collapsed messages are useful with the Firestore adapter - and other data storage adapters which + adopt parent-child style hierarchy. This object is not needed for in-memory storage.

    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Nested Class Summary

      + + + + + + + + + + + + +
      Nested Classes 
      Modifier and TypeClassDescription
      static interface CollapsedMessage.Operation +
      Describes a generic collapsed data store operation.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static CollapsedMessageof​(java.util.List<CollapsedMessage.Operation> operations) +
      Wrap a set of pre-built operations in a collapsed message record.
      +
      voidpersist​(java.lang.String prefix, + WriteProxy<?> proxy) +
      Execute the collapsed message against the provided WriteProxy, which creates it in underlying storage (once + any wrapping transaction finishes).
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        of

        +
        @Nonnull
        +public static CollapsedMessage of​(@Nonnull
        +                                  java.util.List<CollapsedMessage.Operation> operations)
        +
        Wrap a set of pre-built operations in a collapsed message record.
        +
        +
        Parameters:
        +
        operations - Set of operations to execute against underlying storage.
        +
        Returns:
        +
        Pre-filled collapsed message.
        +
        +
      • +
      + + + +
        +
      • +

        persist

        +
        public void persist​(@Nullable
        +                    java.lang.String prefix,
        +                    @Nonnull
        +                    WriteProxy<?> proxy)
        +
        Execute the collapsed message against the provided WriteProxy, which creates it in underlying storage (once + any wrapping transaction finishes).
        +
        +
        Parameters:
        +
        prefix - Path prefix to apply to all sub-writes.
        +
        proxy - Write proxy to employ.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/CollapsedMessageCodec.html b/docs/java/gust/backend/model/CollapsedMessageCodec.html new file mode 100644 index 000000000..9ca05f576 --- /dev/null +++ b/docs/java/gust/backend/model/CollapsedMessageCodec.html @@ -0,0 +1,408 @@ + + + + + +CollapsedMessageCodec + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class CollapsedMessageCodec<Model extends com.google.protobuf.Message,​ReadIntermediate>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.model.CollapsedMessageCodec<Model,​ReadIntermediate>
    • +
    +
  • +
+
+
    +
  • +
    +
    Type Parameters:
    +
    Model -
    +
    ReadIntermediate -
    +
    +
    +
    All Implemented Interfaces:
    +
    ModelCodec<Model,​CollapsedMessage,​ReadIntermediate>
    +
    +
    +
    @Factory
    +@Immutable
    +@ThreadSafe
    +public final class CollapsedMessageCodec<Model extends com.google.protobuf.Message,​ReadIntermediate>
    +extends java.lang.Object
    +implements ModelCodec<Model,​CollapsedMessage,​ReadIntermediate>
    +
  • +
+
+
+ +
+
+ +
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/CollapsedMessageSerializer.html b/docs/java/gust/backend/model/CollapsedMessageSerializer.html new file mode 100644 index 000000000..1150b2344 --- /dev/null +++ b/docs/java/gust/backend/model/CollapsedMessageSerializer.html @@ -0,0 +1,240 @@ + + + + + +CollapsedMessageSerializer + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface CollapsedMessageSerializer<Model extends com.google.protobuf.Message>

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

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/DatabaseAdapter.html b/docs/java/gust/backend/model/DatabaseAdapter.html new file mode 100644 index 000000000..872d3f677 --- /dev/null +++ b/docs/java/gust/backend/model/DatabaseAdapter.html @@ -0,0 +1,362 @@ + + + + + +DatabaseAdapter + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface DatabaseAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadRecord,​WriteRecord>

+
+
+
+
    +
  • +
    +
    Type Parameters:
    +
    Key - Type of key used to uniquely address models.
    +
    Model - Message type which this database adapter is handling.
    +
    +
    +
    All Superinterfaces:
    +
    ModelAdapter<Key,​Model,​ReadRecord,​WriteRecord>, PersistenceDriver<Key,​Model,​ReadRecord,​WriteRecord>
    +
    +
    +
    All Known Implementing Classes:
    +
    FirestoreAdapter, SpannerAdapter
    +
    +
    +
    public interface DatabaseAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadRecord,​WriteRecord>
    +extends ModelAdapter<Key,​Model,​ReadRecord,​WriteRecord>
    +
    Extends the standard ModelAdapter interface with rich persistence features, including querying, indexing, and + other stuff one would expect when interacting with a full database.
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + + + + + +
        +
      • +

        generateKey

        +
        @Nonnull
        +default Key generateKey​(@Nonnull
        +                        com.google.protobuf.Message instance)
        +
        Generate a key for a new entity, which must be stored by this driver, but does not yet have a key. If the driver + does not support key generation, UnsupportedOperationException is thrown. + +

        Generated keys are expected to be best-effort unique. Generally, Java's built-in UUID should + do the trick just fine. In more complex or scalable circumstances, this method can be overridden to reach out to + the data engine to generate a key.

        +
        +
        Specified by:
        +
        generateKey in interface ModelAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadRecord,​WriteRecord>
        +
        Specified by:
        +
        generateKey in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadRecord,​WriteRecord>
        +
        Parameters:
        +
        instance - Default instance of the model type for which a key is desired.
        +
        Returns:
        +
        Generated key for an entity to be stored.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/DatabaseDriver.html b/docs/java/gust/backend/model/DatabaseDriver.html new file mode 100644 index 000000000..69cb914b3 --- /dev/null +++ b/docs/java/gust/backend/model/DatabaseDriver.html @@ -0,0 +1,257 @@ + + + + + +DatabaseDriver + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface DatabaseDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadRecord,​WriteRecord>

+
+
+
+
    +
  • +
    +
    All Superinterfaces:
    +
    PersistenceDriver<Key,​Model,​ReadRecord,​WriteRecord>
    +
    +
    +
    All Known Implementing Classes:
    +
    FirestoreDriver, SpannerDriver
    +
    +
    +
    public interface DatabaseDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadRecord,​WriteRecord>
    +extends PersistenceDriver<Key,​Model,​ReadRecord,​WriteRecord>
    +
  • +
+
+
+ +
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/DatabaseManager.html b/docs/java/gust/backend/model/DatabaseManager.html new file mode 100644 index 000000000..580c1fe12 --- /dev/null +++ b/docs/java/gust/backend/model/DatabaseManager.html @@ -0,0 +1,200 @@ + + + + + +DatabaseManager + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface DatabaseManager<Adapter extends DatabaseAdapter,​Driver extends DatabaseDriver>

+
+
+
+ +
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/DeleteOptions.html b/docs/java/gust/backend/model/DeleteOptions.html new file mode 100644 index 000000000..eacab74a0 --- /dev/null +++ b/docs/java/gust/backend/model/DeleteOptions.html @@ -0,0 +1,296 @@ + + + + + +DeleteOptions + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface DeleteOptions

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

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/EncodedModel.html b/docs/java/gust/backend/model/EncodedModel.html new file mode 100644 index 000000000..1f240c069 --- /dev/null +++ b/docs/java/gust/backend/model/EncodedModel.html @@ -0,0 +1,542 @@ + + + + + +EncodedModel + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class EncodedModel

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.model.EncodedModel
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Cloneable
    +
    +
    +
    @Immutable
    +@ThreadSafe
    +public final class EncodedModel
    +extends java.lang.Object
    +implements java.io.Serializable, java.lang.Cloneable
    +
    Container class for an encoded Protocol Buffer model. Holds raw encoded model data, in any of Protobuf's built-in + well-defined serialization formats (for instance BINARY or JSON). + +

    Raw model data is encoded before being held by this record. In addition to holding the raw data, it also keeps + the fully-qualified path to the model that the data came from, and the serialization format the data lives in. After + being wrapped in this class, a batch of model data is additionally compliant with Serializable.

    +
    +
    See Also:
    +
    Serialized Form
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      protected EncodedModelclone()
      booleanequals​(java.lang.Object o)
      static EncodedModelfrom​(com.google.protobuf.Message message) +
      Return an encoded representation of the provided message.
      +
      static EncodedModelfrom​(com.google.protobuf.Message message, + com.google.protobuf.Descriptors.Descriptor descriptor) +
      Return an encoded representation of the provided message.
      +
      EncodingModegetDataMode() 
      com.google.protobuf.ByteStringgetRawBytes() 
      java.lang.StringgetType() 
      inthashCode()
      <Model extends com.google.protobuf.Message>
      Model
      inflate​(com.google.protobuf.Message model) +
      Re-inflate the encoded model data held by this object, into an instance of Model, via the provided + builder.
      +
      java.lang.StringtoString()
      static EncodedModelwrap​(java.lang.String type, + EncodingMode mode, + byte[] data) +
      Wrap a blob of opaque data, asserting that it is actually an encoded model record.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +finalize, getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        from

        +
        public static EncodedModel from​(@Nonnull
        +                                com.google.protobuf.Message message)
        +
        Return an encoded representation of the provided message. This method is rather heavy-weight: it fully encodes the + provided Protobuf message into the Protocol Buffers binary format. + +

        Note that this method must access the message's descriptor. If a descriptor is already on-hand, use the other + variant of this method.

        +
        +
        Parameters:
        +
        message - Message to encode as binary.
        +
        Returns:
        +
        Java-serializable wrapper including the encoded Protobuf model.
        +
        See Also:
        +
        If a descriptor is already on-hand for the provided message.
        +
        +
      • +
      + + + +
        +
      • +

        from

        +
        public static EncodedModel from​(@Nonnull
        +                                com.google.protobuf.Message message,
        +                                @Nullable
        +                                com.google.protobuf.Descriptors.Descriptor descriptor)
        +
        Return an encoded representation of the provided message. This method is rather heavy-weight: it fully encodes the + provided Protobuf message into the Protocol Buffers binary format. + +

        If a descriptor is not already on-hand, use the other variant of this method, which accesses the + descriptor directly from the message.

        +
        +
        Parameters:
        +
        message - Message to encode as binary.
        +
        Returns:
        +
        Java-serializable wrapper including the encoded Protobuf model.
        +
        See Also:
        +
        If no descriptor is on-hand.
        +
        +
      • +
      + + + +
        +
      • +

        wrap

        +
        public static EncodedModel wrap​(@Nonnull
        +                                java.lang.String type,
        +                                @Nonnull
        +                                EncodingMode mode,
        +                                @Nonnull
        +                                byte[] data)
        +
        Wrap a blob of opaque data, asserting that it is actually an encoded model record. Using this factory method, any + of the Protobuf serialization formats may be used safely with this class. + +

        All details must be provided manually to this method variant. It is incumbent on the developer that they line + up properly. For safer options, see the other factory methods on this class.

        +
        +
        Parameters:
        +
        type - Fully-qualified type name, for the encoded instance we are storing.
        +
        data - Raw data for the encoded model to be wrapped.
        +
        Returns:
        +
        Encoded model instance.
        +
        See Also:
        +
        To encode a model instance., +To encode a model instance with a descriptor already in-hand.
        +
        +
      • +
      + + + +
        +
      • +

        clone

        +
        protected EncodedModel clone()
        +
        +
        Overrides:
        +
        clone in class java.lang.Object
        +
        +
      • +
      + + + +
        +
      • +

        equals

        +
        public boolean equals​(java.lang.Object o)
        +
        +
        Overrides:
        +
        equals in class java.lang.Object
        +
        +
      • +
      + + + +
        +
      • +

        hashCode

        +
        public int hashCode()
        +
        +
        Overrides:
        +
        hashCode in class java.lang.Object
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      + + + +
        +
      • +

        getRawBytes

        +
        @Nonnull
        +public com.google.protobuf.ByteString getRawBytes()
        +
        +
        Returns:
        +
        Raw bytes held by this encoded model.
        +
        +
      • +
      + + + +
        +
      • +

        getType

        +
        @Nonnull
        +public java.lang.String getType()
        +
        +
        Returns:
        +
        Fully-qualified path to the type of model backing this encoded instance.
        +
        +
      • +
      + + + +
        +
      • +

        getDataMode

        +
        @Nonnull
        +public EncodingMode getDataMode()
        +
        +
        Returns:
        +
        Operating mode for the underlying model instance data.
        +
        +
      • +
      + + + +
        +
      • +

        inflate

        +
        @Nonnull
        +public <Model extends com.google.protobuf.Message> Model inflate​(@Nonnull
        +                                                                 com.google.protobuf.Message model)
        +                                                          throws com.google.protobuf.InvalidProtocolBufferException
        +
        Re-inflate the encoded model data held by this object, into an instance of Model, via the provided + builder. + +

        Note: before the model is returned from this method, it will be casted to match the generic type the user + is looking for. It is incumbent on the invoking developer to make sure the generic access that occurs won't produce + a ClassCastException. getType() can be interrogated to resolve types before inflation.

        +
        +
        Type Parameters:
        +
        Model - Generic model type inflated and returned by this method.
        +
        Parameters:
        +
        model - Empty model instance from which to resolve a parser.
        +
        Returns:
        +
        Instance of the model, inflated from the encoded data.
        +
        Throws:
        +
        com.google.protobuf.InvalidProtocolBufferException - If the held data is incorrectly formatted.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/EncodingMode.html b/docs/java/gust/backend/model/EncodingMode.html new file mode 100644 index 000000000..b7996d7cd --- /dev/null +++ b/docs/java/gust/backend/model/EncodingMode.html @@ -0,0 +1,388 @@ + + + + + +EncodingMode + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Enum EncodingMode

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Enum<EncodingMode>
    • +
    • +
        +
      • gust.backend.model.EncodingMode
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Comparable<EncodingMode>
    +
    +
    +
    public enum EncodingMode
    +extends java.lang.Enum<EncodingMode>
    +
    Wire format mode to apply when serializing or de-serializing.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      BINARY +
      Use Protobuf binary serialization.
      +
      JSON +
      Use Protobuf JSON serialization.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static EncodingModevalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static EncodingMode[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static EncodingMode[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (EncodingMode c : EncodingMode.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static EncodingMode valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/model/FetchOptions.MaskMode.html b/docs/java/gust/backend/model/FetchOptions.MaskMode.html new file mode 100644 index 000000000..5c39107df --- /dev/null +++ b/docs/java/gust/backend/model/FetchOptions.MaskMode.html @@ -0,0 +1,408 @@ + + + + + +FetchOptions.MaskMode + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Enum FetchOptions.MaskMode

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      EXCLUDE +
      Omit fields mentioned in the field mask.
      +
      INCLUDE +
      Only include fields mentioned in the field mask.
      +
      PROJECTION +
      Treat the field mask as a projection, for query purposes only.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static FetchOptions.MaskModevalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static FetchOptions.MaskMode[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static FetchOptions.MaskMode[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (FetchOptions.MaskMode c : FetchOptions.MaskMode.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static FetchOptions.MaskMode valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/model/FetchOptions.html b/docs/java/gust/backend/model/FetchOptions.html new file mode 100644 index 000000000..409d3fe17 --- /dev/null +++ b/docs/java/gust/backend/model/FetchOptions.html @@ -0,0 +1,396 @@ + + + + + +FetchOptions + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface FetchOptions

+
+
+
+ +
+
+ +
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        fieldMask

        +
        @Nonnull
        +default java.util.Optional<com.google.protobuf.FieldMask> fieldMask()
        +
        +
        Returns:
        +
        Field mask to apply when fetching properties. Fields not mentioned in the mask will be omitted.
        +
        +
      • +
      + + + + + + + +
        +
      • +

        snapshot

        +
        @Nonnull
        +default java.util.Optional<java.lang.Long> snapshot()
        +
        +
        Returns:
        +
        Read snapshot time, if applicable.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/model/InvalidModelType.html b/docs/java/gust/backend/model/InvalidModelType.html new file mode 100644 index 000000000..d8fb52e3e --- /dev/null +++ b/docs/java/gust/backend/model/InvalidModelType.html @@ -0,0 +1,328 @@ + + + + + +InvalidModelType + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class InvalidModelType

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable
    +
    +
    +
    public final class InvalidModelType
    +extends PersistenceException
    +
    Specifies an error, wherein a user has requested a data adapter or some other database object, for a model which is + not usable with data storage systems (via annotations).
    +
    +
    See Also:
    +
    Serialized Form
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      java.util.Set<tools.elide.core.DatapointType>getDatapointTypes() 
      com.google.protobuf.Descriptors.DescriptorgetViolatingSchema() 
      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getViolatingSchema

        +
        public com.google.protobuf.Descriptors.Descriptor getViolatingSchema()
        +
        +
        Returns:
        +
        Message instance that violated this type constraint.
        +
        +
      • +
      + + + +
        +
      • +

        getDatapointTypes

        +
        public java.util.Set<tools.elide.core.DatapointType> getDatapointTypes()
        +
        +
        Returns:
        +
        Allowed types which the violating message did not match.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/MissingAnnotatedField.html b/docs/java/gust/backend/model/MissingAnnotatedField.html new file mode 100644 index 000000000..f2482d450 --- /dev/null +++ b/docs/java/gust/backend/model/MissingAnnotatedField.html @@ -0,0 +1,329 @@ + + + + + +MissingAnnotatedField + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class MissingAnnotatedField

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      tools.elide.core.FieldTypegetRequiredField() 
      com.google.protobuf.Descriptors.DescriptorgetViolatingSchema() 
      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getViolatingSchema

        +
        @Nonnull
        +public com.google.protobuf.Descriptors.Descriptor getViolatingSchema()
        +
        +
        Returns:
        +
        Descriptor for the schema type which violated the required-field constraint.
        +
        +
      • +
      + + + +
        +
      • +

        getRequiredField

        +
        @Nonnull
        +public tools.elide.core.FieldType getRequiredField()
        +
        +
        Returns:
        +
        The type of field that must be added to pass the failing constraint.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/ModelAdapter.html b/docs/java/gust/backend/model/ModelAdapter.html new file mode 100644 index 000000000..8ecbe9947 --- /dev/null +++ b/docs/java/gust/backend/model/ModelAdapter.html @@ -0,0 +1,585 @@ + + + + + +ModelAdapter + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface ModelAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadIntermediate,​WriteIntermediate>

+
+
+
+
    +
  • +
    +
    Type Parameters:
    +
    Key - Key type, instances of which uniquely address instances of Model.
    +
    Model - Model type which this adapter is responsible for adapting.
    +
    ReadIntermediate - Intermediate record format used by the implementation when de-serializing model instances.
    +
    WriteIntermediate - Intermediate record format used when serializing model instances for write.
    +
    +
    +
    All Superinterfaces:
    +
    PersistenceDriver<Key,​Model,​ReadIntermediate,​WriteIntermediate>
    +
    +
    +
    All Known Subinterfaces:
    +
    DatabaseAdapter<Key,​Model,​ReadRecord,​WriteRecord>
    +
    +
    +
    All Known Implementing Classes:
    +
    FirestoreAdapter, InMemoryAdapter, SpannerAdapter
    +
    +
    +
    public interface ModelAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadIntermediate,​WriteIntermediate>
    +extends PersistenceDriver<Key,​Model,​ReadIntermediate,​WriteIntermediate>
    +
    Specifies an adapter for data models. "Adapters" are responsible for handling data storage and recall, and generic + model serialization and deserialization activities. Adapters are composed of a handful of components, which together + define the functionality that composes the adapter writ-large. + +

    Major components of functionality are described below: +

      +
    • Codec: The ModelCodec is responsible for serialization and deserialization. In some cases, + codecs can be mixed with other objects to customize how data is stored. For example, the Redis cache layer supports + using ProtoJSON, Protobuf binary, or JVM serialization, optionally with compression. On the other hand, the + Firestore adapter specifies its own codecs which serialize into Firestore models.
    • +
    • Driver: The PersistenceDriver is responsible for persisting serialized/collapsed models into + underlying storage, deleting data recalling data via key fetches, and querying indexes to produce result-sets.
    • +

    +
    +
    See Also:
    +
    Interface which defines basic driver functionality., +Cache-specific persistence driver support, included in this object., +Extends this interface with richer data engine features.
    +
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        cache

        +
        @Nonnull
        +java.util.Optional<CacheDriver<Key,​Model>> cache()
        +
        Return the cache driver in use for this particular model adapter. If a cache driver is present, and active/enabled + according to database driver settings, it will be used on read-paths (such as fetching objects by ID).
        +
        +
        Returns:
        +
        Cache driver currently in use by this model adapter.
        +
        +
      • +
      + + + + + + + +
        +
      • +

        executorService

        +
        @Nonnull
        +default com.google.common.util.concurrent.ListeningScheduledExecutorService executorService()
        +
        Resolve an executor service for use with this persistence driver. Operations will be executed against this as they + are received.
        +
        +
        Specified by:
        +
        executorService in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadIntermediate,​WriteIntermediate>
        +
        Returns:
        +
        Scheduled executor service.
        +
        +
      • +
      + + + +
        +
      • +

        generateKey

        +
        @Nonnull
        +default Key generateKey​(@Nonnull
        +                        com.google.protobuf.Message instance)
        +
        Generate a key for a new entity, which must be stored by this driver, but does not yet have a key. If the driver + does not support key generation, UnsupportedOperationException is thrown. + +

        Generated keys are expected to be best-effort unique. Generally, Java's built-in UUID should + do the trick just fine. In more complex or scalable circumstances, this method can be overridden to reach out to + the data engine to generate a key.

        +
        +
        Specified by:
        +
        generateKey in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadIntermediate,​WriteIntermediate>
        +
        Parameters:
        +
        instance - Default instance of the model type for which a key is desired.
        +
        Returns:
        +
        Generated key for an entity to be stored.
        +
        +
      • +
      + + + + + + + + + + + +
        +
      • +

        persist

        +
        @Nonnull
        +default ReactiveFuture<Modelpersist​(@Nullable
        +                                      Key key,
        +                                      @Nonnull
        +                                      Model model,
        +                                      @Nonnull
        +                                      WriteOptions options)
        +
        Low-level record persistence method. Effectively called by all other create/put variants. Asynchronously write a + data model instance to storage, which will populate the provided ReactiveFuture value. + +

        Optionally, a key may be provided as a nominated value to the storage engine. Whether the engine accepts + nominated keys is up to the implementation. In all cases, the engine must return the key used to store and address + the value henceforth. If the engine does support nominated keys, it must operate in an idempotent + manner with regard to those keys. In other words, repeated calls to create the same entity with the same key will + not cause spurious side-effects - only one record will be created, with the remaining calls being rejected by the + underlying engine.

        + +

        All futures emitted via the persistence framework (and Gust writ-large) are ListenableFuture-compliant + implementations under the hood, but ReactiveFuture allows a model-layer result to be used as a + Future, or a one-item reactive Publisher.

        + +

        This method additionally enables specification of custom WriteOptions, which are applied on a per- + operation basis to override global defaults.

        + +

        Exceptions: Instead of throwing a PersistenceException as other methods do, this operation will + emit the exception over the Future channel instead, or raise the exception in the event + Future.get() is called to surface it in the invoking (or dependent) code.

        +
        +
        Specified by:
        +
        persist in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadIntermediate,​WriteIntermediate>
        +
        Parameters:
        +
        key - Key nominated by invoking code for storing this record. If no key is provided, the underlying storage + engine is expected to allocate one. Where unsupported, PersistenceException will be thrown.
        +
        model - Model to store at the specified key, if provided.
        +
        options - Options to apply to this persist operation.
        +
        Returns:
        +
        Reactive future, which resolves to the key where the provided model is now stored. In no case should this + method return null. Instead, PersistenceException will be thrown.
        +
        +
      • +
      + + + + + +
        +
      • +

        delete

        +
        @Nonnull
        +default ReactiveFuture<Keydelete​(@Nonnull
        +                                   Key key,
        +                                   @Nonnull
        +                                   DeleteOptions options)
        +
        Low-level record delete method. Effectively called by all other delete variants. Asynchronously and permanently + erase an existing data model instance from storage, addressed by its key unique key or ID. + +

        If no key or ID field, or value, may be located, an error is raised (see below for details). This operation is + expected to operate in an idempotent manner (i.e. repeated calls with identical parameters do not yield + different side effects). Calls referring to an already-deleted entity should silently succeed.

        + +

        All futures emitted via the persistence framework (and Gust writ-large) are ListenableFuture-compliant + implementations under the hood, but ReactiveFuture allows a model-layer result to be used as a + Future, or a one-item reactive Publisher.

        + +

        This method additionally enables specification of custom DeleteOptions, which are applied on a per- + operation basis to override global defaults.

        + +

        Exceptions: Instead of throwing a PersistenceException as other methods do, this operation will + emit the exception over the Future channel instead, or raise the exception in the event + Future.get() is called to surface it in the invoking (or dependent) code.

        +
        +
        Specified by:
        +
        delete in interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadIntermediate,​WriteIntermediate>
        +
        Parameters:
        +
        key - Unique key referring to the record in storage that should be deleted.
        +
        options - Options to apply to this specific delete operation.
        +
        Returns:
        +
        Future value, which resolves to the deleted record's key when the operation completes.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/ModelCodec.html b/docs/java/gust/backend/model/ModelCodec.html new file mode 100644 index 000000000..5ecae563b --- /dev/null +++ b/docs/java/gust/backend/model/ModelCodec.html @@ -0,0 +1,406 @@ + + + + + +ModelCodec + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface ModelCodec<Model extends com.google.protobuf.Message,​WriteIntermediate,​ReadIntermediate>

+
+
+
+
    +
  • +
    +
    Type Parameters:
    +
    Model - Model type which this codec is responsible for serializing and de-serializing.
    +
    WriteIntermediate - Intermediate record type which this codec converts model instances into.
    +
    +
    +
    All Known Implementing Classes:
    +
    CollapsedMessageCodec, ProtoModelCodec, SpannerCodec
    +
    +
    +
    public interface ModelCodec<Model extends com.google.protobuf.Message,​WriteIntermediate,​ReadIntermediate>
    +
    Specifies the requisite interface for a data codec implementation. These objects are responsible for performing model + serialization and deserialization, within different circumstances. For example, models are used with databases via + adapters that serialize each model into a corresponding database object, or series of database calls. + +

    Adapters are bi-directional, i.e., they must support transitioning both to and from message + representations, based on circumstance. Services are the only case where this is generally not necessary, because + gRPC handles serialization automatically.

    +
    +
    See Also:
    +
    Surface definition for a model serializer., +Surface definition for a model de-serializer.
    +
    +
  • +
+
+
+ +
+
+ +
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/ModelDeflateException.html b/docs/java/gust/backend/model/ModelDeflateException.html new file mode 100644 index 000000000..9a5fbd61e --- /dev/null +++ b/docs/java/gust/backend/model/ModelDeflateException.html @@ -0,0 +1,259 @@ + + + + + +ModelDeflateException + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class ModelDeflateException

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/ModelDeserializer.DeserializationError.html b/docs/java/gust/backend/model/ModelDeserializer.DeserializationError.html new file mode 100644 index 000000000..d974d92f8 --- /dev/null +++ b/docs/java/gust/backend/model/ModelDeserializer.DeserializationError.html @@ -0,0 +1,258 @@ + + + + + +ModelDeserializer.DeserializationError + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class ModelDeserializer.DeserializationError

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Throwable
    • +
    • +
        +
      • java.lang.Exception
      • +
      • +
          +
        • java.lang.RuntimeException
        • +
        • +
            +
          • gust.backend.model.ModelDeserializer.DeserializationError
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/ModelDeserializer.html b/docs/java/gust/backend/model/ModelDeserializer.html new file mode 100644 index 000000000..f133ff0f0 --- /dev/null +++ b/docs/java/gust/backend/model/ModelDeserializer.html @@ -0,0 +1,317 @@ + + + + + +ModelDeserializer + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface ModelDeserializer<Input,​Model extends com.google.protobuf.Message>

+
+
+
+
    +
  • +
    +
    Type Parameters:
    +
    Input - Input data type, which this de-serializer will convert into a message instance.
    +
    Model - Message object type, which is the output of this de-serializer.
    +
    +
    +
    All Known Implementing Classes:
    +
    SpannerStructDeserializer
    +
    +
    +
    public interface ModelDeserializer<Input,​Model extends com.google.protobuf.Message>
    +
    Describes the interface for a de-serializer, which is responsible for transitioning (adapting) objects from a given + type (or tree of types) to Message instances, corresponding with the data record type managed by this object. + +

    Deserializers are part of a wider set of objects, including the inverse of this object, which is called a + ModelSerializer. Generally these objects come in matching pairs, but sometimes they are mixed and matched as + needed. Pairs of serializers/de-serializers are grouped by a ModelCodec.

    +
    +
    See Also:
    +
    The inverse of this object, which is responsible for adapting instances to some + other type of object or data.
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Nested Class Summary

      + + + + + + + + + + + + +
      Nested Classes 
      Modifier and TypeInterfaceDescription
      static class ModelDeserializer.DeserializationError +
      Describes errors that occur during model deserialization or inflation activities.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods 
      Modifier and TypeMethodDescription
      Modelinflate​(Input input) +
      De-serialize a model instance from the provided input type, throwing exceptions verbosely if we are unable to + correctly, verifiably, and properly load the record.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + + + +
        +
      • +

        inflate

        +
        @Nonnull
        +Model inflate​(@Nonnull
        +              Input input)
        +       throws ModelInflateException,
        +              java.io.IOException
        +
        De-serialize a model instance from the provided input type, throwing exceptions verbosely if we are unable to + correctly, verifiably, and properly load the record.
        +
        +
        Parameters:
        +
        input - Input data or object from which to load the model instance.
        +
        Returns:
        +
        De-serialized and inflated model instance. Always a Message.
        +
        Throws:
        +
        ModelInflateException - If the model fails to load for any reason.
        +
        java.io.IOException - If some IO error occurs.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/ModelInflateException.html b/docs/java/gust/backend/model/ModelInflateException.html new file mode 100644 index 000000000..f701cfcd0 --- /dev/null +++ b/docs/java/gust/backend/model/ModelInflateException.html @@ -0,0 +1,259 @@ + + + + + +ModelInflateException + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class ModelInflateException

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/ModelMetadata.FieldContainer.html b/docs/java/gust/backend/model/ModelMetadata.FieldContainer.html new file mode 100644 index 000000000..035529abb --- /dev/null +++ b/docs/java/gust/backend/model/ModelMetadata.FieldContainer.html @@ -0,0 +1,378 @@ + + + + + +ModelMetadata.FieldContainer + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class ModelMetadata.FieldContainer<V>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.model.ModelMetadata.FieldContainer<V>
    • +
    +
  • +
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        equals

        +
        public boolean equals​(java.lang.Object o)
        +
        +
        Overrides:
        +
        equals in class java.lang.Object
        +
        +
      • +
      + + + +
        +
      • +

        hashCode

        +
        public int hashCode()
        +
        +
        Overrides:
        +
        hashCode in class java.lang.Object
        +
        +
      • +
      + + + + + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      + + + + + + + +
        +
      • +

        getValue

        +
        @Nonnull
        +public java.util.Optional<VgetValue()
        +
        Value associated with the specified field, or Optional.empty() if the field has no initialized value.
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/ModelMetadata.FieldPointer.html b/docs/java/gust/backend/model/ModelMetadata.FieldPointer.html new file mode 100644 index 000000000..dc966f901 --- /dev/null +++ b/docs/java/gust/backend/model/ModelMetadata.FieldPointer.html @@ -0,0 +1,511 @@ + + + + + +ModelMetadata.FieldPointer + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class ModelMetadata.FieldPointer

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.model.ModelMetadata.FieldPointer
    • +
    +
  • +
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        fieldAtName

        +
        @Nonnull
        +public static ModelMetadata.FieldPointer fieldAtName​(@Nonnull
        +                                                     com.google.protobuf.Descriptors.Descriptor model,
        +                                                     @Nonnull
        +                                                     java.lang.String name)
        +
        Wrap the field at the specified name on the provided model.
        +
        +
        Parameters:
        +
        model - Descriptor for a protocol buffer model.
        +
        name - Name of a field to get from the provided buffer model.
        +
        Returns:
        +
        Field pointer wrapping the provided information.
        +
        +
      • +
      + + + +
        +
      • +

        equals

        +
        public boolean equals​(java.lang.Object o)
        +
        +
        Overrides:
        +
        equals in class java.lang.Object
        +
        +
      • +
      + + + +
        +
      • +

        hashCode

        +
        public int hashCode()
        +
        +
        Overrides:
        +
        hashCode in class java.lang.Object
        +
        +
      • +
      + + + + + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      + + + +
        +
      • +

        getParent

        +
        @Nonnull
        +public java.lang.String getParent()
        +
        +
        Returns:
        +
        Path to the specified field.
        +
        +
      • +
      + + + +
        +
      • +

        hasParent

        +
        public boolean hasParent()
        +
        +
        Returns:
        +
        Path to the specified field.
        +
        +
      • +
      + + + +
        +
      • +

        getPath

        +
        @Nonnull
        +public java.lang.String getPath()
        +
        +
        Returns:
        +
        Path to the specified field.
        +
        +
      • +
      + + + +
        +
      • +

        getName

        +
        @Nonnull
        +public java.lang.String getName()
        +
        +
        Returns:
        +
        Simple proto name for the field.
        +
        +
      • +
      + + + +
        +
      • +

        getJsonName

        +
        @Nonnull
        +public java.lang.String getJsonName()
        +
        +
        Returns:
        +
        Simple JSON name for the field.
        +
        +
      • +
      + + + +
        +
      • +

        getBase

        +
        @Nonnull
        +public com.google.protobuf.Descriptors.Descriptor getBase()
        +
        +
        Returns:
        +
        Base model type where the specified path begins.
        +
        +
      • +
      + + + +
        +
      • +

        getField

        +
        @Nonnull
        +public com.google.protobuf.Descriptors.FieldDescriptor getField()
        +
        +
        Returns:
        +
        Descriptor for the targeted field.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/ModelMetadata.html b/docs/java/gust/backend/model/ModelMetadata.html new file mode 100644 index 000000000..20af538ba --- /dev/null +++ b/docs/java/gust/backend/model/ModelMetadata.html @@ -0,0 +1,2363 @@ + + + + + +ModelMetadata + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class ModelMetadata

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.model.ModelMetadata
    • +
    +
  • +
+
+
    +
  • +
    +
    @ThreadSafe
    +public final class ModelMetadata
    +extends java.lang.Object
    +
    Utility helper class, which is responsible for resolving metadata (based on the core framework annotations) from + arbitrary model definitions. + +

    Model "metadata," in this case, refers to annotation-based declarations on the protocol buffer definitions + themselves. As such, the source for most (if not all) of the data provided by this helper is the Descriptors.Descriptor + that accompanies a Java-side protobuf model.

    + +

    Note: Using this class, or the model layer writ-large, requires the full runtime Protobuf library (the lite + runtime for Protobuf in Java does not include descriptors at all, which this class relies on).

    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static java.lang.Iterable<ModelMetadata.FieldPointer>allFields​(com.google.protobuf.Descriptors.Descriptor descriptor) +
      Crawl all fields, recursively, on the descriptor provided.
      +
      static java.lang.Iterable<ModelMetadata.FieldPointer>allFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate) +
      Crawl all fields, recursively, on the descriptor provided.
      +
      static java.lang.Iterable<ModelMetadata.FieldPointer>allFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.lang.Boolean recursive) +
      Crawl all fields, recursively, on the descriptor provided.
      +
      static java.lang.Iterable<ModelMetadata.FieldPointer>allFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.util.function.Predicate<ModelMetadata.FieldPointer> decider) +
      Crawl all fields, recursively, on the descriptor provided.
      +
      static <E> java.util.Optional<ModelMetadata.FieldPointer>annotatedField​(com.google.protobuf.Descriptors.Descriptor descriptor, + com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext) +
      Resolve a ModelMetadata.FieldPointer within the scope of the provided model descriptor, that holds values for the + specified metadata annotation ext.
      +
      static <E> java.util.Optional<ModelMetadata.FieldPointer>annotatedField​(com.google.protobuf.Descriptors.Descriptor descriptor, + com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext, + java.lang.Boolean recursive, + java.util.Optional<java.util.function.Function<E,​java.lang.Boolean>> filter) +
      Resolve a ModelMetadata.FieldPointer within the scope of the provided model descriptor, that holds values for the + specified metadata annotation ext.
      +
      static <E> java.util.Optional<ModelMetadata.FieldPointer>annotatedField​(com.google.protobuf.Descriptors.Descriptor descriptor, + com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext, + java.util.Optional<java.util.function.Function<E,​java.lang.Boolean>> filter) +
      Resolve a ModelMetadata.FieldPointer within the scope of the provided model descriptor, that holds values for the + specified metadata annotation ext.
      +
      static <E> java.util.Optional<ModelMetadata.FieldPointer>annotatedField​(com.google.protobuf.Message instance, + com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext) +
      Resolve a ModelMetadata.FieldPointer within the scope of instance, that holds values for the specified metadata + annotation ext.
      +
      static <E> java.util.Optional<ModelMetadata.FieldPointer>annotatedField​(com.google.protobuf.Message instance, + com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext, + java.lang.Boolean recursive) +
      Resolve a ModelMetadata.FieldPointer within the scope of instance, that holds values for the specified metadata + annotation ext.
      +
      static <E> java.util.Optional<ModelMetadata.FieldPointer>annotatedField​(com.google.protobuf.Message instance, + com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext, + java.lang.Boolean recursive, + java.util.Optional<java.util.function.Function<E,​java.lang.Boolean>> filter) +
      Resolve a ModelMetadata.FieldPointer within the scope of instance, that holds values for the specified metadata + annotation ext.
      +
      static voidenforceAnyRole​(com.google.protobuf.Descriptors.Descriptor descriptor, + tools.elide.core.DatapointType... types) +
      Enforce that a particular schema descriptor matches any of the provided DatapointType + annotations.
      +
      static voidenforceAnyRole​(com.google.protobuf.Message model, + tools.elide.core.DatapointType... types) +
      Enforce that a particular datamodel type matches any of the provided DatapointType + annotations.
      +
      static voidenforceRole​(com.google.protobuf.Descriptors.Descriptor descriptor, + tools.elide.core.DatapointType type) +
      Enforce that a particular datamodel schema descriptor matches the provided DatapointType + annotation.
      +
      static voidenforceRole​(com.google.protobuf.Message model, + tools.elide.core.DatapointType type) +
      Enforce that a particular model instance matches the provided DatapointType annotation.
      +
      static <E> java.util.Optional<E>fieldAnnotation​(com.google.protobuf.Descriptors.FieldDescriptor descriptor, + com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext) +
      Retrieve a field-level annotation, from the provided field schema descriptor, structured by ext.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>forEachField​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate) +
      Crawl all fields, recursively, on the provided descriptor for a model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>forEachField​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + boolean recursive) +
      Crawl all fields, recursively, on the provided descriptor for a model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>forEachField​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.util.function.Predicate<ModelMetadata.FieldPointer> decider) +
      Crawl all fields, recursively, on the provided descriptor for a model instance.
      +
      static java.lang.StringfullyQualifiedName​(com.google.protobuf.Descriptors.Descriptor descriptor) +
      Resolve the fully-qualified type path, or name, for the provided datamodel type descriptor.
      +
      static java.lang.StringfullyQualifiedName​(com.google.protobuf.Message model) +
      Resolve the fully-qualified type path, or name, for the provided datamodel instance.
      +
      static <ID> java.util.Optional<ID>id​(com.google.protobuf.Message instance) +
      Resolve the provided model instance's assigned ID, by walking the property structure for the entity, and returning + either the first id-annotated field's value at the top-level, or the first id-annotated field value + on the first key-annotated message field at the top level of the provided message.
      +
      static java.util.Optional<ModelMetadata.FieldPointer>idField​(com.google.protobuf.Descriptors.Descriptor descriptor) +
      Resolve a pointer to the provided schema type descriptor's ID field, whether or not it has a value.
      +
      static java.util.Optional<ModelMetadata.FieldPointer>idField​(com.google.protobuf.Message instance) +
      Resolve a pointer to the provided model instance's ID field, whether or not it has a value.
      +
      static <Key> java.util.Optional<Key>key​(com.google.protobuf.Message instance) +
      Resolve the provided model instance's assigned KEY instance, by walking the property structure for the + entity, and returning the first key-annotated field's value at the top-level of the provided message.
      +
      static java.util.Optional<ModelMetadata.FieldPointer>keyField​(com.google.protobuf.Descriptors.Descriptor descriptor) +
      Resolve a pointer to the provided schema type descriptor's KEY field, whether or not it has a value + assigned.
      +
      static java.util.Optional<ModelMetadata.FieldPointer>keyField​(com.google.protobuf.Message instance) +
      Resolve a pointer to the provided schema type descriptor's KEY field, whether or not it has a value + assigned.
      +
      static booleanmatchAnyRole​(com.google.protobuf.Descriptors.Descriptor descriptor, + tools.elide.core.DatapointType... types) +
      Check that a particular schema descriptor matches any of the provided DatapointType + annotations.
      +
      static booleanmatchAnyRole​(com.google.protobuf.Message model, + tools.elide.core.DatapointType... types) +
      Check that a particular datamodel type matches any of the provided DatapointType annotations.
      +
      static booleanmatchCollectionAnnotation​(com.google.protobuf.Descriptors.FieldDescriptor field, + tools.elide.core.CollectionMode mode) +
      Match a collection annotation.
      +
      static booleanmatchFieldAnnotation​(com.google.protobuf.Descriptors.FieldDescriptor field, + tools.elide.core.FieldType annotation) +
      Match an annotation to a field.
      +
      static booleanmatchRole​(com.google.protobuf.Descriptors.Descriptor descriptor, + tools.elide.core.DatapointType type) +
      Enforce that a particular datamodel schema descriptor matches any of the provided + DatapointType annotations.
      +
      static booleanmatchRole​(com.google.protobuf.Message model, + tools.elide.core.DatapointType type) +
      Enforce that a particular datamodel type matches any of the provided DatapointType annotations.
      +
      static <E> java.util.Optional<E>modelAnnotation​(com.google.protobuf.Descriptors.Descriptor descriptor, + com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.MessageOptions,​E> ext, + java.lang.Boolean recursive) +
      Retrieve a model-level annotation, from the provided model schema descriptor, structured by ext.
      +
      static <E> java.util.Optional<E>modelAnnotation​(com.google.protobuf.Message instance, + com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.MessageOptions,​E> ext, + java.lang.Boolean recursive) +
      Retrieve a model-level annotation, from instance, structured by ext.
      +
      static <V> ModelMetadata.FieldContainer<V>pluck​(com.google.protobuf.Message instance, + ModelMetadata.FieldPointer fieldPointer) +
      Pluck a field value, addressed by a ModelMetadata.FieldPointer, from the provided instance.
      +
      static <V> ModelMetadata.FieldContainer<V>pluck​(com.google.protobuf.Message instance, + java.lang.String path) +
      Return a single field value container, plucked from the specified deep path, in dot form, using the regular + protobuf-definition names for each field.
      +
      static java.util.SortedSet<ModelMetadata.FieldContainer<java.lang.Object>>pluckAll​(com.google.protobuf.Message instance, + com.google.protobuf.FieldMask mask) +
      Return an iterable containing plucked value containers for each field mentioned in mask, that is present on + instance with an initialized value.
      +
      static java.util.SortedSet<ModelMetadata.FieldContainer<java.lang.Object>>pluckAll​(com.google.protobuf.Message instance, + com.google.protobuf.FieldMask mask, + java.lang.Boolean normalize) +
      Return an iterable containing plucked value containers for each field mentioned in mask, that is present on + instance with an initialized value.
      +
      static java.util.stream.Stream<ModelMetadata.FieldContainer<java.lang.Object>>pluckStream​(com.google.protobuf.Message instance, + com.google.protobuf.FieldMask mask) +
      Return a stream which emits plucked value containers for each field mentioned in mask, that is present on + instance with an initialized value.
      +
      static java.util.stream.Stream<ModelMetadata.FieldContainer<java.lang.Object>>pluckStream​(com.google.protobuf.Message instance, + com.google.protobuf.FieldMask mask, + java.lang.Boolean normalize) +
      Return a stream which emits plucked value containers for each field mentioned in mask, that is present on + instance with an initialized value.
      +
      static java.util.Optional<ModelMetadata.FieldPointer>resolveField​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.lang.String path) +
      Resolve an arbitrary field pointer from the provided model type escriptor, specified by the given + path to the property.
      +
      static java.util.Optional<ModelMetadata.FieldPointer>resolveField​(com.google.protobuf.Message instance, + java.lang.String path) +
      Resolve an arbitrary field pointer from the provided model instance, specified by the given path to + the property.
      +
      static tools.elide.core.DatapointTyperole​(com.google.protobuf.Descriptors.Descriptor descriptor) +
      Resolve the general type for a given datamodel type descriptor.
      +
      static tools.elide.core.DatapointTyperole​(com.google.protobuf.Message model) +
      Resolve the general type for a given datamodel.
      +
      static <Model extends com.google.protobuf.Message,​Value>
      Model
      splice​(com.google.protobuf.Message instance, + ModelMetadata.FieldPointer field, + java.util.Optional<Value> val) +
      Splice the provided optional value (or clear any existing value) at the specified field pointer, in the + provided model instance.
      +
      static <Model extends com.google.protobuf.Message,​Value>
      Model
      splice​(com.google.protobuf.Message instance, + java.lang.String path, + java.util.Optional<Value> val) +
      Splice the provided optional value (or clear any existing value) at the field path in the provided model + instance.
      +
      static <Builder extends com.google.protobuf.Message.Builder,​Value>
      Builder
      spliceBuilder​(com.google.protobuf.Message.Builder builder, + ModelMetadata.FieldPointer field, + java.util.Optional<Value> val) +
      Splice the provided optional value (or clear any existing value) at the specified field pointer, in the + provided model instance.
      +
      static <Model extends com.google.protobuf.Message,​Value>
      Model
      spliceId​(com.google.protobuf.Message instance, + java.util.Optional<Value> val) +
      Splice the provided value at val, into the ID field value for instance.
      +
      static <Builder extends com.google.protobuf.Message.Builder,​Value>
      Builder
      spliceIdBuilder​(com.google.protobuf.Message.Builder builder, + java.util.Optional<Value> val) +
      Splice the provided value at val, into the ID field value for the provided model builder.
      +
      static <Model extends com.google.protobuf.Message,​Key extends com.google.protobuf.Message>
      Model
      spliceKey​(com.google.protobuf.Message instance, + java.util.Optional<Key> val) +
      Splice the provided value at val, into the key message value for instance.
      +
      static <Builder extends com.google.protobuf.Message.Builder,​Key extends com.google.protobuf.Message>
      Builder
      spliceKeyBuilder​(com.google.protobuf.Message.Builder builder, + java.util.Optional<Key> val) +
      Splice the provided value at val, into the key message value for the supplied builder.
      +
      static <M extends com.google.protobuf.Message>
      java.util.stream.Stream<ModelMetadata.FieldPointer>
      streamFields​(com.google.protobuf.Descriptors.Descriptor descriptor) +
      Crawl all fields, recursively, on the descriptor associated with the provided model instance, and return them in + a stream.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>streamFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.function.Predicate<ModelMetadata.FieldPointer> predicate) +
      Crawl all fields, recursively, on the descriptor associated with the provided model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>streamFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate) +
      Crawl all fields, recursively, on the descriptor associated with the provided model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>streamFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.lang.Boolean recursive) +
      Crawl all fields, recursively, on the provided descriptor for a model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>streamFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.util.function.Predicate<ModelMetadata.FieldPointer> decider) +
      Crawl all fields, recursively, on the provided descriptor for a model instance.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        matchFieldAnnotation

        +
        public static boolean matchFieldAnnotation​(@Nonnull
        +                                           com.google.protobuf.Descriptors.FieldDescriptor field,
        +                                           @Nonnull
        +                                           tools.elide.core.FieldType annotation)
        +
        Match an annotation to a field. If the field is not annotated as such, the method returns `false`.
        +
        +
        Parameters:
        +
        field - Field to check for the provided annotation.
        +
        annotation - Annotation to check for.
        +
        Returns:
        +
        Whether the field is annotated with the provided annotation.
        +
        +
      • +
      + + + +
        +
      • +

        matchCollectionAnnotation

        +
        public static boolean matchCollectionAnnotation​(@Nonnull
        +                                                com.google.protobuf.Descriptors.FieldDescriptor field,
        +                                                @Nonnull
        +                                                tools.elide.core.CollectionMode mode)
        +
        Match a collection annotation. If the field or model is not annotated as such, the method returns `false`.
        +
        +
        Parameters:
        +
        field - Field to check for the provided annotation.
        +
        mode - Collection mode to check for.
        +
        Returns:
        +
        Whether the field is annotated for the provided collection mode.
        +
        +
      • +
      + + + +
        +
      • +

        fullyQualifiedName

        +
        @Nonnull
        +public static java.lang.String fullyQualifiedName​(@Nonnull
        +                                                  com.google.protobuf.Descriptors.Descriptor descriptor)
        +
        Resolve the fully-qualified type path, or name, for the provided datamodel type descriptor. This is essentially + syntactic sugar.
        +
        +
        Parameters:
        +
        descriptor - Model descriptor to resolve a fully-qualified name for.
        +
        Returns:
        +
        Fully-qualified model type name.
        +
        +
      • +
      + + + +
        +
      • +

        fullyQualifiedName

        +
        @Nonnull
        +public static java.lang.String fullyQualifiedName​(@Nonnull
        +                                                  com.google.protobuf.Message model)
        +
        Resolve the fully-qualified type path, or name, for the provided datamodel instance. This method is essentially + syntactic sugar for accessing the model instance's descriptor, and then grabbing the fully-qualified name.
        +
        +
        Parameters:
        +
        model - Model instance to resolve a fully-qualified name for.
        +
        Returns:
        +
        Fully-qualified model type name.
        +
        +
      • +
      + + + +
        +
      • +

        role

        +
        @Nonnull
        +public static tools.elide.core.DatapointType role​(@Nonnull
        +                                                  com.google.protobuf.Descriptors.Descriptor descriptor)
        +
        Resolve the general type for a given datamodel type descriptor. The type is either set by default, or set by an + explicit annotation affixed to the protocol buffer definition that backs the model. + +

        DatapointType annotations describe the general use case for a given model definition. Is it a database + model? A wire model? DatapointType will tell you.

        +
        +
        Parameters:
        +
        descriptor - Model descriptor to retrieve a type for.
        +
        Returns:
        +
        Type of the provided datamodel.
        +
        +
      • +
      + + + +
        +
      • +

        role

        +
        @Nonnull
        +public static tools.elide.core.DatapointType role​(@Nonnull
        +                                                  com.google.protobuf.Message model)
        +
        Resolve the general type for a given datamodel. The type is either set by default, or set by an explicit annotation + affixed to the protocol buffer definition that backs the model. + +

        DatapointType annotations describe the general use case for a given model definition. Is it a database + model? A wire model? DatapointType will tell you.

        +
        +
        Parameters:
        +
        model - Model to retrieve a type for.
        +
        Returns:
        +
        Type of the provided datamodel.
        +
        +
      • +
      + + + +
        +
      • +

        matchRole

        +
        public static boolean matchRole​(@Nonnull
        +                                com.google.protobuf.Message model,
        +                                @Nonnull
        +                                tools.elide.core.DatapointType type)
        +
        Enforce that a particular datamodel type matches any of the provided DatapointType annotations. + +

        DatapointType annotations describe the general use case for a given model definition. Is it a database + model? A wire model? DatapointType will tell you, and this model will sweetly enforce membership amongst + a set of types for you.

        +
        +
        Parameters:
        +
        model - Model to validate against the provided set of types.
        +
        type - Type to enforce for the provided model.
        +
        Returns:
        +
        Whether the provided model is a member-of (annotated-by) any of the provided types.
        +
        +
      • +
      + + + +
        +
      • +

        matchRole

        +
        public static boolean matchRole​(@Nonnull
        +                                com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                @Nonnull
        +                                tools.elide.core.DatapointType type)
        +
        Enforce that a particular datamodel schema descriptor matches any of the provided + DatapointType annotations. + +

        DatapointType annotations describe the general use case for a given model definition. Is it a database + model? A wire model? DatapointType will tell you, and this model will sweetly enforce membership amongst + a set of types for you.

        +
        +
        Parameters:
        +
        descriptor - Schema descriptor to validate against the provided set of types.
        +
        type - Type to enforce for the provided model.
        +
        Returns:
        +
        Whether the provided model is a member-of (annotated-by) any of the provided types.
        +
        +
      • +
      + + + +
        +
      • +

        matchAnyRole

        +
        public static boolean matchAnyRole​(@Nonnull
        +                                   com.google.protobuf.Message model,
        +                                   @Nonnull
        +                                   tools.elide.core.DatapointType... types)
        +
        Check that a particular datamodel type matches any of the provided DatapointType annotations. + +

        DatapointType annotations describe the general use case for a given model definition. Is it a database + model? A wire model? DatapointType will tell you, and this model will sweetly enforce membership amongst + a set of types for you.

        +
        +
        Parameters:
        +
        model - Model to validate against the provided set of types.
        +
        types - Types to validate the model against. If any of the provided types match, the check passes.
        +
        Returns:
        +
        Whether the provided model is a member-of (annotated-by) any of the provided types.
        +
        +
      • +
      + + + +
        +
      • +

        matchAnyRole

        +
        public static boolean matchAnyRole​(@Nonnull
        +                                   com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                   @Nonnull
        +                                   tools.elide.core.DatapointType... types)
        +
        Check that a particular schema descriptor matches any of the provided DatapointType + annotations. + +

        DatapointType annotations describe the general use case for a given model definition. Is it a database + model? A wire model? DatapointType will tell you, and this model will sweetly enforce membership amongst + a set of types for you.

        +
        +
        Parameters:
        +
        descriptor - Schema descriptor to validate against the provided set of types.
        +
        types - Types to validate the model against. If any of the provided types match, the check passes.
        +
        Returns:
        +
        Whether the provided model is a member-of (annotated-by) any of the provided types.
        +
        +
      • +
      + + + +
        +
      • +

        enforceRole

        +
        public static void enforceRole​(@Nonnull
        +                               com.google.protobuf.Message model,
        +                               @Nonnull
        +                               tools.elide.core.DatapointType type)
        +                        throws InvalidModelType
        +
        Enforce that a particular model instance matches the provided DatapointType annotation. + +

        DatapointType annotations describe the general use case for a given model definition. Is it a database + model? A wire model? DatapointType will tell you, and this model will sweetly enforce membership amongst + a set of types for you.

        +
        +
        Parameters:
        +
        model - Model to validate against the provided set of types.
        +
        type - Types to validate the model against. If any of the provided types match, the check passes.
        +
        Throws:
        +
        InvalidModelType - If the specified model's type is not included in types.
        +
        +
      • +
      + + + +
        +
      • +

        enforceRole

        +
        public static void enforceRole​(@Nonnull
        +                               com.google.protobuf.Descriptors.Descriptor descriptor,
        +                               @Nonnull
        +                               tools.elide.core.DatapointType type)
        +                        throws InvalidModelType
        +
        Enforce that a particular datamodel schema descriptor matches the provided DatapointType + annotation. + +

        DatapointType annotations describe the general use case for a given model definition. Is it a database + model? A wire model? DatapointType will tell you, and this model will sweetly enforce membership amongst + a set of types for you.

        +
        +
        Parameters:
        +
        descriptor - Descriptor to validate against the provided set of types.
        +
        type - Types to validate the model against. If any of the provided types match, the check passes.
        +
        Throws:
        +
        InvalidModelType - If the specified model's type is not included in types.
        +
        +
      • +
      + + + +
        +
      • +

        enforceAnyRole

        +
        public static void enforceAnyRole​(@Nonnull
        +                                  com.google.protobuf.Message model,
        +                                  @Nonnull
        +                                  tools.elide.core.DatapointType... types)
        +                           throws InvalidModelType
        +
        Enforce that a particular datamodel type matches any of the provided DatapointType + annotations. + +

        DatapointType annotations describe the general use case for a given model definition. Is it a database + model? A wire model? DatapointType will tell you, and this model will sweetly enforce membership amongst + a set of types for you.

        +
        +
        Parameters:
        +
        model - Model to validate against the provided set of types.
        +
        types - Types to validate the model against. If any of the provided types match, the check passes.
        +
        Throws:
        +
        InvalidModelType - If the specified model's type is not included in types.
        +
        +
      • +
      + + + +
        +
      • +

        enforceAnyRole

        +
        public static void enforceAnyRole​(@Nonnull
        +                                  com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                  @Nonnull
        +                                  tools.elide.core.DatapointType... types)
        +                           throws InvalidModelType
        +
        Enforce that a particular schema descriptor matches any of the provided DatapointType + annotations. + +

        DatapointType annotations describe the general use case for a given model definition. Is it a database + model? A wire model? DatapointType will tell you, and this model will sweetly enforce membership amongst + a set of types for you.

        +
        +
        Parameters:
        +
        descriptor - Schema descriptor to validate against the provided set of types.
        +
        types - Types to validate the model against. If any of the provided types match, the check passes.
        +
        Throws:
        +
        InvalidModelType - If the specified model's type is not included in types.
        +
        +
      • +
      + + + +
        +
      • +

        resolveField

        +
        @Nonnull
        +public static java.util.Optional<ModelMetadata.FieldPointerresolveField​(@Nonnull
        +                                                                          com.google.protobuf.Message instance,
        +                                                                          @Nonnull
        +                                                                          java.lang.String path)
        +
        Resolve an arbitrary field pointer from the provided model instance, specified by the given path to + the property. If the property cannot be found, Optional.empty() is returned. + +

        This method is safe, in that, unlike other util methods for model metadata, it will not throw if the + provided path is invalid.

        +
        +
        Parameters:
        +
        instance - Model instance on which to resolve the specified field.
        +
        path - Dotted deep-path to the desired field.
        +
        Returns:
        +
        Resolved field pointer for the requested field, or Optional.empty().
        +
        +
      • +
      + + + +
        +
      • +

        resolveField

        +
        @Nonnull
        +public static java.util.Optional<ModelMetadata.FieldPointerresolveField​(@Nonnull
        +                                                                          com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                                                          @Nonnull
        +                                                                          java.lang.String path)
        +
        Resolve an arbitrary field pointer from the provided model type escriptor, specified by the given + path to the property. If the property cannot be found, Optional.empty() is returned. + +

        This method is safe, in that, unlike other util methods for model metadata, it will not throw if the + provided path is invalid.

        +
        +
        Parameters:
        +
        descriptor - Model type descriptor on which to resolve the specified field.
        +
        path - Dotted deep-path to the desired field.
        +
        Returns:
        +
        Resolved field pointer for the requested field, or Optional.empty().
        +
        +
      • +
      + + + +
        +
      • +

        modelAnnotation

        +
        @Nonnull
        +public static <E> java.util.Optional<E> modelAnnotation​(@Nonnull
        +                                                        com.google.protobuf.Message instance,
        +                                                        @Nonnull
        +                                                        com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.MessageOptions,​E> ext,
        +                                                        @Nonnull
        +                                                        java.lang.Boolean recursive)
        +
        Retrieve a model-level annotation, from instance, structured by ext. If no instance of the + requested model annotation can be found, Optional.empty() is returned. Search recursively is supported as + well, which descends the search to sub-messages to search for the desired annotation.
        +
        +
        Type Parameters:
        +
        E - Generic type of extension we are looking for.
        +
        Parameters:
        +
        instance - Message instance to scan for the specified annotation.
        +
        ext - Extension to fetch from the subject model, or any sub-model (if recursive is true).
        +
        recursive - Whether to search recursively for the desired extension.
        +
        Returns:
        +
        Optional, either Optional.empty(), or wrapping the found extension data instance.
        +
        +
      • +
      + + + +
        +
      • +

        modelAnnotation

        +
        @Nonnull
        +public static <E> java.util.Optional<E> modelAnnotation​(@Nonnull
        +                                                        com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                                        @Nonnull
        +                                                        com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.MessageOptions,​E> ext,
        +                                                        @Nonnull
        +                                                        java.lang.Boolean recursive)
        +
        Retrieve a model-level annotation, from the provided model schema descriptor, structured by ext. If + no instance of the requested model annotation can be found, Optional.empty() is returned. Search + recursively is supported as well, which descends the search to sub-messages to search for the desired annotation.
        +
        +
        Type Parameters:
        +
        E - Generic type of extension we are looking for.
        +
        Parameters:
        +
        descriptor - Schema descriptor for a model type.
        +
        ext - Extension to fetch from the subject model, or any sub-model (if recursive is true).
        +
        recursive - Whether to search recursively for the desired extension.
        +
        Returns:
        +
        Optional, either Optional.empty(), or wrapping the found extension data instance.
        +
        +
      • +
      + + + +
        +
      • +

        annotatedField

        +
        @Nonnull
        +public static <E> java.util.Optional<ModelMetadata.FieldPointerannotatedField​(@Nonnull
        +                                                                                com.google.protobuf.Message instance,
        +                                                                                @Nonnull
        +                                                                                com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext)
        +
        Resolve a ModelMetadata.FieldPointer within the scope of instance, that holds values for the specified metadata + annotation ext. By default, this method searches recursively.
        +
        +
        Type Parameters:
        +
        E - Extension generic type.
        +
        Parameters:
        +
        instance - Model instance to search for the specified annotated field on.
        +
        ext - Extension (annotation) which should be affixed to the field we are searching for.
        +
        Returns:
        +
        Optional-wrapped field pointer, or Optional.empty().
        +
        See Also:
        +
        variant if a descriptor is on-hand, +full-spec variant.
        +
        +
      • +
      + + + +
        +
      • +

        annotatedField

        +
        @Nonnull
        +public static <E> java.util.Optional<ModelMetadata.FieldPointerannotatedField​(@Nonnull
        +                                                                                com.google.protobuf.Message instance,
        +                                                                                @Nonnull
        +                                                                                com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext,
        +                                                                                @Nonnull
        +                                                                                java.lang.Boolean recursive)
        +
        Resolve a ModelMetadata.FieldPointer within the scope of instance, that holds values for the specified metadata + annotation ext. + +

        This method variant also allows specifying a recursive flag, which, if specified, causes the search to + proceed to sub-models (recursively) until a matching field is found. If recursive is passed as false + then the search will only occur at the top-level of instance.

        +
        +
        Type Parameters:
        +
        E - Extension generic type.
        +
        Parameters:
        +
        instance - Model instance to search for the specified annotated field on.
        +
        ext - Extension (annotation) which should be affixed to the field we are searching for.
        +
        recursive - Whether to conduct this search recursively, or just at the top-level.
        +
        Returns:
        +
        Optional-wrapped field pointer, or Optional.empty().
        +
        See Also:
        +
        Variant that supports a filter, +full-spec variant.
        +
        +
      • +
      + + + +
        +
      • +

        annotatedField

        +
        @Nonnull
        +public static <E> java.util.Optional<ModelMetadata.FieldPointerannotatedField​(@Nonnull
        +                                                                                com.google.protobuf.Message instance,
        +                                                                                @Nonnull
        +                                                                                com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext,
        +                                                                                @Nonnull
        +                                                                                java.lang.Boolean recursive,
        +                                                                                @Nonnull
        +                                                                                java.util.Optional<java.util.function.Function<E,​java.lang.Boolean>> filter)
        +
        Resolve a ModelMetadata.FieldPointer within the scope of instance, that holds values for the specified metadata + annotation ext. + +

        This method variant also allows specifying a filter, which will be run for each property encountered with + the annotation present. If the filter returns true, the field will be selected, otherwise, the search + continues until all properties are exhausted (depending on recursive).

        +
        +
        Type Parameters:
        +
        E - Extension generic type.
        +
        Parameters:
        +
        instance - Model instance to search for the specified annotated field on.
        +
        ext - Extension (annotation) which should be affixed to the field we are searching for.
        +
        recursive - Whether to conduct this search recursively, or just at the top-level.
        +
        Returns:
        +
        Optional-wrapped field pointer, or Optional.empty().
        +
        +
      • +
      + + + +
        +
      • +

        annotatedField

        +
        @Nonnull
        +public static <E> java.util.Optional<ModelMetadata.FieldPointerannotatedField​(@Nonnull
        +                                                                                com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                                                                @Nonnull
        +                                                                                com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext)
        +
        Resolve a ModelMetadata.FieldPointer within the scope of the provided model descriptor, that holds values for the + specified metadata annotation ext. By default, this search occurs recursively, examining all nested sub- + models on the provided instance.
        +
        +
        Type Parameters:
        +
        E - Extension generic type.
        +
        Parameters:
        +
        descriptor - Model object descriptor to search for the specified annotated field on.
        +
        ext - Extension (annotation) which should be affixed to the field we are searching for.
        +
        Returns:
        +
        Optional-wrapped field pointer, or Optional.empty().
        +
        +
      • +
      + + + +
        +
      • +

        annotatedField

        +
        @Nonnull
        +public static <E> java.util.Optional<ModelMetadata.FieldPointerannotatedField​(@Nonnull
        +                                                                                com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                                                                @Nonnull
        +                                                                                com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext,
        +                                                                                @Nonnull
        +                                                                                java.util.Optional<java.util.function.Function<E,​java.lang.Boolean>> filter)
        +
        Resolve a ModelMetadata.FieldPointer within the scope of the provided model descriptor, that holds values for the + specified metadata annotation ext. By default, this search occurs recursively, examining all nested sub- + models on the provided instance. + +

        This method variant also allows specifying a filter, which will be run for each property encountered with + the annotation present. If the filter returns true, the field will be selected, otherwise, the search + continues until all properties are exhausted (depending on recursive).

        +
        +
        Type Parameters:
        +
        E - Extension generic type.
        +
        Parameters:
        +
        descriptor - Model object descriptor to search for the specified annotated field on.
        +
        ext - Extension (annotation) which should be affixed to the field we are searching for.
        +
        Returns:
        +
        Optional-wrapped field pointer, or Optional.empty().
        +
        +
      • +
      + + + +
        +
      • +

        annotatedField

        +
        @Nonnull
        +public static <E> java.util.Optional<ModelMetadata.FieldPointerannotatedField​(@Nonnull
        +                                                                                com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                                                                @Nonnull
        +                                                                                com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext,
        +                                                                                @Nonnull
        +                                                                                java.lang.Boolean recursive,
        +                                                                                @Nonnull
        +                                                                                java.util.Optional<java.util.function.Function<E,​java.lang.Boolean>> filter)
        +
        Resolve a ModelMetadata.FieldPointer within the scope of the provided model descriptor, that holds values for the + specified metadata annotation ext. Using the recursive parameter, the invoking developer may opt to + search for the annotated field recursively. + +

        This method variant also allows specifying a filter, which will be run for each property encountered with + the annotation present. If the filter returns true, the field will be selected, otherwise, the search + continues until all properties are exhausted (depending on recursive).

        +
        +
        Type Parameters:
        +
        E - Extension generic type.
        +
        Parameters:
        +
        descriptor - Model object descriptor to search for the specified annotated field on.
        +
        ext - Extension (annotation) which should be affixed to the field we are searching for.
        +
        Returns:
        +
        Optional-wrapped field pointer, or Optional.empty().
        +
        +
      • +
      + + + +
        +
      • +

        fieldAnnotation

        +
        @Nonnull
        +public static <E> java.util.Optional<E> fieldAnnotation​(@Nonnull
        +                                                        com.google.protobuf.Descriptors.FieldDescriptor descriptor,
        +                                                        @Nonnull
        +                                                        com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext)
        +
        Retrieve a field-level annotation, from the provided field schema descriptor, structured by ext. If + no instance of the requested field annotation can be found, Optional.empty() is returned.
        +
        +
        Type Parameters:
        +
        E - Generic type of extension we are looking for.
        +
        Parameters:
        +
        descriptor - Schema descriptor for a field on a model type.
        +
        ext - Extension to fetch from the subject field.
        +
        Returns:
        +
        Optional, either Optional.empty(), or wrapping the found extension data instance.
        +
        +
      • +
      + + + +
        +
      • +

        idField

        +
        @Nonnull
        +public static java.util.Optional<ModelMetadata.FieldPointeridField​(@Nonnull
        +                                                                     com.google.protobuf.Message instance)
        +                                                              throws InvalidModelType
        +
        Resolve a pointer to the provided model instance's ID field, whether or not it has a value. If there is no + ID-annotated field at all, Optional.empty() is returned. Alternatively, if the model is not compatible with + ID fields, an exception is raised (see below).
        +
        +
        Parameters:
        +
        instance - Model instance for which an ID field is being resolved.
        +
        Returns:
        +
        Optional, either Optional.empty() or containing a ModelMetadata.FieldPointer to the resolved ID field.
        +
        Throws:
        +
        InvalidModelType - If the specified model does not support IDs. Only objects of type OBJECT can be + used with this interface.
        +
        +
      • +
      + + + +
        +
      • +

        idField

        +
        @Nonnull
        +public static java.util.Optional<ModelMetadata.FieldPointeridField​(@Nonnull
        +                                                                     com.google.protobuf.Descriptors.Descriptor descriptor)
        +                                                              throws InvalidModelType
        +
        Resolve a pointer to the provided schema type descriptor's ID field, whether or not it has a value. If + there is no ID-annotated field at all, Optional.empty() is returned. Alternatively, if the model is not + compatible with ID fields, an exception is raised (see below).
        +
        +
        Parameters:
        +
        descriptor - Model instance for which an ID field is being resolved.
        +
        Returns:
        +
        Optional, either Optional.empty() or containing a ModelMetadata.FieldPointer to the resolved ID field.
        +
        Throws:
        +
        InvalidModelType - If the specified model does not support IDs. Only objects of type OBJECT can be + used with this interface.
        +
        +
      • +
      + + + +
        +
      • +

        keyField

        +
        @Nonnull
        +public static java.util.Optional<ModelMetadata.FieldPointerkeyField​(@Nonnull
        +                                                                      com.google.protobuf.Message instance)
        +                                                               throws InvalidModelType
        +
        Resolve a pointer to the provided schema type descriptor's KEY field, whether or not it has a value + assigned. If there is no key-annotated field at all, Optional.empty() is returned. Alternatively, if the + model is not compatible with key fields, an exception is raised (see below).
        +
        +
        Parameters:
        +
        instance - Model instance for which a key field is being resolved.
        +
        Returns:
        +
        Optional, either Optional.empty() or containing a ModelMetadata.FieldPointer to the resolved key field.
        +
        Throws:
        +
        InvalidModelType - If the specified model does not support keys. Only objects of type OBJECT can be + used with this interface.
        +
        +
      • +
      + + + +
        +
      • +

        keyField

        +
        @Nonnull
        +public static java.util.Optional<ModelMetadata.FieldPointerkeyField​(@Nonnull
        +                                                                      com.google.protobuf.Descriptors.Descriptor descriptor)
        +                                                               throws InvalidModelType
        +
        Resolve a pointer to the provided schema type descriptor's KEY field, whether or not it has a value + assigned. If there is no key-annotated field at all, Optional.empty() is returned. Alternatively, if the + model is not compatible with key fields, an exception is raised (see below).
        +
        +
        Parameters:
        +
        descriptor - Model type descriptor for which a key field is being resolved.
        +
        Returns:
        +
        Optional, either Optional.empty() or containing a ModelMetadata.FieldPointer to the resolved key field.
        +
        Throws:
        +
        InvalidModelType - If the specified model does not support keys. Only objects of type OBJECT can be + used with this interface.
        +
        +
      • +
      + + + +
        +
      • +

        pluck

        +
        @Nonnull
        +public static <V> ModelMetadata.FieldContainer<V> pluck​(@Nonnull
        +                                                        com.google.protobuf.Message instance,
        +                                                        @Nonnull
        +                                                        ModelMetadata.FieldPointer fieldPointer)
        +
        Pluck a field value, addressed by a ModelMetadata.FieldPointer, from the provided instance. If the referenced + field is a message, a message instance will be handed back only if there is an initialized value. Leaf fields + return their raw value, if set. In all cases, if there is no initialized value, Optional.empty() is + returned.
        +
        +
        Type Parameters:
        +
        V - Generic type of data returned by this operation.
        +
        Parameters:
        +
        instance - Model instance from which to pluck the property.
        +
        fieldPointer - Pointer to the field we wish to fetch.
        +
        Returns:
        +
        Optional wrapping the resolved value, or Optional.empty() if no value could be resolved.
        +
        Throws:
        +
        java.lang.IllegalStateException - If the referenced property is not found, despite witnessing matching types.
        +
        java.lang.IllegalArgumentException - If the specified field does not have a matching base type with instance.
        +
        +
      • +
      + + + +
        +
      • +

        pluck

        +
        @Nonnull
        +public static <V> ModelMetadata.FieldContainer<V> pluck​(@Nonnull
        +                                                        com.google.protobuf.Message instance,
        +                                                        @Nonnull
        +                                                        java.lang.String path)
        +
        Return a single field value container, plucked from the specified deep path, in dot form, using the regular + protobuf-definition names for each field. If a referenced field is a message, a message instance will be returned + only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no + initialized value, Optional.empty() is supplied in place.
        +
        +
        Type Parameters:
        +
        V - Expected type for the property. If types do not match, a ClassCastException will be raised.
        +
        Parameters:
        +
        instance - Model instance to pluck the specified property from.
        +
        path - Deep path for the property value we wish to pluck.
        +
        Returns:
        +
        Field container, either empty, or containing the plucked value.
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If the provided path is syntactically invalid, or the field does not exist.
        +
        +
      • +
      + + + +
        +
      • +

        pluckAll

        +
        @Nonnull
        +public static java.util.SortedSet<ModelMetadata.FieldContainer<java.lang.Object>> pluckAll​(@Nonnull
        +                                                                                           com.google.protobuf.Message instance,
        +                                                                                           @Nonnull
        +                                                                                           com.google.protobuf.FieldMask mask)
        +
        Return an iterable containing plucked value containers for each field mentioned in mask, that is present on + instance with an initialized value. If a referenced field is a message, a message instance will be included + only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no + initialized value, Optional.empty() is supplied in place. + +

        If a field cannot be found, Optional.empty() is supplied in its place, so that the output order matches + path iteration order on the supplied mask. This method is therefore safe with regard to path access.

        +
        +
        Parameters:
        +
        instance - Model instance to pluck the specified properties from.
        +
        mask - Mask of properties to pluck from the model instance.
        +
        Returns:
        +
        Stream which emits each field container, with a generic Object for each value.
        +
        +
      • +
      + + + +
        +
      • +

        pluckAll

        +
        @Nonnull
        +public static java.util.SortedSet<ModelMetadata.FieldContainer<java.lang.Object>> pluckAll​(@Nonnull
        +                                                                                           com.google.protobuf.Message instance,
        +                                                                                           @Nonnull
        +                                                                                           com.google.protobuf.FieldMask mask,
        +                                                                                           @Nonnull
        +                                                                                           java.lang.Boolean normalize)
        +
        Return an iterable containing plucked value containers for each field mentioned in mask, that is present on + instance with an initialized value. If a referenced field is a message, a message instance will be included + only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no + initialized value, Optional.empty() is supplied in place. + +

        If a field cannot be found, Optional.empty() is supplied in its place, so that the output order matches + path iteration order on the supplied mask. This method is therefore safe with regard to path access. If + normalize is activated (the default for pluckAll(Message, FieldMask)), the field mask will be + sorted and de-duplicated before processing.

        + +

        Sort order of the return value is based on the full path of properties selected - i.e. field containers are + returned in lexicographic sort order matching their underlying property paths.

        +
        +
        Parameters:
        +
        instance - Model instance to pluck the specified properties from.
        +
        mask - Mask of properties to pluck from the model instance.
        +
        normalize - Whether to normalize the field mask before plucking fields.
        +
        Returns:
        +
        Stream which emits each field container, with a generic Object for each value.
        +
        +
      • +
      + + + +
        +
      • +

        pluckStream

        +
        @Nonnull
        +public static java.util.stream.Stream<ModelMetadata.FieldContainer<java.lang.Object>> pluckStream​(@Nonnull
        +                                                                                                  com.google.protobuf.Message instance,
        +                                                                                                  @Nonnull
        +                                                                                                  com.google.protobuf.FieldMask mask)
        +
        Return a stream which emits plucked value containers for each field mentioned in mask, that is present on + instance with an initialized value. If a referenced field is a message, a message instance will be emitted + only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no + initialized value, Optional.empty() is supplied in place. + +

        If a field cannot be found, Optional.empty() is supplied in its place, so that the output order matches + path iteration order on the supplied mask. This method is therefore safe with regard to path access.

        + +

        Performance note: the Stream returned by this method is explicitly parallel-capable, because + reading descriptor schema is safely concurrent.

        +
        +
        Parameters:
        +
        instance - Model instance to pluck the specified properties from.
        +
        mask - Mask of properties to pluck from the model instance.
        +
        Returns:
        +
        Stream which emits each field container, with a generic Object for each value.
        +
        +
      • +
      + + + +
        +
      • +

        pluckStream

        +
        @Nonnull
        +public static java.util.stream.Stream<ModelMetadata.FieldContainer<java.lang.Object>> pluckStream​(@Nonnull
        +                                                                                                  com.google.protobuf.Message instance,
        +                                                                                                  @Nonnull
        +                                                                                                  com.google.protobuf.FieldMask mask,
        +                                                                                                  @Nonnull
        +                                                                                                  java.lang.Boolean normalize)
        +
        Return a stream which emits plucked value containers for each field mentioned in mask, that is present on + instance with an initialized value. If a referenced field is a message, a message instance will be emitted + only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no + initialized value, Optional.empty() is supplied in place. + +

        If a field cannot be found, Optional.empty() is supplied in its place, so that the output order matches + path iteration order on the supplied mask. This method is therefore safe with regard to path access. If + normalize is activated (the default for pluckStream(Message, FieldMask)), the field mask will be + sorted and de-duplicated before processing.

        + +

        Performance note: the Stream returned by this method is explicitly parallel-capable, because + reading descriptor schema is safely concurrent.

        +
        +
        Parameters:
        +
        instance - Model instance to pluck the specified properties from.
        +
        mask - Mask of properties to pluck from the model instance.
        +
        normalize - Whether to normalize the field mask before plucking fields.
        +
        Returns:
        +
        Stream which emits each field container, with a generic Object for each value.
        +
        +
      • +
      + + + +
        +
      • +

        id

        +
        @Nonnull
        +public static <ID> java.util.Optional<ID> id​(@Nonnull
        +                                             com.google.protobuf.Message instance)
        +
        Resolve the provided model instance's assigned ID, by walking the property structure for the entity, and returning + either the first id-annotated field's value at the top-level, or the first id-annotated field value + on the first key-annotated message field at the top level of the provided message. + +

        If no ID field value can be resolved, Optional.empty() is returned. On the other hand, if the + model is not a business object or does not have an ID annotation at all, an exception is raised (see below).

        +
        +
        Type Parameters:
        +
        ID - Type for the ID value we are resolving.
        +
        Parameters:
        +
        instance - Model instance for which an ID value is desired.
        +
        Returns:
        +
        Optional wrapping the value of the model instance's ID, or an empty optional if no value could be resolved.
        +
        Throws:
        +
        InvalidModelType - If the supplied model is not a business object and/or does not have an ID field at all.
        +
        +
      • +
      + + + +
        +
      • +

        key

        +
        @Nonnull
        +public static <Key> java.util.Optional<Key> key​(@Nonnull
        +                                                com.google.protobuf.Message instance)
        +
        Resolve the provided model instance's assigned KEY instance, by walking the property structure for the + entity, and returning the first key-annotated field's value at the top-level of the provided message. + +

        If no key field value can be resolved, Optional.empty() is returned. On the other hand, if the + model is not a business object or does not support key annotations at all, an exception is raised (see below).

        +
        +
        Type Parameters:
        +
        Key - Type for the key we are resolving.
        +
        Parameters:
        +
        instance - Model instance for which an key value is desired.
        +
        Returns:
        +
        Optional wrapping the value of the model instance's key, or an empty optional if none could be resolved.
        +
        Throws:
        +
        InvalidModelType - If the supplied model is not a business object and/or does not have an key field at all.
        +
        +
      • +
      + + + +
        +
      • +

        splice

        +
        @Nonnull
        +public static <Model extends com.google.protobuf.Message,​Value> Model splice​(@Nonnull
        +                                                                                   com.google.protobuf.Message instance,
        +                                                                                   @Nonnull
        +                                                                                   java.lang.String path,
        +                                                                                   @Nonnull
        +                                                                                   java.util.Optional<Value> val)
        +
        Splice the provided optional value (or clear any existing value) at the field path in the provided model + instance. Return a re-built message after the splice. + +

        If Optional.empty() is passed for the value to set, any existing value placed in that field + will be cleared. This method works identically for primitive leaf fields and message fields.

        +
        +
        Type Parameters:
        +
        Model - Model type which we are working with for this splice operation.
        +
        Value - Value type which we are splicing in, if applicable.
        +
        Parameters:
        +
        instance - Model instance to splice the value into.
        +
        path - Deep path at which to splice the value.
        +
        val - Value to splice into the model, or Optional.empty() to clear any existing value.
        +
        Returns:
        +
        Re-built model, after the splice operation.
        +
        +
      • +
      + + + +
        +
      • +

        splice

        +
        @Nonnull
        +public static <Model extends com.google.protobuf.Message,​Value> Model splice​(@Nonnull
        +                                                                                   com.google.protobuf.Message instance,
        +                                                                                   @Nonnull
        +                                                                                   ModelMetadata.FieldPointer field,
        +                                                                                   @Nonnull
        +                                                                                   java.util.Optional<Value> val)
        +
        Splice the provided optional value (or clear any existing value) at the specified field pointer, in the + provided model instance. Return a re-built message after the splice. + +

        If Optional.empty() is passed for the value to set, any existing value placed in that field + will be cleared. This method works identically for primitive leaf fields and message fields.

        +
        +
        Type Parameters:
        +
        Model - Model type which we are working with for this splice operation.
        +
        Value - Value type which we are splicing in, if applicable.
        +
        Parameters:
        +
        instance - Model instance to splice the value into.
        +
        field - Resolved and validated field pointer for the field to splice.
        +
        val - Value to splice into the model, or Optional.empty() to clear any existing value.
        +
        Returns:
        +
        Re-built model, after the splice operation.
        +
        +
      • +
      + + + +
        +
      • +

        spliceBuilder

        +
        @CanIgnoreReturnValue
        +@Nonnull
        +public static <Builder extends com.google.protobuf.Message.Builder,​Value> Builder spliceBuilder​(@Nonnull
        +                                                                                                      com.google.protobuf.Message.Builder builder,
        +                                                                                                      @Nonnull
        +                                                                                                      ModelMetadata.FieldPointer field,
        +                                                                                                      @Nonnull
        +                                                                                                      java.util.Optional<Value> val)
        +
        Splice the provided optional value (or clear any existing value) at the specified field pointer, in the + provided model instance. Return the provided builder after the splice operation. The return value may be + ignored if the developer so wishes (the provided builder is mutated in place). + +

        If Optional.empty() is passed for the value to set, any existing value placed in that field + will be cleared. This method works identically for primitive leaf fields and message fields.

        +
        +
        Type Parameters:
        +
        Builder - Model builder type which we are working with for this splice operation.
        +
        Value - Value type which we are splicing in, if applicable.
        +
        Parameters:
        +
        builder - Model builder to splice the value into.
        +
        field - Resolved and validated field pointer for the field to splice.
        +
        val - Value to splice into the model, or Optional.empty() to clear any existing value.
        +
        Returns:
        +
        Model builder, after the splice operation.
        +
        +
      • +
      + + + +
        +
      • +

        spliceId

        +
        @Nonnull
        +public static <Model extends com.google.protobuf.Message,​Value> Model spliceId​(@Nonnull
        +                                                                                     com.google.protobuf.Message instance,
        +                                                                                     @Nonnull
        +                                                                                     java.util.Optional<Value> val)
        +
        Splice the provided value at val, into the ID field value for instance. If an ID-annotated property + cannot be located, or the model is not of a suitable/type role for use with IDs, an exception is raised (see below + for more info). + +

        If an existing value exists for the model's ID, it will be replaced. In most object-based storage engines + this will end up copying the object, rather than mutating an ID. Be careful of this behavior. Passing + Optional.empty() will clear any existing ID on the model.

        +
        +
        Type Parameters:
        +
        Model - Type of model we are splicing an ID value into.
        +
        Value - Type of ID value we are splicing into the model.
        +
        Parameters:
        +
        instance - Model instance to splice the value into. Because models are immutable, this involves converting the + model to a builder, splicing in the value, and then re-building the model. As such, the model + returned will be a different object instance, but will otherwise be untouched.
        +
        val - Value we should splice-into the ID field for the record. It is expected that the generic type of this + value will line up with the ID field type, otherwise a ClassCastException will be thrown.
        +
        Returns:
        +
        Model instance, rebuilt, after splicing in the provided value, at the model's ID-annotated field.
        +
        Throws:
        +
        InvalidModelType - If the specified model is not suitable for use with IDs at all.
        +
        java.lang.ClassCastException - If the Value generic type does not match the ID field primitive type.
        +
        MissingAnnotatedField - If the provided instance is not of the correct type, or has no ID field.
        +
        +
      • +
      + + + +
        +
      • +

        spliceIdBuilder

        +
        @Nonnull
        +public static <Builder extends com.google.protobuf.Message.Builder,​Value> Builder spliceIdBuilder​(@Nonnull
        +                                                                                                        com.google.protobuf.Message.Builder builder,
        +                                                                                                        @Nonnull
        +                                                                                                        java.util.Optional<Value> val)
        +
        Splice the provided value at val, into the ID field value for the provided model builder. If an ID- + annotated property cannot be located, or the model is not of a suitable/type role for use with IDs, an exception is + raised (see below for more info). + +

        If an existing value exists for the model's ID, it will be replaced. In most object-based storage engines + this will end up copying the object, rather than mutating an ID. Be careful of this behavior. Passing + Optional.empty() will clear any existing ID on the model.

        +
        +
        Type Parameters:
        +
        Builder - Type of model builder we are splicing an ID value into.
        +
        Value - Type of ID value we are splicing into the model.
        +
        Parameters:
        +
        builder - Model instance builder to splice the value into. The builder provided is mutated in place, so + it will be an identical object instance to the one provided, but with the ID property filled in.
        +
        val - Value we should splice-into the ID field for the record. It is expected that the generic type of this + value will line up with the ID field type, otherwise a ClassCastException will be thrown.
        +
        Returns:
        +
        Model builder, after splicing in the provided value, at the model's ID-annotated field.
        +
        Throws:
        +
        InvalidModelType - If the specified model is not suitable for use with IDs at all.
        +
        java.lang.ClassCastException - If the Value generic type does not match the ID field primitive type.
        +
        MissingAnnotatedField - If the provided builder is not of the correct type, or has no ID field.
        +
        +
      • +
      + + + +
        +
      • +

        spliceKey

        +
        @Nonnull
        +public static <Model extends com.google.protobuf.Message,​Key extends com.google.protobuf.Message> Model spliceKey​(@Nonnull
        +                                                                                                                        com.google.protobuf.Message instance,
        +                                                                                                                        @Nonnull
        +                                                                                                                        java.util.Optional<Key> val)
        +
        Splice the provided value at val, into the key message value for instance. If a key-annotated + property cannot be located, or the model is not of a suitable/type role for use with keys, an exception is raised + (see below for more info). + +

        If an existing value is set for the model's key, it will be replaced. In most object-based storage + engines this will end up copying the object, rather than mutating a key. Keys are usually immutable for this + reason, so use this method with care. Passing Optional.empty() will clear any existing key message + currently affixed to the model instance.

        +
        +
        Type Parameters:
        +
        Model - Type of model we are splicing an ID value into.
        +
        Key - Type of key message we are splicing into the model.
        +
        Parameters:
        +
        instance - Model instance to splice the value into. Because models are immutable, this involves converting the + model to a builder, splicing in the value, and then re-building the model. As such, the model + returned will be a different object instance, but will otherwise be untouched.
        +
        val - Value we should splice-into the ID field for the record. It is expected that the generic type of this + value will line up with the ID field type, otherwise a ClassCastException will be thrown.
        +
        Returns:
        +
        Model instance, rebuilt, after splicing in the provided value, at the model's ID-annotated field.
        +
        Throws:
        +
        InvalidModelType - If the specified model is not suitable for use with IDs at all.
        +
        java.lang.ClassCastException - If the Value generic type does not match the ID field primitive type.
        +
        MissingAnnotatedField - If the provided builder is not of the correct type, or has no ID field.
        +
        +
      • +
      + + + +
        +
      • +

        spliceKeyBuilder

        +
        @Nonnull
        +public static <Builder extends com.google.protobuf.Message.Builder,​Key extends com.google.protobuf.Message> Builder spliceKeyBuilder​(@Nonnull
        +                                                                                                                                           com.google.protobuf.Message.Builder builder,
        +                                                                                                                                           @Nonnull
        +                                                                                                                                           java.util.Optional<Key> val)
        +
        Splice the provided value at val, into the key message value for the supplied builder. If a + key-annotated property cannot be located, or the model is not of a suitable/type role for use with keys, an + exception is raised (see below for more info). + +

        If an existing value is set for the model's key, it will be replaced. In most object-based storage + engines this will end up copying the object, rather than mutating a key. Keys are usually immutable for this + reason, so use this method with care. Passing Optional.empty() will clear any existing key message + currently affixed to the model instance.

        +
        +
        Type Parameters:
        +
        Builder - Type of model builder we are splicing a key value into.
        +
        Key - Type of key message we are splicing into the model.
        +
        Parameters:
        +
        builder - Model instance builder to splice the value into. The builder provided is mutated in place, so + it will be an identical object instance to the one provided, but with the key property filled in.
        +
        val - Value we should splice-into the key field for the record. It is expected that the generic type of this + value will line up with the key message type, otherwise a ClassCastException will be thrown.
        +
        Returns:
        +
        Model builder, after splicing in the provided message, at the model's key-annotated field.
        +
        Throws:
        +
        InvalidModelType - If the specified model is not suitable for use with keys at all.
        +
        java.lang.ClassCastException - If the Value generic type does not match the key field primitive type.
        +
        MissingAnnotatedField - If the provided builder is not of the correct type, or has no key field.
        +
        +
      • +
      + + + + + + + +
        +
      • +

        allFields

        +
        @Nonnull
        +public static java.lang.Iterable<ModelMetadata.FieldPointerallFields​(@Nonnull
        +                                                                       com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                                                       @Nonnull
        +                                                                       java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate)
        +
        Crawl all fields, recursively, on the descriptor provided. For each field encountered, run `predicate` to determine + whether to include the field, filtering the returned iterable accordingly. This data may also be accessed via a + Java stream via the method variants listed below.
        +
        +
        Parameters:
        +
        descriptor - Schema descriptor to crawl model definitions on.
        +
        predicate - Filter predicate function, if applicable.
        +
        Returns:
        +
        Iterable of all fields, recursively, from the descriptor, filtered by `predicate`.
        +
        See Also:
        +
        to additionally control recursion.
        +
        +
      • +
      + + + +
        +
      • +

        allFields

        +
        @Nonnull
        +public static java.lang.Iterable<ModelMetadata.FieldPointerallFields​(@Nonnull
        +                                                                       com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                                                       @Nonnull
        +                                                                       java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate,
        +                                                                       @Nonnull
        +                                                                       java.lang.Boolean recursive)
        +
        Crawl all fields, recursively, on the descriptor provided. For each field encountered, run `predicate` to determine + whether to include the field, filtering the returned iterable accordingly. This data may also be accessed via a + Java stream via the method variants listed below.
        +
        +
        Parameters:
        +
        descriptor - Schema descriptor to crawl model definitions on.
        +
        predicate - Filter predicate function, if applicable.
        +
        Returns:
        +
        Iterable of all fields, optionally recursively, from the descriptor, filtered by `predicate`.
        +
        See Also:
        +
        to access a stream of fields instead.
        +
        +
      • +
      + + + +
        +
      • +

        allFields

        +
        @Nonnull
        +public static java.lang.Iterable<ModelMetadata.FieldPointerallFields​(@Nonnull
        +                                                                       com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                                                       @Nonnull
        +                                                                       java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate,
        +                                                                       @Nonnull
        +                                                                       java.util.function.Predicate<ModelMetadata.FieldPointer> decider)
        +
        Crawl all fields, recursively, on the descriptor provided. For each field encountered, run `predicate` to determine + whether to include the field, filtering the returned iterable accordingly. This data may also be accessed via a + Java stream via the method variants listed below. + +

        If a `MESSAGE` field is encountered and the algorithm needs to decide whether to recurse, this variant includes + support for the `decider` function. `decider` is invoked to decide whether to recurse for opportunity to do so.

        +
        +
        Parameters:
        +
        descriptor - Schema descriptor to crawl model definitions on.
        +
        predicate - Filter predicate function, if applicable.
        +
        decider - Function which decides whether to recurse, for each opportunity to do so.
        +
        Returns:
        +
        Iterable of all fields, optionally recursively, from the descriptor, filtered by `predicate`.
        +
        See Also:
        +
        to access a stream of fields instead.
        +
        +
      • +
      + + + +
        +
      • +

        forEachField

        +
        @Nonnull
        +public static java.util.stream.Stream<ModelMetadata.FieldPointerforEachField​(@Nonnull
        +                                                                               com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                                                               @Nonnull
        +                                                                               java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate)
        +
        Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run + `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. This + method variant runs each operation serially. + +

        This method variant does not allow the invoking user to crawl recursively.

        +
        +
        Parameters:
        +
        descriptor - Schema descriptor to crawl model definitions on.
        +
        predicate - Filter predicate function, if applicable.
        +
        Returns:
        +
        Stream of field descriptors, recursively, which match the `predicate`, if provided.
        +
        See Also:
        +
        for the cleanest invocation of this method.
        +
        +
      • +
      + + + +
        +
      • +

        forEachField

        +
        @Nonnull
        +public static java.util.stream.Stream<ModelMetadata.FieldPointerforEachField​(@Nonnull
        +                                                                               com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                                                               @Nonnull
        +                                                                               java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate,
        +                                                                               boolean recursive)
        +
        Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run + `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. This + method variant runs each operation serially. + +

        This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search + is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate + is provided, the entire set of recursive effective fields is returned from the provided descriptor.

        +
        +
        Parameters:
        +
        descriptor - Schema descriptor to crawl model definitions on.
        +
        predicate - Filter predicate function, if applicable.
        +
        recursive - Whether to perform recursion down to sub-messages.
        +
        Returns:
        +
        Stream of field descriptors, recursively, which match the `predicate`, if provided.
        +
        See Also:
        +
        for the cleanest invocation of this method.
        +
        +
      • +
      + + + +
        +
      • +

        forEachField

        +
        @Nonnull
        +public static java.util.stream.Stream<ModelMetadata.FieldPointerforEachField​(@Nonnull
        +                                                                               com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                                                               @Nonnull
        +                                                                               java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate,
        +                                                                               @Nonnull
        +                                                                               java.util.function.Predicate<ModelMetadata.FieldPointer> decider)
        +
        Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run + `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. This + method variant runs each operation serially. + +

        If a `MESSAGE` field is encountered and the algorithm needs to decide whether to recurse, this variant includes + support for the `decider` function. `decider` is invoked to decide whether to recurse for opportunity to do so.

        + +

        This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search + is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate + is provided, the entire set of recursive effective fields is returned from the provided descriptor.

        +
        +
        Parameters:
        +
        descriptor - Schema descriptor to crawl model definitions on.
        +
        predicate - Filter predicate function, if applicable.
        +
        decider - Function that decides whether to recurse.
        +
        Returns:
        +
        Stream of field descriptors, recursively, which match the `predicate`, if provided.
        +
        See Also:
        +
        for the cleanest invocation of this method.
        +
        +
      • +
      + + + +
        +
      • +

        streamFields

        +
        @Nonnull
        +public static <M extends com.google.protobuf.Message> java.util.stream.Stream<ModelMetadata.FieldPointerstreamFields​(@Nonnull
        +                                                                                                                       com.google.protobuf.Descriptors.Descriptor descriptor)
        +
        Crawl all fields, recursively, on the descriptor associated with the provided model instance, and return them in + a stream. + +

        This method crawls recursively by default, but this behavior can be customized via the alternate method variants + listed below. Other variants also allow applying a predicate to filter the returned fields.

        +
        +
        Parameters:
        +
        descriptor - Schema descriptor to crawl model definitions on.
        +
        Returns:
        +
        Stream of field descriptors, recursively, which match the `predicate`, if provided.
        +
        See Also:
        +
        for the opportunity to provide a filter predicate., +for the opportunity to control recursive crawling, and provide a + filter predicate.
        +
        +
      • +
      + + + +
        +
      • +

        streamFields

        +
        @Nonnull
        +public static java.util.stream.Stream<ModelMetadata.FieldPointerstreamFields​(@Nonnull
        +                                                                               com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                                                               @Nonnull
        +                                                                               java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate)
        +
        Crawl all fields, recursively, on the descriptor associated with the provided model instance. For each field + encountered, run `predicate` to determine whether to include the field, filtering the returned stream of fields + accordingly. + +

        This method crawls recursively by default, but this behavior can be customized via the alternate method variants + listed below.

        +
        +
        Parameters:
        +
        descriptor - Schema descriptor to crawl model definitions on.
        +
        predicate - Filter predicate function, if applicable.
        +
        Returns:
        +
        Stream of field descriptors, recursively, which match the `predicate`, if provided.
        +
        See Also:
        +
        for the opportunity to control recursive crawling.
        +
        +
      • +
      + + + +
        +
      • +

        streamFields

        +
        @Nonnull
        +public static java.util.stream.Stream<ModelMetadata.FieldPointerstreamFields​(@Nonnull
        +                                                                               com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                                                               @Nonnull
        +                                                                               java.util.function.Predicate<ModelMetadata.FieldPointer> predicate)
        +
        Crawl all fields, recursively, on the descriptor associated with the provided model instance. For each field + encountered, run `predicate` to determine whether to include the field, filtering the returned stream of fields + accordingly. In this case, `predicate` is required. + +

        This method crawls recursively by default, but this behavior can be customized via the alternate method variants + listed below.

        +
        +
        Parameters:
        +
        descriptor - Schema descriptor to crawl model definitions on.
        +
        predicate - Filter predicate function, if applicable.
        +
        Returns:
        +
        Stream of field descriptors, recursively, which match the `predicate`, if provided.
        +
        See Also:
        +
        for the opportunity to control recursive crawling.
        +
        +
      • +
      + + + +
        +
      • +

        streamFields

        +
        @Nonnull
        +public static java.util.stream.Stream<ModelMetadata.FieldPointerstreamFields​(@Nonnull
        +                                                                               com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                                                               @Nonnull
        +                                                                               java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate,
        +                                                                               @Nonnull
        +                                                                               java.lang.Boolean recursive)
        +
        Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run + `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. + +

        This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search + is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate + is provided, the entire set of recursive effective fields is returned from the provided descriptor.

        +
        +
        Parameters:
        +
        descriptor - Schema descriptor to crawl model definitions on.
        +
        predicate - Filter predicate function, if applicable.
        +
        recursive - Whether to descend to sub-models recursively.
        +
        Returns:
        +
        Stream of field descriptors, recursively, which match the `predicate`, if provided.
        +
        See Also:
        +
        for the cleanest invocation of this method.
        +
        +
      • +
      + + + +
        +
      • +

        streamFields

        +
        @Nonnull
        +public static java.util.stream.Stream<ModelMetadata.FieldPointerstreamFields​(@Nonnull
        +                                                                               com.google.protobuf.Descriptors.Descriptor descriptor,
        +                                                                               @Nonnull
        +                                                                               java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate,
        +                                                                               @Nonnull
        +                                                                               java.util.function.Predicate<ModelMetadata.FieldPointer> decider)
        +
        Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run + `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. By + default, all field streaming methods run in parallel. + +

        If a `MESSAGE` field is encountered and the algorithm needs to decide whether to recurse, this variant includes + support for the `decider` function. `decider` is invoked to decide whether to recurse for opportunity to do so.

        + +

        This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search + is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate + is provided, the entire set of recursive effective fields is returned from the provided descriptor.

        +
        +
        Parameters:
        +
        descriptor - Schema descriptor to crawl model definitions on.
        +
        predicate - Filter predicate function, if applicable.
        +
        decider - Function that decides whether to recurse.
        +
        Returns:
        +
        Stream of field descriptors, recursively, which match the `predicate`, if provided.
        +
        See Also:
        +
        for the cleanest invocation of this method.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/ModelSerializer.EnumSerializeMode.html b/docs/java/gust/backend/model/ModelSerializer.EnumSerializeMode.html new file mode 100644 index 000000000..57dc076a1 --- /dev/null +++ b/docs/java/gust/backend/model/ModelSerializer.EnumSerializeMode.html @@ -0,0 +1,393 @@ + + + + + +ModelSerializer.EnumSerializeMode + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Enum ModelSerializer.EnumSerializeMode

+
+
+ +
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      NAME +
      Encode enum values as their string name.
      +
      NUMERIC +
      Encode enum values as their numeric ID.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static ModelSerializer.EnumSerializeModevalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static ModelSerializer.EnumSerializeMode[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static ModelSerializer.EnumSerializeMode[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (ModelSerializer.EnumSerializeMode c : ModelSerializer.EnumSerializeMode.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static ModelSerializer.EnumSerializeMode valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/model/ModelSerializer.InstantSerializeMode.html b/docs/java/gust/backend/model/ModelSerializer.InstantSerializeMode.html new file mode 100644 index 000000000..ca69bd079 --- /dev/null +++ b/docs/java/gust/backend/model/ModelSerializer.InstantSerializeMode.html @@ -0,0 +1,393 @@ + + + + + +ModelSerializer.InstantSerializeMode + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Enum ModelSerializer.InstantSerializeMode

+
+
+ +
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      ISO8601 +
      Encode temporal instants as ISO8601-formatted strings.
      +
      TIMESTAMP +
      Encode temporal instants as millisecond-precision Unix timestamps.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static ModelSerializer.InstantSerializeModevalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static ModelSerializer.InstantSerializeMode[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static ModelSerializer.InstantSerializeMode[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (ModelSerializer.InstantSerializeMode c : ModelSerializer.InstantSerializeMode.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static ModelSerializer.InstantSerializeMode valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/model/ModelSerializer.SerializationError.html b/docs/java/gust/backend/model/ModelSerializer.SerializationError.html new file mode 100644 index 000000000..8ce256f48 --- /dev/null +++ b/docs/java/gust/backend/model/ModelSerializer.SerializationError.html @@ -0,0 +1,258 @@ + + + + + +ModelSerializer.SerializationError + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class ModelSerializer.SerializationError

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Throwable
    • +
    • +
        +
      • java.lang.Exception
      • +
      • +
          +
        • java.lang.RuntimeException
        • +
        • +
            +
          • gust.backend.model.ModelSerializer.SerializationError
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/ModelSerializer.WriteDisposition.html b/docs/java/gust/backend/model/ModelSerializer.WriteDisposition.html new file mode 100644 index 000000000..77a45d9df --- /dev/null +++ b/docs/java/gust/backend/model/ModelSerializer.WriteDisposition.html @@ -0,0 +1,409 @@ + + + + + +ModelSerializer.WriteDisposition + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Enum ModelSerializer.WriteDisposition

+
+
+ +
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      BLIND +
      Blind writes.
      +
      CREATE +
      Create-style writes.
      +
      UPDATE +
      Update-style writes.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static ModelSerializer.WriteDispositionvalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static ModelSerializer.WriteDisposition[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static ModelSerializer.WriteDisposition[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (ModelSerializer.WriteDisposition c : ModelSerializer.WriteDisposition.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static ModelSerializer.WriteDisposition valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/model/ModelSerializer.html b/docs/java/gust/backend/model/ModelSerializer.html new file mode 100644 index 000000000..7970bf7a2 --- /dev/null +++ b/docs/java/gust/backend/model/ModelSerializer.html @@ -0,0 +1,334 @@ + + + + + +ModelSerializer + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface ModelSerializer<Model extends com.google.protobuf.Message,​Output>

+
+
+
+
    +
  • +
    +
    Type Parameters:
    +
    Model - Data model which a given serializer implementation is responsible for adapting.
    +
    Output - Output type which the serializer will provide when invoked with a matching model instance.
    +
    +
    +
    All Known Subinterfaces:
    +
    CollapsedMessageSerializer<Model>
    +
    +
    +
    All Known Implementing Classes:
    +
    SpannerMutationSerializer
    +
    +
    +
    public interface ModelSerializer<Model extends com.google.protobuf.Message,​Output>
    +
    Describes the surface interface of an object responsible for serializing business data objects (hereinafter, + "models"). In other words, converting Message instances into some generic type
    Output
    .
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods 
      Modifier and TypeMethodDescription
      Outputdeflate​(Model input) +
      Serialize a model instance from the provided object type to the specified output type, throwing exceptions + verbosely if we are unable to correctly, verifiably, and properly export the record.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + + + +
        +
      • +

        deflate

        +
        @Nonnull
        +Output deflate​(@Nonnull
        +               Model input)
        +        throws ModelDeflateException,
        +               java.io.IOException
        +
        Serialize a model instance from the provided object type to the specified output type, throwing exceptions + verbosely if we are unable to correctly, verifiably, and properly export the record.
        +
        +
        Parameters:
        +
        input - Input record object to serialize.
        +
        Returns:
        +
        Serialized record data, of the specified output type.
        +
        Throws:
        +
        ModelDeflateException - If the model fails to export or serialize for any reason.
        +
        java.io.IOException - If an IO error of some kind occurs.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/ModelWriteConflict.html b/docs/java/gust/backend/model/ModelWriteConflict.html new file mode 100644 index 000000000..94744335b --- /dev/null +++ b/docs/java/gust/backend/model/ModelWriteConflict.html @@ -0,0 +1,377 @@ + + + + + +ModelWriteConflict + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class ModelWriteConflict

+
+
+ +
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + +
      Constructors 
      ConstructorDescription
      ModelWriteConflict​(java.lang.Object key, + com.google.protobuf.Message model, + WriteOptions.WriteDisposition expectation) +
      Create a model write exception with a throwable as a cause.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      WriteOptions.WriteDispositiongetFailedExpectation() 
      + +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        ModelWriteConflict

        +
        public ModelWriteConflict​(@Nullable
        +                          java.lang.Object key,
        +                          @Nonnull
        +                          com.google.protobuf.Message model,
        +                          @Nonnull
        +                          WriteOptions.WriteDisposition expectation)
        +
        Create a model write exception with a throwable as a cause.
        +
        +
        Parameters:
        +
        key - Key for the record that failed to write.
        +
        model - Model that failed to write.
        +
        expectation - Expectation that failed to be met.
        +
        +
      • +
      +
    • +
    +
    + +
    + +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/ModelWriteFailure.html b/docs/java/gust/backend/model/ModelWriteFailure.html new file mode 100644 index 000000000..5ece9070d --- /dev/null +++ b/docs/java/gust/backend/model/ModelWriteFailure.html @@ -0,0 +1,333 @@ + + + + + +ModelWriteFailure + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class ModelWriteFailure

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      java.lang.ObjectgetKey() 
      com.google.protobuf.MessagegetModel() 
      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getKey

        +
        @Nullable
        +public java.lang.Object getKey()
        +
        +
        Returns:
        +
        Key for the model that failed to write.
        +
        +
      • +
      + + + +
        +
      • +

        getModel

        +
        @Nonnull
        +public com.google.protobuf.Message getModel()
        +
        +
        Returns:
        +
        Model that failed to write.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/OperationOptions.html b/docs/java/gust/backend/model/OperationOptions.html new file mode 100644 index 000000000..8380087da --- /dev/null +++ b/docs/java/gust/backend/model/OperationOptions.html @@ -0,0 +1,383 @@ + + + + + +OperationOptions + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface OperationOptions

+
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Default Methods 
      Modifier and TypeMethodDescription
      default java.util.Optional<com.google.common.util.concurrent.ListeningScheduledExecutorService>executorService() 
      default java.util.Optional<java.lang.Integer>retries() 
      default java.util.Optional<java.util.concurrent.TimeUnit>timeoutUnit() 
      default java.util.Optional<java.lang.Long>timeoutValue() 
      default java.util.Optional<java.lang.Boolean>transactional() 
      default java.util.Optional<java.lang.Long>updatedAtMicros() 
      default java.util.Optional<java.lang.Long>updatedAtSeconds() 
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        timeoutValue

        +
        @Nonnull
        +default java.util.Optional<java.lang.Long> timeoutValue()
        +
        +
        Returns:
        +
        Value to apply to the operation timeout. If left unspecified, the global default is used.
        +
        +
      • +
      + + + +
        +
      • +

        timeoutUnit

        +
        @Nonnull
        +default java.util.Optional<java.util.concurrent.TimeUnit> timeoutUnit()
        +
        +
        Returns:
        +
        Unit to apply to the operation timeout. If left unspecified, the global default is used.
        +
        +
      • +
      + + + +
        +
      • +

        executorService

        +
        @Nonnull
        +default java.util.Optional<com.google.common.util.concurrent.ListeningScheduledExecutorService> executorService()
        +
        +
        Returns:
        +
        Executor service that should be used for calls that reference this option set.
        +
        +
      • +
      + + + +
        +
      • +

        updatedAtMicros

        +
        @Nonnull
        +default java.util.Optional<java.lang.Long> updatedAtMicros()
        +
        +
        Returns:
        +
        Set a precondition for the precise time (in microseconds) that a record was updated.
        +
        +
      • +
      + + + +
        +
      • +

        updatedAtSeconds

        +
        @Nonnull
        +default java.util.Optional<java.lang.Long> updatedAtSeconds()
        +
        +
        Returns:
        +
        Set a precondition for the precise time (in seconds) that a record was updated.
        +
        +
      • +
      + + + +
        +
      • +

        retries

        +
        @Nonnull
        +default java.util.Optional<java.lang.Integer> retries()
        +
        +
        Returns:
        +
        Number of retries, otherwise the default is used.
        +
        +
      • +
      + + + +
        +
      • +

        transactional

        +
        @Nonnull
        +default java.util.Optional<java.lang.Boolean> transactional()
        +
        +
        Returns:
        +
        Whether to run in a transaction.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/PersistenceDriver.Internals.html b/docs/java/gust/backend/model/PersistenceDriver.Internals.html new file mode 100644 index 000000000..9d96881f5 --- /dev/null +++ b/docs/java/gust/backend/model/PersistenceDriver.Internals.html @@ -0,0 +1,228 @@ + + + + + +PersistenceDriver.Internals + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class PersistenceDriver.Internals

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.model.PersistenceDriver.Internals
    • +
    +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/PersistenceDriver.html b/docs/java/gust/backend/model/PersistenceDriver.html new file mode 100644 index 000000000..499ce36ca --- /dev/null +++ b/docs/java/gust/backend/model/PersistenceDriver.html @@ -0,0 +1,1595 @@ + + + + + +PersistenceDriver + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadIntermediate,​WriteIntermediate>

+
+
+
+
    +
  • +
    +
    Type Parameters:
    +
    Key - Key record type (must be annotated with model role OBJECT_KEY).
    +
    Model - Message/model type which this persistence driver is specialized for.
    +
    ReadIntermediate - Intermediate record format used by the underlying driver implementation during model + de-serialization.
    +
    WriteIntermediate - Intermediate record format used by the underlying driver implementation during model + serialization.
    +
    +
    +
    All Known Subinterfaces:
    +
    DatabaseAdapter<Key,​Model,​ReadRecord,​WriteRecord>, DatabaseDriver<Key,​Model,​ReadRecord,​WriteRecord>, ModelAdapter<Key,​Model,​ReadIntermediate,​WriteIntermediate>
    +
    +
    +
    All Known Implementing Classes:
    +
    FirestoreAdapter, FirestoreDriver, InMemoryAdapter, InMemoryDriver, SpannerAdapter, SpannerDriver
    +
    +
    +
    @Immutable
    +@ThreadSafe
    +public interface PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadIntermediate,​WriteIntermediate>
    +
    Describes the surface of a generic persistence driver, which is capable of accepting arbitrary structured and typed + business data (also called "data models"), and managing them with regard to persistent storage, which includes + storing them when asked, and recalling them when subsequently asked to do so. + +

    Persistence driver implementations do not always guarantee durability of data. For example, + CacheDriver implementations are also PersistenceDrivers, and that entire class of implementations + does not guarantee data will be there when you ask for it at all (relying on cache state is generally + considered to be a very bad practice).

    + +

    Other implementation trees exist (notably, DatabaseDriver) which go the other way, and are expected to + guarantee durability of data across restarts, distributed systems and networks, and failure cases, as applicable. + Database driver implementations also support richer data storage features like querying and indexing.

    +
    +
    See Also:
    +
    `CacheDriver` for persistence drivers with volatile durability guarantees, +`DatabaseDriver` for drivers with rich features and/or strong durability guarantees.
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Nested Class Summary

      + + + + + + + + + + + + +
      Nested Classes 
      Modifier and TypeInterfaceDescription
      static class PersistenceDriver.Internals +
      Default model adapter internals.
      +
      +
    • +
    +
    + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods Default Methods 
      Modifier and TypeMethodDescription
      default com.google.protobuf.Message.BuilderapplyFieldsRecursive​(com.google.protobuf.Message.Builder target, + com.google.protobuf.Message source, + java.util.Set<java.lang.String> markedPaths, + FetchOptions.MaskMode markEffect, + java.lang.String stackPrefix) +
      Apply the fields from source to target, considering any provided FieldMask.
      +
      default ModelapplyMask​(Model instance, + FetchOptions options) +
      Apply mask-related options to the provided instance.
      +
      ModelCodec<Model,​WriteIntermediate,​ReadIntermediate>codec() +
      Acquire an instance of the codec used by this adapter.
      +
      default ReactiveFuture<Model>create​(Key key, + Model model) +
      Create the record specified by model using the optional pre-fabricated key, in underlying storage.
      +
      default ReactiveFuture<Model>create​(Key key, + Model model, + WriteOptions options) +
      Create the record specified by model using the optional pre-fabricated key, and making use of the + specified options, in underlying storage.
      +
      default ReactiveFuture<Model>create​(Model model) +
      Create the record specified by model in underlying storage, provisioning a key or ID for the record if + needed.
      +
      default ReactiveFuture<Model>create​(Model model, + WriteOptions options) +
      Create the record specified by model using the specified set of options, in underlying storage.
      +
      default ReactiveFuture<Key>delete​(Key key) +
      Delete and fully erase the record referenced by key from underlying storage, permanently.
      +
      ReactiveFuture<Key>delete​(Key key, + DeleteOptions options) +
      Low-level record delete method.
      +
      default ReactiveFuture<Key>deleteRecord​(Model model) +
      Delete and fully erase the supplied model from underlying storage, permanently.
      +
      default ReactiveFuture<Key>deleteRecord​(Model model, + DeleteOptions options) +
      Delete and fully erase the supplied model from underlying storage, permanently.
      +
      com.google.common.util.concurrent.ListeningScheduledExecutorServiceexecutorService() +
      Resolve an executor service for use with this persistence driver.
      +
      default Modelfetch​(Key key) +
      Synchronously retrieve a data model instance from underlying storage, addressed by its unique ID.
      +
      default Modelfetch​(Key key, + FetchOptions options) +
      Synchronously retrieve a data model instance from underlying storage, addressed by its unique ID.
      +
      default ReactiveFuture<java.util.Optional<Model>>fetchAsync​(Key key) +
      Asynchronously retrieve a data model instance from storage, which will populate the provided Future value.
      +
      default ReactiveFuture<java.util.Optional<Model>>fetchAsync​(Key key, + FetchOptions options) +
      Asynchronously retrieve a data model instance from storage, which will populate the provided Future value.
      +
      default ReactiveFuture<java.util.Optional<Model>>fetchReactive​(Key key) +
      Reactively retrieve a data model instance from storage, emitting it over a Publisher wrapped in an + Optional.
      +
      default ReactiveFuture<java.util.Optional<Model>>fetchReactive​(Key key, + FetchOptions options) +
      Reactively retrieve a data model instance from storage, emitting it over a Publisher wrapped in an + Optional.
      +
      default java.util.Optional<Model>fetchSafe​(Key key) +
      Safely (and synchronously) retrieve a data model instance from storage, returning Optional.empty() if it + cannot be located, rather than null.
      +
      default java.util.Optional<Model>fetchSafe​(Key key, + FetchOptions options) +
      Safely (and synchronously) retrieve a data model instance from storage, returning Optional.empty() if it + cannot be located, rather than null.
      +
      default java.lang.StringgenerateId​(com.google.protobuf.Message instance) +
      Generate a semi-random opaque token, usable as an ID for a newly-created entity via the model layer.
      +
      default KeygenerateKey​(com.google.protobuf.Message instance) +
      Generate a key for a new entity, which must be stored by this driver, but does not yet have a key.
      +
      ReactiveFuture<Model>persist​(Key key, + Model model, + WriteOptions options) +
      Low-level record persistence method.
      +
      ReactiveFuture<java.util.Optional<Model>>retrieve​(Key key, + FetchOptions options) +
      Low-level record retrieval method.
      +
      default ReactiveFuture<Model>update​(Key key, + Model model) +
      Update the record specified by model, and addressed by key, in underlying storage.
      +
      default ReactiveFuture<Model>update​(Key key, + Model model, + UpdateOptions options) +
      Update the record specified by model, and addressed by key, in underlying storage.
      +
      default ReactiveFuture<Model>update​(Model model) +
      Update the record specified by model in underlying storage, using the existing key or ID value affixed to + the model.
      +
      default ReactiveFuture<Model>update​(Model model, + UpdateOptions options) +
      Update the record specified by model in underlying storage, making use of the specified options, + using the existing key or ID value affixed to the model.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        executorService

        +
        @Nonnull
        +com.google.common.util.concurrent.ListeningScheduledExecutorService executorService()
        +
        Resolve an executor service for use with this persistence driver. Operations will be executed against this as they + are received.
        +
        +
        Returns:
        +
        Scheduled executor service.
        +
        +
      • +
      + + + +
        +
      • +

        codec

        +
        @Nonnull
        +ModelCodec<Model,​WriteIntermediate,​ReadIntermediatecodec()
        +
        Acquire an instance of the codec used by this adapter. Codecs are either injected/otherwise provided during adapter + construction, or they are specified statically if the adapter depends on a specific codec.
        +
        +
        Returns:
        +
        Model codec currently in use by this adapter.
        +
        +
      • +
      + + + +
        +
      • +

        generateId

        +
        @Nonnull
        +default java.lang.String generateId​(@Nonnull
        +                                    com.google.protobuf.Message instance)
        +
        Generate a semi-random opaque token, usable as an ID for a newly-created entity via the model layer. In this case, + the ID is returned directly, so it may be used to populate a key.
        +
        +
        Parameters:
        +
        instance - Model instance to generate an ID for.
        +
        Returns:
        +
        Generated opaque string ID.
        +
        +
      • +
      + + + +
        +
      • +

        generateKey

        +
        @Nonnull
        +default Key generateKey​(@Nonnull
        +                        com.google.protobuf.Message instance)
        +
        Generate a key for a new entity, which must be stored by this driver, but does not yet have a key. If the driver + does not support key generation, UnsupportedOperationException is thrown. + +

        Generated keys are expected to be best-effort unique. Generally, Java's built-in UUID should + do the trick just fine. In more complex or scalable circumstances, this method can be overridden to reach out to + the data engine to generate a key.

        +
        +
        Parameters:
        +
        instance - Default instance of the model type for which a key is desired.
        +
        Returns:
        +
        Generated key for an entity to be stored.
        +
        +
      • +
      + + + +
        +
      • +

        applyFieldsRecursive

        +
        default com.google.protobuf.Message.Builder applyFieldsRecursive​(@Nonnull
        +                                                                 com.google.protobuf.Message.Builder target,
        +                                                                 @Nonnull
        +                                                                 com.google.protobuf.Message source,
        +                                                                 @Nonnull
        +                                                                 java.util.Set<java.lang.String> markedPaths,
        +                                                                 @Nonnull
        +                                                                 FetchOptions.MaskMode markEffect,
        +                                                                 @Nonnull
        +                                                                 java.lang.String stackPrefix)
        +
        Apply the fields from source to target, considering any provided FieldMask. + +

        If the invoking developer chooses to provide markedPaths, they must also supply markEffect. For + each field encountered that matches a property path in markedPaths, markEffect is applied. This + happens recursively for the entire model tree of source (and, consequently, target).

        + +

        After all field computations are complete, the builder is built (and casted, if necessary), before being handed + back to invoking code.

        +
        +
        Parameters:
        +
        target - Builder to set each field value on, as appropriate.
        +
        source - Source instance to pull fields and field values from.
        +
        markedPaths - "Marked" paths - each one will be treated, as encountered, according to markEffect.
        +
        markEffect - Determines how to treat "marked" paths. See FetchOptions.MaskMode for more information.
        +
        stackPrefix - Dotted stack of properties describing the path that got us to this point (via recursion).
        +
        Returns:
        +
        Constructed model, after applying the provided field mask, as applicable.
        +
        See Also:
        +
        Determines how "marked" fields are treated.
        +
        +
      • +
      + + + + + +
        +
      • +

        applyMask

        +
        default Model applyMask​(@Nonnull
        +                        Model instance,
        +                        @Nonnull
        +                        FetchOptions options)
        +
        Apply mask-related options to the provided instance. This may include re-building without certain fields, so + the instance returned may be different.
        +
        +
        Parameters:
        +
        instance - Instance to filter based on any provided field mask.k
        +
        options - Options to apply to the provided instance.
        +
        Returns:
        +
        Model, post-filtering.
        +
        +
      • +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +
      • +

        fetchReactive

        +
        @Nonnull
        +default ReactiveFuture<java.util.Optional<Model>> fetchReactive​(@Nonnull
        +                                                                Key key,
        +                                                                @Nullable
        +                                                                FetchOptions options)
        +
        Reactively retrieve a data model instance from storage, emitting it over a Publisher wrapped in an + Optional. + +

        In other words, if the model cannot be located, exactly one Optional.empty() will be emitted over the + channel. If the model is successfully located and retrieved, it is emitted exactly once. See other method variants, + which allow specification of additional options. This method variant additionally allows the specification of + FetchOptions.

        + +

        Exceptions: Instead of throwing a PersistenceException as other methods do, this operation will + emit the exception over the Publisher channel instead, to enable reactive exception handling.

        +
        +
        Parameters:
        +
        key - Key at which we should look for the requested entity, and emit it if found.
        +
        options - Options to apply to this individual retrieval operation.
        +
        Returns:
        +
        Publisher which will receive exactly-one emitted Optional.empty(), or wrapped object.
        +
        Throws:
        +
        InvalidModelType - If the specified key type is not compatible with model-layer operations.
        +
        PersistenceException - If an unexpected failure occurs, of any kind, while fetching the requested resource.
        +
        MissingAnnotatedField - If the specified key record has no resolvable ID field.
        +
        See Also:
        +
        For a simple, synchronous (-unsafe) version of this method., +For an async version of this method, which produces a .
        +
        +
      • +
      + + + + + + + + + + + +
        +
      • +

        fetchAsync

        +
        @OverridingMethodsMustInvokeSuper
        +@Nonnull
        +default ReactiveFuture<java.util.Optional<Model>> fetchAsync​(@Nonnull
        +                                                             Key key,
        +                                                             @Nullable
        +                                                             FetchOptions options)
        +
        Asynchronously retrieve a data model instance from storage, which will populate the provided Future value. + +

        All futures emitted via the persistence framework (and Gust writ-large) are ListenableFuture-compliant + implementations under the hood. If the requested record cannot be located, Optional.empty() is returned as + the future value, otherwise, the model is returned.

        + +

        This method additionally enables specification of custom FetchOptions, which are applied on a per- + operation basis to override global defaults.

        + +

        Exceptions: Instead of throwing a PersistenceException as other methods do, this operation will + emit the exception over the Future channel instead, or raise the exception in the event + Future.get() is called to surface it in the invoking (or dependent) code.

        +
        +
        Parameters:
        +
        key - Key at which we should look for the requested entity, and emit it if found.
        +
        options - Options to apply to this individual retrieval operation.
        +
        Returns:
        +
        Future value, which resolves to the specified datamodel instance, or Optional.empty() if the record + could not be located by the storage engine.
        +
        Throws:
        +
        InvalidModelType - If the specified key type is not compatible with model-layer operations.
        +
        PersistenceException - If an unexpected failure occurs, of any kind, while fetching the requested resource.
        +
        MissingAnnotatedField - If the specified key record has no resolvable ID field.
        +
        See Also:
        +
        For a simple, synchronous (=unsafe) version of this method., +For a simple, synchronous (-safe) version of this method., +For a reactive version of this method, which returns a .
        +
        +
      • +
      + + + + + + + + + + + +
        +
      • +

        create

        +
        @Nonnull
        +default ReactiveFuture<Modelcreate​(@Nonnull
        +                                     Model model)
        +
        Create the record specified by model in underlying storage, provisioning a key or ID for the record if + needed. The persisted entity is returned or an error occurs. + +

        This operation will enforce the option MUST_NOT_EXIST for the write - i.e., "creating" a record implies + that it must not exist beforehand. Additionally, if the record is missing a unique ID or key (one or the other must + be annotated on the record), then a semi-random value will be generated for the record.

        + +

        The returned record will be re-constituted, with the spliced-in ID or key value, as applicable, and with any + computed or framework-related properties filled in (i.e. automatic timestamping).

        +
        +
        Parameters:
        +
        model - Model to create in underlying storage. Requires a ID or KEY-annotated field.
        +
        Returns:
        +
        Future value, which resolves to the stored model entity, affixed with an assigned ID or key.
        +
        Throws:
        +
        InvalidModelType - If the specified model record is not usable with storage.
        +
        PersistenceException - If an unexpected failure occurs, of any kind, while creating the record.
        +
        MissingAnnotatedField - If a required annotated field cannot be located (i.e. ID or KEY).
        +
        +
      • +
      + + + + + +
        +
      • +

        create

        +
        @Nonnull
        +default ReactiveFuture<Modelcreate​(@Nullable
        +                                     Key key,
        +                                     @Nonnull
        +                                     Model model)
        +
        Create the record specified by model using the optional pre-fabricated key, in underlying storage. + If the provided key is empty or null, the engine will provision a key or ID for the record. The persisted + entity is returned or an error occurs. + +

        This operation will enforce the option MUST_NOT_EXIST for the write - i.e., "creating" a record implies + that it must not exist beforehand. Additionally, if the record is missing a unique ID or key (one or the other must + be annotated on the record), then a semi-random value will be generated for the record.

        + +

        The returned record will be re-constituted, with the spliced-in ID or key value, as applicable, and with any + computed or framework-related properties filled in (i.e. automatic timestamping).

        +
        +
        Parameters:
        +
        model - Model to create in underlying storage. Requires a ID or KEY-annotated field.
        +
        Returns:
        +
        Future value, which resolves to the stored model entity, affixed with an assigned ID or key.
        +
        Throws:
        +
        InvalidModelType - If the specified model record is not usable with storage.
        +
        PersistenceException - If an unexpected failure occurs, of any kind, while creating the record.
        +
        MissingAnnotatedField - If a required annotated field cannot be located (i.e. ID or KEY).
        +
        +
      • +
      + + + + + +
        +
      • +

        create

        +
        @Nonnull
        +default ReactiveFuture<Modelcreate​(@Nonnull
        +                                     Model model,
        +                                     @Nonnull
        +                                     WriteOptions options)
        +
        Create the record specified by model using the specified set of options, in underlying storage. If + the provided mode's key or ID is empty or null, the engine will provision a key or ID for the record. The + persisted entity is returned or an error occurs. + +

        This operation will enforce the option MUST_NOT_EXIST for the write - i.e., "creating" a record implies + that it must not exist beforehand. Additionally, if the record is missing a unique ID or key (one or the other must + be annotated on the record), then a semi-random value will be generated for the record.

        + +

        The returned record will be re-constituted, with the spliced-in ID or key value, as applicable, and with any + computed or framework-related properties filled in (i.e. automatic timestamping).

        +
        +
        Parameters:
        +
        model - Model to create in underlying storage. Requires a ID or KEY-annotated field.
        +
        Returns:
        +
        Future value, which resolves to the stored model entity, affixed with an assigned ID or key.
        +
        Throws:
        +
        InvalidModelType - If the specified model record is not usable with storage.
        +
        PersistenceException - If an unexpected failure occurs, of any kind, while creating the record.
        +
        MissingAnnotatedField - If a required annotated field cannot be located (i.e. ID or KEY).
        +
        +
      • +
      + + + + + +
        +
      • +

        create

        +
        @Nonnull
        +default ReactiveFuture<Modelcreate​(@Nullable
        +                                     Key key,
        +                                     @Nonnull
        +                                     Model model,
        +                                     @Nonnull
        +                                     WriteOptions options)
        +
        Create the record specified by model using the optional pre-fabricated key, and making use of the + specified options, in underlying storage. If the provided key is empty or null, the engine will + provision a key or ID for the record. The persisted entity is returned or an error occurs. + +

        This operation will enforce the option MUST_NOT_EXIST for the write - i.e., "creating" a record implies + that it must not exist beforehand. Additionally, if the record is missing a unique ID or key (one or the other must + be annotated on the record), then a semi-random value will be generated for the record.

        + +

        The returned record will be re-constituted, with the spliced-in ID or key value, as applicable, and with any + computed or framework-related properties filled in (i.e. automatic timestamping).

        +
        +
        Parameters:
        +
        model - Model to create in underlying storage. Requires a ID or KEY-annotated field.
        +
        Returns:
        +
        Future value, which resolves to the stored model entity, affixed with an assigned ID or key.
        +
        Throws:
        +
        InvalidModelType - If the specified model record is not usable with storage.
        +
        PersistenceException - If an unexpected failure occurs, of any kind, while creating the record.
        +
        MissingAnnotatedField - If a required annotated field cannot be located (i.e. ID or KEY).
        +
        java.lang.IllegalArgumentException - If an incompatible WriteOptions.WriteDisposition value is specified.
        +
        +
      • +
      + + + + + +
        +
      • +

        update

        +
        @Nonnull
        +default ReactiveFuture<Modelupdate​(@Nonnull
        +                                     Model model)
        +
        Update the record specified by model in underlying storage, using the existing key or ID value affixed to + the model. The entity is returned in its updated form, or an error occurs. + +

        This operation will enforce the option MUST_EXIST for the write - i.e., "updating" a record implies that + it must exist beforehand. This means, if the record is missing a unique ID or key (one or the other must be + annotated on the record), then an error occurs (specifically, either MissingAnnotatedField) for a missing + schema field, or IllegalStateException for a missing required value).

        + +

        The returned record will be re-constituted, with the ID or key value unmodified, as applicable, and with any + computed or framework-related properties updated in (i.e. automatic update timestamping).

        +
        +
        Parameters:
        +
        model - Model to update in underlying storage. Requires a ID or KEY-annotated field and value.
        +
        Returns:
        +
        Future value, which resolves to the stored model entity, after it has been updated.
        +
        Throws:
        +
        InvalidModelType - If the specified model record is not usable with storage.
        +
        PersistenceException - If an unexpected failure occurs, of any kind, while updated the record.
        +
        MissingAnnotatedField - If a required annotated field cannot be located (i.e. ID or KEY).
        +
        java.lang.IllegalStateException - If a required annotated field value cannot be resolved (i.e. an empty key or ID).
        +
        +
      • +
      + + + + + +
        +
      • +

        update

        +
        @Nonnull
        +default ReactiveFuture<Modelupdate​(@Nonnull
        +                                     Model model,
        +                                     @Nonnull
        +                                     UpdateOptions options)
        +
        Update the record specified by model in underlying storage, making use of the specified options, + using the existing key or ID value affixed to the model. The entity is returned in its updated form, or an error + occurs. + +

        This operation will enforce the option MUST_EXIST for the write - i.e., "updating" a record implies that + it must exist beforehand. This means, if the record is missing a unique ID or key (one or the other must be + annotated on the record), then an error occurs (specifically, either MissingAnnotatedField) for a missing + schema field, or IllegalStateException for a missing required value).

        + +

        The returned record will be re-constituted, with the ID or key value unmodified, as applicable, and with any + computed or framework-related properties updated in (i.e. automatic update timestamping).

        +
        +
        Parameters:
        +
        model - Model to update in underlying storage. Requires a ID or KEY-annotated field and value.
        +
        Returns:
        +
        Future value, which resolves to the stored model entity, after it has been updated.
        +
        Throws:
        +
        InvalidModelType - If the specified model record is not usable with storage.
        +
        PersistenceException - If an unexpected failure occurs, of any kind, while updated the record.
        +
        MissingAnnotatedField - If a required annotated field cannot be located (i.e. ID or KEY).
        +
        java.lang.IllegalStateException - If a required annotated field value cannot be resolved (i.e. an empty key or ID).
        +
        +
      • +
      + + + + + +
        +
      • +

        update

        +
        @Nonnull
        +default ReactiveFuture<Modelupdate​(@Nonnull
        +                                     Key key,
        +                                     @Nonnull
        +                                     Model model)
        +
        Update the record specified by model, and addressed by key, in underlying storage. The entity is + returned in its updated form, or an error occurs. + +

        This operation will enforce the option MUST_EXIST for the write - i.e., "updating" a record implies that + it must exist beforehand. This means, if the record is missing a unique ID or key (one or the other must be + annotated on the record), then an error occurs (specifically, either MissingAnnotatedField) for a missing + schema field, or IllegalStateException for a missing required value).

        + +

        The returned record will be re-constituted, with the ID or key value unmodified, as applicable, and with any + computed or framework-related properties updated in (i.e. automatic update timestamping).

        +
        +
        Parameters:
        +
        model - Model to update in underlying storage. Requires a ID or KEY-annotated field and value.
        +
        Returns:
        +
        Future value, which resolves to the stored model entity, after it has been updated.
        +
        Throws:
        +
        InvalidModelType - If the specified model record is not usable with storage.
        +
        PersistenceException - If an unexpected failure occurs, of any kind, while updated the record.
        +
        MissingAnnotatedField - If a required annotated field cannot be located (i.e. ID or KEY).
        +
        java.lang.IllegalStateException - If a required annotated field value cannot be resolved (i.e. an empty key or ID).
        +
        +
      • +
      + + + + + +
        +
      • +

        update

        +
        @Nonnull
        +default ReactiveFuture<Modelupdate​(@Nonnull
        +                                     Key key,
        +                                     @Nonnull
        +                                     Model model,
        +                                     @Nonnull
        +                                     UpdateOptions options)
        +
        Update the record specified by model, and addressed by key, in underlying storage. The entity is + returned in its updated form, or an error occurs. This method variant additionally allows specification of custom + options for this individual operation. + +

        This operation will enforce the option MUST_EXIST for the write - i.e., "updating" a record implies that + it must exist beforehand. This means, if the record is missing a unique ID or key (one or the other must be + annotated on the record), then an error occurs (specifically, either MissingAnnotatedField) for a missing + schema field, or IllegalStateException for a missing required value).

        + +

        The returned record will be re-constituted, with the ID or key value unmodified, as applicable, and with any + computed or framework-related properties updated in (i.e. automatic update timestamping).

        +
        +
        Parameters:
        +
        model - Model to update in underlying storage. Requires a ID or KEY-annotated field and value.
        +
        Returns:
        +
        Future value, which resolves to the stored model entity, after it has been updated.
        +
        Throws:
        +
        InvalidModelType - If the specified model record is not usable with storage.
        +
        PersistenceException - If an unexpected failure occurs, of any kind, while updated the record.
        +
        MissingAnnotatedField - If a required annotated field cannot be located (i.e. ID or KEY).
        +
        java.lang.IllegalStateException - If a required annotated field value cannot be resolved (i.e. an empty key or ID).
        +
        java.lang.IllegalArgumentException - If an incompatible WriteOptions.WriteDisposition value is specified.
        +
        +
      • +
      + + + + + +
        +
      • +

        persist

        +
        @Nonnull
        +ReactiveFuture<Modelpersist​(@Nullable
        +                              Key key,
        +                              @Nonnull
        +                              Model model,
        +                              @Nonnull
        +                              WriteOptions options)
        +
        Low-level record persistence method. Effectively called by all other create/put variants. Asynchronously write a + data model instance to storage, which will populate the provided ReactiveFuture value. + +

        Optionally, a key may be provided as a nominated value to the storage engine. Whether the engine accepts + nominated keys is up to the implementation. In all cases, the engine must return the key used to store and address + the value henceforth. If the engine does support nominated keys, it must operate in an idempotent + manner with regard to those keys. In other words, repeated calls to create the same entity with the same key will + not cause spurious side-effects - only one record will be created, with the remaining calls being rejected by the + underlying engine.

        + +

        All futures emitted via the persistence framework (and Gust writ-large) are ListenableFuture-compliant + implementations under the hood, but ReactiveFuture allows a model-layer result to be used as a + Future, or a one-item reactive Publisher.

        + +

        This method additionally enables specification of custom WriteOptions, which are applied on a per- + operation basis to override global defaults.

        + +

        Exceptions: Instead of throwing a PersistenceException as other methods do, this operation will + emit the exception over the Future channel instead, or raise the exception in the event + Future.get() is called to surface it in the invoking (or dependent) code.

        +
        +
        Parameters:
        +
        key - Key nominated by invoking code for storing this record. If no key is provided, the underlying storage + engine is expected to allocate one. Where unsupported, PersistenceException will be thrown.
        +
        model - Model to store at the specified key, if provided.
        +
        options - Options to apply to this persist operation.
        +
        Returns:
        +
        Reactive future, which resolves to the key where the provided model is now stored. In no case should this + method return null. Instead, PersistenceException will be thrown.
        +
        Throws:
        +
        InvalidModelType - If the specified key type is not compatible with model-layer operations.
        +
        PersistenceException - If an unexpected failure occurs, of any kind, while fetching the requested resource.
        +
        MissingAnnotatedField - If the specified key record has no resolvable ID field.
        +
        +
      • +
      + + + + + +
        +
      • +

        delete

        +
        @Nonnull
        +default ReactiveFuture<Keydelete​(@Nonnull
        +                                   Key key)
        +
        Delete and fully erase the record referenced by key from underlying storage, permanently. The resulting + future resolves to the provided key value once the operation completes. If any issue occurs (besides encountering + an already-deleted entity, which is not an error), an exception is raised.
        +
        +
        Parameters:
        +
        key - Key referring to the record which should be deleted, permanently, from underlying storage.
        +
        Returns:
        +
        Future, which resolves to the provided key when the operation is complete.
        +
        Throws:
        +
        InvalidModelType - If the specified key type is not compatible with model-layer operations.
        +
        PersistenceException - If an unexpected failure occurs, of any kind, while deleting the requested resource.
        +
        MissingAnnotatedField - If the specified key record has no resolvable ID field.
        +
        java.lang.IllegalStateException - If a required annotated field value cannot be resolved (i.e. an empty key or ID).
        +
        +
      • +
      + + + + + +
        +
      • +

        deleteRecord

        +
        @Nonnull
        +default ReactiveFuture<KeydeleteRecord​(@Nonnull
        +                                         Model model)
        +
        Delete and fully erase the supplied model from underlying storage, permanently. The resulting future + resolves to the provided record's key value once the operation completes. If any issue occurs (besides encountering + an already-deleted entity, which is not an error), an exception is raised.
        +
        +
        Parameters:
        +
        model - Model instance to delete from underlying storage.
        +
        Returns:
        +
        Future, which resolves to the provided key when the operation is complete.
        +
        Throws:
        +
        InvalidModelType - If the specified key type is not compatible with model-layer operations.
        +
        PersistenceException - If an unexpected failure occurs, of any kind, while deleting the requested resource.
        +
        MissingAnnotatedField - If the specified key record has no resolvable ID field.
        +
        java.lang.IllegalStateException - If a required annotated field value cannot be resolved (i.e. an empty key or ID).
        +
        +
      • +
      + + + + + +
        +
      • +

        deleteRecord

        +
        @Nonnull
        +default ReactiveFuture<KeydeleteRecord​(@Nonnull
        +                                         Model model,
        +                                         @Nonnull
        +                                         DeleteOptions options)
        +
        Delete and fully erase the supplied model from underlying storage, permanently. The resulting future + resolves to the provided record's key value once the operation completes. If any issue occurs (besides encountering + an already-deleted entity, which is not an error), an exception is raised.
        +
        +
        Parameters:
        +
        model - Model instance to delete from underlying storage.
        +
        options - Options to apply to this specific delete operation.
        +
        Returns:
        +
        Future, which resolves to the provided key when the operation is complete.
        +
        Throws:
        +
        InvalidModelType - If the specified key type is not compatible with model-layer operations.
        +
        PersistenceException - If an unexpected failure occurs, of any kind, while deleting the requested resource.
        +
        MissingAnnotatedField - If the specified key record has no resolvable ID field.
        +
        java.lang.IllegalStateException - If a required annotated field value cannot be resolved (i.e. an empty key or ID).
        +
        +
      • +
      + + + + + +
        +
      • +

        delete

        +
        @Nonnull
        +ReactiveFuture<Keydelete​(@Nonnull
        +                           Key key,
        +                           @Nonnull
        +                           DeleteOptions options)
        +
        Low-level record delete method. Effectively called by all other delete variants. Asynchronously and permanently + erase an existing data model instance from storage, addressed by its key unique key or ID. + +

        If no key or ID field, or value, may be located, an error is raised (see below for details). This operation is + expected to operate in an idempotent manner (i.e. repeated calls with identical parameters do not yield + different side effects). Calls referring to an already-deleted entity should silently succeed.

        + +

        All futures emitted via the persistence framework (and Gust writ-large) are ListenableFuture-compliant + implementations under the hood, but ReactiveFuture allows a model-layer result to be used as a + Future, or a one-item reactive Publisher.

        + +

        This method additionally enables specification of custom DeleteOptions, which are applied on a per- + operation basis to override global defaults.

        + +

        Exceptions: Instead of throwing a PersistenceException as other methods do, this operation will + emit the exception over the Future channel instead, or raise the exception in the event + Future.get() is called to surface it in the invoking (or dependent) code.

        +
        +
        Parameters:
        +
        key - Unique key referring to the record in storage that should be deleted.
        +
        options - Options to apply to this specific delete operation.
        +
        Returns:
        +
        Future value, which resolves to the deleted record's key when the operation completes.
        +
        Throws:
        +
        InvalidModelType - If the specified key type is not compatible with model-layer operations.
        +
        PersistenceException - If an unexpected failure occurs, of any kind, while deleting the requested resource.
        +
        MissingAnnotatedField - If the specified key record has no resolvable ID field.
        +
        java.lang.IllegalStateException - If a required annotated field value cannot be resolved (i.e. an empty key or ID).
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/model/PersistenceException.html b/docs/java/gust/backend/model/PersistenceException.html new file mode 100644 index 000000000..6967162ae --- /dev/null +++ b/docs/java/gust/backend/model/PersistenceException.html @@ -0,0 +1,259 @@ + + + + + +PersistenceException + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class PersistenceException

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Throwable
    • +
    • +
        +
      • java.lang.Exception
      • +
      • +
          +
        • java.lang.RuntimeException
        • +
        • +
            +
          • gust.backend.model.PersistenceException
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/PersistenceFailure.html b/docs/java/gust/backend/model/PersistenceFailure.html new file mode 100644 index 000000000..f6ea3ebd2 --- /dev/null +++ b/docs/java/gust/backend/model/PersistenceFailure.html @@ -0,0 +1,420 @@ + + + + + +PersistenceFailure + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Enum PersistenceFailure

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      CANCELLED +
      The operation was cancelled.
      +
      INTERNAL +
      An unknown internal error occurred.
      +
      INTERRUPTED +
      The operation was interrupted.
      +
      TIMEOUT +
      The operation timed out.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static PersistenceFailurevalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static PersistenceFailure[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static PersistenceFailure[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (PersistenceFailure c : PersistenceFailure.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static PersistenceFailure valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/model/PersistenceManager.html b/docs/java/gust/backend/model/PersistenceManager.html new file mode 100644 index 000000000..978b31ad2 --- /dev/null +++ b/docs/java/gust/backend/model/PersistenceManager.html @@ -0,0 +1,199 @@ + + + + + +PersistenceManager + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface PersistenceManager<Driver extends PersistenceDriver>

+
+
+
+ +
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/PersistenceOperationFailed.html b/docs/java/gust/backend/model/PersistenceOperationFailed.html new file mode 100644 index 000000000..bc2d8f400 --- /dev/null +++ b/docs/java/gust/backend/model/PersistenceOperationFailed.html @@ -0,0 +1,309 @@ + + + + + +PersistenceOperationFailed + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class PersistenceOperationFailed

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      PersistenceFailuregetFailure() 
      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+ +
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/ProtoModelCodec.html b/docs/java/gust/backend/model/ProtoModelCodec.html new file mode 100644 index 000000000..08b89c491 --- /dev/null +++ b/docs/java/gust/backend/model/ProtoModelCodec.html @@ -0,0 +1,466 @@ + + + + + +ProtoModelCodec + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class ProtoModelCodec<Model extends com.google.protobuf.Message>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.model.ProtoModelCodec<Model>
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    ModelCodec<Model,​EncodedModel,​EncodedModel>
    +
    +
    +
    @Immutable
    +@ThreadSafe
    +public final class ProtoModelCodec<Model extends com.google.protobuf.Message>
    +extends java.lang.Object
    +implements ModelCodec<Model,​EncodedModel,​EncodedModel>
    +
    Defines a ModelCodec which uses Protobuf serialization to export and import protos to and from from raw + byte-strings. These formats are built into Protobuf and are considered extremely reliable, even across languages. + +

    Two formats of Protobuf serialization are supported: +

      +
    • Binary: Most efficient format. Best for production use. Completely illegible to humans.
    • +
    • ProtoJSON: Protocol Buffers-defined JSON translation protocol.
    • +

    +
    +
    See Also:
    +
    Generic model codec interface.
    +
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + + + +
        +
      • +

        forModel

        +
        @Nonnull
        +public static <M extends com.google.protobuf.Message> ProtoModelCodec<M> forModel​(@Nonnull
        +                                                                                  M instance)
        +
        Acquire a Protobuf model codec for the provided model instance. The codec will operate in the default + EncodingMode unless specified otherwise via the other method variants on this object.
        +
        +
        Type Parameters:
        +
        M - Model instance type.
        +
        Parameters:
        +
        instance - Model instance to return a codec for.
        +
        Returns:
        +
        Model codec which serializes and de-serializes to/from Protobuf wire formats.
        +
        +
      • +
      + + + + + +
        +
      • +

        forModel

        +
        @Nonnull
        +public static <M extends com.google.protobuf.Message> ProtoModelCodec<M> forModel​(@Nonnull
        +                                                                                  M instance,
        +                                                                                  @Nonnull
        +                                                                                  EncodingMode mode)
        +
        Acquire a Protobuf model codec for the provided model instance. The codec will operate in the default + EncodingMode unless specified otherwise via the other method variants on this object.
        +
        +
        Type Parameters:
        +
        M - Model instance type.
        +
        Parameters:
        +
        instance - Model instance to return a codec for.
        +
        mode - Wire format mode to operate in (one of JSON or BINARY).
        +
        Returns:
        +
        Model codec which serializes and de-serializes to/from Protobuf wire formats.
        +
        +
      • +
      + + + + + +
        +
      • +

        forModel

        +
        @Nonnull
        +public static <M extends com.google.protobuf.Message> ProtoModelCodec<M> forModel​(@Nonnull
        +                                                                                  M instance,
        +                                                                                  @Nonnull
        +                                                                                  EncodingMode mode,
        +                                                                                  @Nonnull
        +                                                                                  java.util.Optional<com.google.protobuf.TypeRegistry> registry)
        +
        Acquire a Protobuf model codec for the provided model instance. The codec will operate in the default + EncodingMode unless specified otherwise via the other method variants on this object.
        +
        +
        Type Parameters:
        +
        M - Model instance type.
        +
        Parameters:
        +
        instance - Model instance to return a codec for.
        +
        mode - Wire format mode to operate in (one of JSON or BINARY).
        +
        Returns:
        +
        Model codec which serializes and de-serializes to/from Protobuf wire formats.
        +
        +
      • +
      + + + +
        +
      • +

        instance

        +
        @Nonnull
        +public Model instance()
        +
        Description copied from interface: ModelCodec
        +
        Retrieve the default instance stored with this codec. Each Message with a paired ModelCodec retains + a reference to its corresponding default instance.
        +
        +
        Specified by:
        +
        instance in interface ModelCodec<Model extends com.google.protobuf.Message,​EncodedModel,​EncodedModel>
        +
        Returns:
        +
        Default model instance.
        +
        +
      • +
      + + + + + + + + +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/SerializedModel.html b/docs/java/gust/backend/model/SerializedModel.html new file mode 100644 index 000000000..74b23f390 --- /dev/null +++ b/docs/java/gust/backend/model/SerializedModel.html @@ -0,0 +1,631 @@ + + + + + +SerializedModel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class SerializedModel

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.model.SerializedModel
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.util.Map<java.lang.String,​com.google.firestore.v1.Value>
    +
    +
    +
    public final class SerializedModel
    +extends java.lang.Object
    +implements java.util.Map<java.lang.String,​com.google.firestore.v1.Value>
    +
    Describes a model which has been serialized into a backing map of keys and properties.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Nested Class Summary

      +
        +
      • + + +

        Nested classes/interfaces inherited from interface java.util.Map

        +java.util.Map.Entry<K extends java.lang.Object,​V extends java.lang.Object>
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      voidclear() 
      booleancontainsKey​(java.lang.Object key) 
      booleancontainsValue​(java.lang.Object value) 
      java.util.Set<java.util.Map.Entry<java.lang.String,​com.google.firestore.v1.Value>>entrySet() 
      static SerializedModelfactory() +
      Create an empty serialized model, for use as a container.
      +
      static SerializedModelfactory​(java.util.SortedMap<java.lang.String,​com.google.firestore.v1.Value> data) +
      Create a serialized model, pre-filled with the provided backing data.
      +
      com.google.firestore.v1.Valueget​(java.lang.Object key) 
      java.util.SortedMap<java.lang.String,​com.google.firestore.v1.Value>getData() 
      java.util.Optional<com.google.protobuf.Message>getMessage() 
      booleanisEmpty() 
      java.util.Set<java.lang.String>keySet() 
      com.google.firestore.v1.Valueput​(java.lang.String key, + com.google.firestore.v1.Value value) 
      voidputAll​(java.util.Map<? extends java.lang.String,​? extends com.google.firestore.v1.Value> map) 
      com.google.firestore.v1.Valueremove​(java.lang.Object key) 
      intsize() 
      java.util.Collection<com.google.firestore.v1.Value>values() 
      static SerializedModelwrap​(java.util.SortedMap<java.lang.String,​com.google.firestore.v1.Value> data, + com.google.protobuf.Message proto) +
      Create a serialized model, pre-filled with the provided backing data.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
        +
      • + + +

        Methods inherited from interface java.util.Map

        +compute, computeIfAbsent, computeIfPresent, equals, forEach, getOrDefault, hashCode, merge, putIfAbsent, remove, replace, replace, replaceAll
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        factory

        +
        @Nonnull
        +public static SerializedModel factory()
        +
        Create an empty serialized model, for use as a container.
        +
        +
        Returns:
        +
        Empty serialized model.
        +
        +
      • +
      + + + +
        +
      • +

        factory

        +
        @Nonnull
        +public static SerializedModel factory​(@Nonnull
        +                                      java.util.SortedMap<java.lang.String,​com.google.firestore.v1.Value> data)
        +
        Create a serialized model, pre-filled with the provided backing data.
        +
        +
        Parameters:
        +
        data - Data to pre-fill the serialized model with.
        +
        Returns:
        +
        Serialized model, pre-filled with the specified data.
        +
        +
      • +
      + + + +
        +
      • +

        wrap

        +
        @Nonnull
        +public static SerializedModel wrap​(@Nonnull
        +                                   java.util.SortedMap<java.lang.String,​com.google.firestore.v1.Value> data,
        +                                   @Nonnull
        +                                   com.google.protobuf.Message proto)
        +
        Create a serialized model, pre-filled with the provided backing data.
        +
        +
        Parameters:
        +
        data - Data to pre-fill the serialized model with.
        +
        proto - Message instance to wrap, for which `data` is provided.
        +
        Returns:
        +
        Serialized model, pre-filled with the specified data.
        +
        +
      • +
      + + + +
        +
      • +

        getData

        +
        @Nonnull
        +public java.util.SortedMap<java.lang.String,​com.google.firestore.v1.Value> getData()
        +
        +
        Returns:
        +
        Underlying data for this serialized model instance.
        +
        +
      • +
      + + + +
        +
      • +

        getMessage

        +
        @Nonnull
        +public java.util.Optional<com.google.protobuf.Message> getMessage()
        +
        +
        Returns:
        +
        Message instance which spawned this serialized model.
        +
        +
      • +
      + + + +
        +
      • +

        size

        +
        public int size()
        +
        +
        Specified by:
        +
        size in interface java.util.Map<java.lang.String,​com.google.firestore.v1.Value>
        +
        +
      • +
      + + + +
        +
      • +

        isEmpty

        +
        public boolean isEmpty()
        +
        +
        Specified by:
        +
        isEmpty in interface java.util.Map<java.lang.String,​com.google.firestore.v1.Value>
        +
        +
      • +
      + + + +
        +
      • +

        containsKey

        +
        public boolean containsKey​(java.lang.Object key)
        +
        +
        Specified by:
        +
        containsKey in interface java.util.Map<java.lang.String,​com.google.firestore.v1.Value>
        +
        +
      • +
      + + + +
        +
      • +

        get

        +
        public com.google.firestore.v1.Value get​(java.lang.Object key)
        +
        +
        Specified by:
        +
        get in interface java.util.Map<java.lang.String,​com.google.firestore.v1.Value>
        +
        +
      • +
      + + + +
        +
      • +

        put

        +
        public com.google.firestore.v1.Value put​(java.lang.String key,
        +                                         com.google.firestore.v1.Value value)
        +
        +
        Specified by:
        +
        put in interface java.util.Map<java.lang.String,​com.google.firestore.v1.Value>
        +
        +
      • +
      + + + +
        +
      • +

        remove

        +
        public com.google.firestore.v1.Value remove​(java.lang.Object key)
        +
        +
        Specified by:
        +
        remove in interface java.util.Map<java.lang.String,​com.google.firestore.v1.Value>
        +
        +
      • +
      + + + +
        +
      • +

        containsValue

        +
        public boolean containsValue​(java.lang.Object value)
        +
        +
        Specified by:
        +
        containsValue in interface java.util.Map<java.lang.String,​com.google.firestore.v1.Value>
        +
        +
      • +
      + + + +
        +
      • +

        putAll

        +
        public void putAll​(java.util.Map<? extends java.lang.String,​? extends com.google.firestore.v1.Value> map)
        +
        +
        Specified by:
        +
        putAll in interface java.util.Map<java.lang.String,​com.google.firestore.v1.Value>
        +
        +
      • +
      + + + +
        +
      • +

        clear

        +
        public void clear()
        +
        +
        Specified by:
        +
        clear in interface java.util.Map<java.lang.String,​com.google.firestore.v1.Value>
        +
        +
      • +
      + + + +
        +
      • +

        keySet

        +
        public java.util.Set<java.lang.String> keySet()
        +
        +
        Specified by:
        +
        keySet in interface java.util.Map<java.lang.String,​com.google.firestore.v1.Value>
        +
        +
      • +
      + + + +
        +
      • +

        values

        +
        public java.util.Collection<com.google.firestore.v1.Value> values()
        +
        +
        Specified by:
        +
        values in interface java.util.Map<java.lang.String,​com.google.firestore.v1.Value>
        +
        +
      • +
      + + + +
        +
      • +

        entrySet

        +
        public java.util.Set<java.util.Map.Entry<java.lang.String,​com.google.firestore.v1.Value>> entrySet()
        +
        +
        Specified by:
        +
        entrySet in interface java.util.Map<java.lang.String,​com.google.firestore.v1.Value>
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/Transaction.html b/docs/java/gust/backend/model/Transaction.html new file mode 100644 index 000000000..18d6b870b --- /dev/null +++ b/docs/java/gust/backend/model/Transaction.html @@ -0,0 +1,269 @@ + + + + + +Transaction + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class Transaction

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.model.Transaction
    • +
    +
  • +
+
+
    +
  • +
    +
    public final class Transaction
    +extends java.lang.Object
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + +
      Constructors 
      ConstructorDescription
      Transaction() 
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+ +
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/UpdateOptions.html b/docs/java/gust/backend/model/UpdateOptions.html new file mode 100644 index 000000000..469e5354f --- /dev/null +++ b/docs/java/gust/backend/model/UpdateOptions.html @@ -0,0 +1,296 @@ + + + + + +UpdateOptions + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface UpdateOptions

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

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/WriteOptions.WriteDisposition.html b/docs/java/gust/backend/model/WriteOptions.WriteDisposition.html new file mode 100644 index 000000000..efff73250 --- /dev/null +++ b/docs/java/gust/backend/model/WriteOptions.WriteDisposition.html @@ -0,0 +1,408 @@ + + + + + +WriteOptions.WriteDisposition + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Enum WriteOptions.WriteDisposition

+
+
+ +
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      BLIND +
      We don't care.
      +
      MUST_EXIST +
      The record must exist for the write to proceed (an update operation).
      +
      MUST_NOT_EXIST +
      The record must not exist for the write to proceed (a create operation).
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static WriteOptions.WriteDispositionvalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static WriteOptions.WriteDisposition[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static WriteOptions.WriteDisposition[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (WriteOptions.WriteDisposition c : WriteOptions.WriteDisposition.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static WriteOptions.WriteDisposition valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/model/WriteOptions.html b/docs/java/gust/backend/model/WriteOptions.html new file mode 100644 index 000000000..9f5f81727 --- /dev/null +++ b/docs/java/gust/backend/model/WriteOptions.html @@ -0,0 +1,345 @@ + + + + + +WriteOptions + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface WriteOptions

+
+
+
+ +
+
+ +
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        writeMode

        +
        @Nonnull
        +default java.util.Optional<WriteOptions.WriteDispositionwriteMode()
        +
        +
        Returns:
        +
        Specifies the write mode for an operation. Overridden by some methods (for instance, create).
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/model/WriteProxy.html b/docs/java/gust/backend/model/WriteProxy.html new file mode 100644 index 000000000..73de21915 --- /dev/null +++ b/docs/java/gust/backend/model/WriteProxy.html @@ -0,0 +1,399 @@ + + + + + +WriteProxy + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface WriteProxy<Reference>

+
+
+
+
    +
  • +
    +
    public interface WriteProxy<Reference>
    +
    Provides an interface for virtualized object writes during transactions or hierarchical serialization.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods Default Methods 
      Modifier and TypeMethodDescription
      voidcreate​(Reference reference, + SerializedModel message) +
      Create a collapsed `message` in underlying storage, referenced by `reference`.
      +
      voidput​(Reference reference, + SerializedModel message) +
      Save a collapsed `message` in underlying storage, referenced by `reference`.
      +
      default Referenceref​(java.lang.String path) +
      Prepare a database reference, based on the provided `path`.
      +
      Referenceref​(java.lang.String path, + java.lang.String prefix) +
      Prepare a database reference, based on the provided `path`, and prepend the provided transaction-wide `prefix`.
      +
      voidupdate​(Reference reference, + SerializedModel message) +
      Update a collapsed `message` in underlying storage, referenced by `reference`.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        ref

        +
        @Nonnull
        +default Reference ref​(@Nonnull
        +                      java.lang.String path)
        +
        Prepare a database reference, based on the provided `path`. + +

        "Paths" in the model layer refer to hierarchically-stored entities. Typically, a path contains "collections" - + i.e. buckets full of similarly shaped data, and "documents" within those collections - i.e., individual bags of + schema-less key value associations.

        +
        +
        Parameters:
        +
        path - Path to prep a database reference for.
        +
        Returns:
        +
        Prepped database reference corresponding to the provided `path`.
        +
        +
      • +
      + + + +
        +
      • +

        ref

        +
        @Nonnull
        +Reference ref​(@Nonnull
        +              java.lang.String path,
        +              @Nullable
        +              java.lang.String prefix)
        +
        Prepare a database reference, based on the provided `path`, and prepend the provided transaction-wide `prefix`. + +

        "Paths" in the model layer refer to hierarchically-stored entities. Typically, a path contains "collections" - + i.e. buckets full of similarly shaped data, and "documents" within those collections - i.e., individual bags of + schema-less key value associations.

        +
        +
        Parameters:
        +
        path - Path to prep a database reference for.
        +
        prefix - Transaction-wide prefix for this reference.
        +
        Returns:
        +
        Prepped database reference corresponding to the provided `path`.
        +
        +
      • +
      + + + + + +
        +
      • +

        put

        +
        void put​(@Nonnull
        +         Reference reference,
        +         @Nonnull
        +         SerializedModel message)
        +
        Save a collapsed `message` in underlying storage, referenced by `reference`. + + This method specifies the interface that corresponds with the ModelSerializer.WriteDisposition.BLIND mode, + wherein a write occurs without regard to underlying state.
        +
        +
        Parameters:
        +
        reference - Reference / key for the serialized message.
        +
        message - Serialized message (i.e. serialized hierarchical data payload for the message).
        +
        +
      • +
      + + + + + +
        +
      • +

        create

        +
        void create​(@Nonnull
        +            Reference reference,
        +            @Nonnull
        +            SerializedModel message)
        +
        Create a collapsed `message` in underlying storage, referenced by `reference`. If the record is determined to + already exist in underlying storage, the operation fails. + + This method specifies the interface that corresponds with the ModelSerializer.WriteDisposition.CREATE mode, + wherein a write occurs if-and-only-if the entity does not already exist.
        +
        +
        Parameters:
        +
        reference - Reference / key for the serialized message.
        +
        message - Serialized message (i.e. serialized hierarchical data payload for the message).
        +
        +
      • +
      + + + + + +
        +
      • +

        update

        +
        void update​(@Nonnull
        +            Reference reference,
        +            @Nonnull
        +            SerializedModel message)
        +
        Update a collapsed `message` in underlying storage, referenced by `reference`. If the record is determined to be + non-existent in underlying storage, the operation fails. + + This method specifies the interface that corresponds with the ModelSerializer.WriteDisposition.UPDATE mode, + wherein a write occurs if-and-only-if the underlying entity exists during transaction execution.
        +
        +
        Parameters:
        +
        reference - Reference / key for the serialized message.
        +
        message - Serialized message (i.e. serialized hierarchical data payload for the message).
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/CacheDriver.html b/docs/java/gust/backend/model/class-use/CacheDriver.html new file mode 100644 index 000000000..1fa181099 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/CacheDriver.html @@ -0,0 +1,380 @@ + + + + + +Uses of Interface gust.backend.model.CacheDriver + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.CacheDriver

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/CacheOptions.EvictionMode.html b/docs/java/gust/backend/model/class-use/CacheOptions.EvictionMode.html new file mode 100644 index 000000000..b1b795e34 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/CacheOptions.EvictionMode.html @@ -0,0 +1,219 @@ + + + + + +Uses of Class gust.backend.model.CacheOptions.EvictionMode + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.CacheOptions.EvictionMode

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/CacheOptions.html b/docs/java/gust/backend/model/class-use/CacheOptions.html new file mode 100644 index 000000000..36da66f7e --- /dev/null +++ b/docs/java/gust/backend/model/class-use/CacheOptions.html @@ -0,0 +1,204 @@ + + + + + +Uses of Interface gust.backend.model.CacheOptions + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.CacheOptions

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/CollapsedMessage.Operation.html b/docs/java/gust/backend/model/class-use/CollapsedMessage.Operation.html new file mode 100644 index 000000000..b0adc0775 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/CollapsedMessage.Operation.html @@ -0,0 +1,211 @@ + + + + + +Uses of Interface gust.backend.model.CollapsedMessage.Operation + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.CollapsedMessage.Operation

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/CollapsedMessage.html b/docs/java/gust/backend/model/class-use/CollapsedMessage.html new file mode 100644 index 000000000..e046a21d4 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/CollapsedMessage.html @@ -0,0 +1,257 @@ + + + + + +Uses of Class gust.backend.model.CollapsedMessage + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.CollapsedMessage

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/CollapsedMessageCodec.html b/docs/java/gust/backend/model/class-use/CollapsedMessageCodec.html new file mode 100644 index 000000000..67bc22fc2 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/CollapsedMessageCodec.html @@ -0,0 +1,197 @@ + + + + + +Uses of Class gust.backend.model.CollapsedMessageCodec + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.CollapsedMessageCodec

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/CollapsedMessageSerializer.html b/docs/java/gust/backend/model/class-use/CollapsedMessageSerializer.html new file mode 100644 index 000000000..78049fd10 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/CollapsedMessageSerializer.html @@ -0,0 +1,148 @@ + + + + + +Uses of Interface gust.backend.model.CollapsedMessageSerializer + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.CollapsedMessageSerializer

+
+
No usage of gust.backend.model.CollapsedMessageSerializer
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/DatabaseAdapter.html b/docs/java/gust/backend/model/class-use/DatabaseAdapter.html new file mode 100644 index 000000000..2bb0d2e3f --- /dev/null +++ b/docs/java/gust/backend/model/class-use/DatabaseAdapter.html @@ -0,0 +1,256 @@ + + + + + +Uses of Interface gust.backend.model.DatabaseAdapter + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.DatabaseAdapter

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/DatabaseDriver.html b/docs/java/gust/backend/model/class-use/DatabaseDriver.html new file mode 100644 index 000000000..8954a9561 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/DatabaseDriver.html @@ -0,0 +1,306 @@ + + + + + +Uses of Interface gust.backend.model.DatabaseDriver + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.DatabaseDriver

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/DatabaseManager.html b/docs/java/gust/backend/model/class-use/DatabaseManager.html new file mode 100644 index 000000000..8829abaf5 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/DatabaseManager.html @@ -0,0 +1,233 @@ + + + + + +Uses of Interface gust.backend.model.DatabaseManager + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.DatabaseManager

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/DeleteOptions.html b/docs/java/gust/backend/model/class-use/DeleteOptions.html new file mode 100644 index 000000000..56deadb8d --- /dev/null +++ b/docs/java/gust/backend/model/class-use/DeleteOptions.html @@ -0,0 +1,321 @@ + + + + + +Uses of Interface gust.backend.model.DeleteOptions + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.DeleteOptions

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/EncodedModel.html b/docs/java/gust/backend/model/class-use/EncodedModel.html new file mode 100644 index 000000000..8c3ef330e --- /dev/null +++ b/docs/java/gust/backend/model/class-use/EncodedModel.html @@ -0,0 +1,292 @@ + + + + + +Uses of Class gust.backend.model.EncodedModel + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.EncodedModel

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/EncodingMode.html b/docs/java/gust/backend/model/class-use/EncodingMode.html new file mode 100644 index 000000000..8aa2f1df2 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/EncodingMode.html @@ -0,0 +1,245 @@ + + + + + +Uses of Class gust.backend.model.EncodingMode + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.EncodingMode

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use EncodingMode 
    PackageDescription
    gust.backend.model +
    Provides definitions and structure for logic dealing with business data.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of EncodingMode in gust.backend.model

      + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.model that return EncodingMode 
      Modifier and TypeMethodDescription
      EncodingModeEncodedModel.getDataMode() 
      static EncodingModeEncodingMode.valueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static EncodingMode[]EncodingMode.values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.model with parameters of type EncodingMode 
      Modifier and TypeMethodDescription
      static <M extends com.google.protobuf.Message>
      ProtoModelCodec<M>
      ProtoModelCodec.forModel​(M instance, + EncodingMode mode) +
      Acquire a Protobuf model codec for the provided model instance.
      +
      static <M extends com.google.protobuf.Message>
      ProtoModelCodec<M>
      ProtoModelCodec.forModel​(M instance, + EncodingMode mode, + java.util.Optional<com.google.protobuf.TypeRegistry> registry) +
      Acquire a Protobuf model codec for the provided model instance.
      +
      static EncodedModelEncodedModel.wrap​(java.lang.String type, + EncodingMode mode, + byte[] data) +
      Wrap a blob of opaque data, asserting that it is actually an encoded model record.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/FetchOptions.MaskMode.html b/docs/java/gust/backend/model/class-use/FetchOptions.MaskMode.html new file mode 100644 index 000000000..81f35fa7f --- /dev/null +++ b/docs/java/gust/backend/model/class-use/FetchOptions.MaskMode.html @@ -0,0 +1,230 @@ + + + + + +Uses of Class gust.backend.model.FetchOptions.MaskMode + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.FetchOptions.MaskMode

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/FetchOptions.html b/docs/java/gust/backend/model/class-use/FetchOptions.html new file mode 100644 index 000000000..4394d22c7 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/FetchOptions.html @@ -0,0 +1,375 @@ + + + + + +Uses of Interface gust.backend.model.FetchOptions + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.FetchOptions

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/InvalidModelType.html b/docs/java/gust/backend/model/class-use/InvalidModelType.html new file mode 100644 index 000000000..6a329f0fb --- /dev/null +++ b/docs/java/gust/backend/model/class-use/InvalidModelType.html @@ -0,0 +1,297 @@ + + + + + +Uses of Class gust.backend.model.InvalidModelType + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.InvalidModelType

+
+
+
    +
  • + + + + + + + + + + + + + + + + +
    Packages that use InvalidModelType 
    PackageDescription
    gust.backend.driver.inmemory +
    Provides a reference implementation of a persistence driver, backed by a concurrent map.
    +
    gust.backend.model +
    Provides definitions and structure for logic dealing with business data.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of InvalidModelType in gust.backend.driver.inmemory

      + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.driver.inmemory that throw InvalidModelType 
      Modifier and TypeMethodDescription
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      InMemoryAdapter<K,​M>
      InMemoryAdapter.acquire​(K keyInstance, + M instance, + com.google.common.util.concurrent.ListeningScheduledExecutorService executorService) +
      Acquire an instance of the InMemoryAdapter, specialized for the provided empty model instance.
      +
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      InMemoryAdapter<K,​M>
      InMemoryAdapter.acquire​(K keyInstance, + M instance, + java.util.Optional<CacheDriver<K,​M>> cache, + com.google.common.util.concurrent.ListeningScheduledExecutorService executorService) +
      Acquire an instance of the InMemoryAdapter, specialized for the provided empty model instance, optionally + specifying a CacheDriver to use.
      +
      +
      +
    • +
    • +
      + + +

      Uses of InvalidModelType in gust.backend.model

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.model that throw InvalidModelType 
      Modifier and TypeMethodDescription
      static voidModelMetadata.enforceAnyRole​(com.google.protobuf.Descriptors.Descriptor descriptor, + tools.elide.core.DatapointType... types) +
      Enforce that a particular schema descriptor matches any of the provided DatapointType + annotations.
      +
      static voidModelMetadata.enforceAnyRole​(com.google.protobuf.Message model, + tools.elide.core.DatapointType... types) +
      Enforce that a particular datamodel type matches any of the provided DatapointType + annotations.
      +
      static voidModelMetadata.enforceRole​(com.google.protobuf.Descriptors.Descriptor descriptor, + tools.elide.core.DatapointType type) +
      Enforce that a particular datamodel schema descriptor matches the provided DatapointType + annotation.
      +
      static voidModelMetadata.enforceRole​(com.google.protobuf.Message model, + tools.elide.core.DatapointType type) +
      Enforce that a particular model instance matches the provided DatapointType annotation.
      +
      static java.util.Optional<ModelMetadata.FieldPointer>ModelMetadata.idField​(com.google.protobuf.Descriptors.Descriptor descriptor) +
      Resolve a pointer to the provided schema type descriptor's ID field, whether or not it has a value.
      +
      static java.util.Optional<ModelMetadata.FieldPointer>ModelMetadata.idField​(com.google.protobuf.Message instance) +
      Resolve a pointer to the provided model instance's ID field, whether or not it has a value.
      +
      static java.util.Optional<ModelMetadata.FieldPointer>ModelMetadata.keyField​(com.google.protobuf.Descriptors.Descriptor descriptor) +
      Resolve a pointer to the provided schema type descriptor's KEY field, whether or not it has a value + assigned.
      +
      static java.util.Optional<ModelMetadata.FieldPointer>ModelMetadata.keyField​(com.google.protobuf.Message instance) +
      Resolve a pointer to the provided schema type descriptor's KEY field, whether or not it has a value + assigned.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/MissingAnnotatedField.html b/docs/java/gust/backend/model/class-use/MissingAnnotatedField.html new file mode 100644 index 000000000..ad0d16a2a --- /dev/null +++ b/docs/java/gust/backend/model/class-use/MissingAnnotatedField.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.model.MissingAnnotatedField + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.MissingAnnotatedField

+
+
No usage of gust.backend.model.MissingAnnotatedField
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/ModelAdapter.html b/docs/java/gust/backend/model/class-use/ModelAdapter.html new file mode 100644 index 000000000..32c24a533 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/ModelAdapter.html @@ -0,0 +1,289 @@ + + + + + +Uses of Interface gust.backend.model.ModelAdapter + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.ModelAdapter

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/ModelCodec.html b/docs/java/gust/backend/model/class-use/ModelCodec.html new file mode 100644 index 000000000..76a8555bf --- /dev/null +++ b/docs/java/gust/backend/model/class-use/ModelCodec.html @@ -0,0 +1,345 @@ + + + + + +Uses of Interface gust.backend.model.ModelCodec + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.ModelCodec

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/ModelDeflateException.html b/docs/java/gust/backend/model/class-use/ModelDeflateException.html new file mode 100644 index 000000000..5c5097ad0 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/ModelDeflateException.html @@ -0,0 +1,232 @@ + + + + + +Uses of Class gust.backend.model.ModelDeflateException + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.ModelDeflateException

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/ModelDeserializer.DeserializationError.html b/docs/java/gust/backend/model/class-use/ModelDeserializer.DeserializationError.html new file mode 100644 index 000000000..70c0aef0b --- /dev/null +++ b/docs/java/gust/backend/model/class-use/ModelDeserializer.DeserializationError.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.model.ModelDeserializer.DeserializationError + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.ModelDeserializer.DeserializationError

+
+
No usage of gust.backend.model.ModelDeserializer.DeserializationError
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/ModelDeserializer.html b/docs/java/gust/backend/model/class-use/ModelDeserializer.html new file mode 100644 index 000000000..4f38681a8 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/ModelDeserializer.html @@ -0,0 +1,293 @@ + + + + + +Uses of Interface gust.backend.model.ModelDeserializer + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.ModelDeserializer

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/ModelInflateException.html b/docs/java/gust/backend/model/class-use/ModelInflateException.html new file mode 100644 index 000000000..bf0bbec74 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/ModelInflateException.html @@ -0,0 +1,232 @@ + + + + + +Uses of Class gust.backend.model.ModelInflateException + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.ModelInflateException

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/ModelMetadata.FieldContainer.html b/docs/java/gust/backend/model/class-use/ModelMetadata.FieldContainer.html new file mode 100644 index 000000000..92a320765 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/ModelMetadata.FieldContainer.html @@ -0,0 +1,268 @@ + + + + + +Uses of Class gust.backend.model.ModelMetadata.FieldContainer + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.ModelMetadata.FieldContainer

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use ModelMetadata.FieldContainer 
    PackageDescription
    gust.backend.model +
    Provides definitions and structure for logic dealing with business data.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of ModelMetadata.FieldContainer in gust.backend.model

      + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.model that return ModelMetadata.FieldContainer 
      Modifier and TypeMethodDescription
      static <V> ModelMetadata.FieldContainer<V>ModelMetadata.pluck​(com.google.protobuf.Message instance, + ModelMetadata.FieldPointer fieldPointer) +
      Pluck a field value, addressed by a ModelMetadata.FieldPointer, from the provided instance.
      +
      static <V> ModelMetadata.FieldContainer<V>ModelMetadata.pluck​(com.google.protobuf.Message instance, + java.lang.String path) +
      Return a single field value container, plucked from the specified deep path, in dot form, using the regular + protobuf-definition names for each field.
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.model that return types with arguments of type ModelMetadata.FieldContainer 
      Modifier and TypeMethodDescription
      static java.util.SortedSet<ModelMetadata.FieldContainer<java.lang.Object>>ModelMetadata.pluckAll​(com.google.protobuf.Message instance, + com.google.protobuf.FieldMask mask) +
      Return an iterable containing plucked value containers for each field mentioned in mask, that is present on + instance with an initialized value.
      +
      static java.util.SortedSet<ModelMetadata.FieldContainer<java.lang.Object>>ModelMetadata.pluckAll​(com.google.protobuf.Message instance, + com.google.protobuf.FieldMask mask, + java.lang.Boolean normalize) +
      Return an iterable containing plucked value containers for each field mentioned in mask, that is present on + instance with an initialized value.
      +
      static java.util.stream.Stream<ModelMetadata.FieldContainer<java.lang.Object>>ModelMetadata.pluckStream​(com.google.protobuf.Message instance, + com.google.protobuf.FieldMask mask) +
      Return a stream which emits plucked value containers for each field mentioned in mask, that is present on + instance with an initialized value.
      +
      static java.util.stream.Stream<ModelMetadata.FieldContainer<java.lang.Object>>ModelMetadata.pluckStream​(com.google.protobuf.Message instance, + com.google.protobuf.FieldMask mask, + java.lang.Boolean normalize) +
      Return a stream which emits plucked value containers for each field mentioned in mask, that is present on + instance with an initialized value.
      +
      + + + + + + + + + + + + + +
      Methods in gust.backend.model with parameters of type ModelMetadata.FieldContainer 
      Modifier and TypeMethodDescription
      intModelMetadata.FieldContainer.compareTo​(ModelMetadata.FieldContainer<V> other)
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/ModelMetadata.FieldPointer.html b/docs/java/gust/backend/model/class-use/ModelMetadata.FieldPointer.html new file mode 100644 index 000000000..c778d2123 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/ModelMetadata.FieldPointer.html @@ -0,0 +1,755 @@ + + + + + +Uses of Class gust.backend.model.ModelMetadata.FieldPointer + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.ModelMetadata.FieldPointer

+
+
+
    +
  • + + + + + + + + + + + + + + + + +
    Packages that use ModelMetadata.FieldPointer 
    PackageDescription
    gust.backend.driver.spanner +
    Provides a DatabaseDriver implementation for integration with Google Cloud Spanner.
    +
    gust.backend.model +
    Provides definitions and structure for logic dealing with business data.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of ModelMetadata.FieldPointer in gust.backend.driver.spanner

      + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.driver.spanner that return types with arguments of type ModelMetadata.FieldPointer 
      Modifier and TypeMethodDescription
      static java.util.function.Predicate<ModelMetadata.FieldPointer>SpannerUtil.onlySpannerEligibleFields​(SpannerDriverSettings settings) +
      Return a Predicate implementation which operates on ModelMetadata.FieldPointer objects to determine eligibility + for interaction with Spanner.
      +
      static java.util.function.Predicate<ModelMetadata.FieldPointer>SpannerUtil.onlySpannerEligibleFields​(java.util.SortedSet<java.lang.String> eligibleFields, + SpannerDriverSettings settings) +
      Return a Predicate implementation which determines field eligibility with regard to interaction with + Cloud Spanner, optionally considering the provided set of circumstantial higher-order eligible fields (for + instance, in the case of a known property projection).
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.driver.spanner with parameters of type ModelMetadata.FieldPointer 
      Modifier and TypeMethodDescription
      static java.util.Optional<tools.elide.core.TableFieldOptions>SpannerUtil.columnOpts​(ModelMetadata.FieldPointer fieldPointer) +
      Given a pre-resolved ModelMetadata.FieldPointer, resolve any present TableFieldOptions.
      +
      static java.util.Optional<tools.elide.core.FieldPersistenceOptions>SpannerUtil.fieldOpts​(ModelMetadata.FieldPointer fieldPointer) +
      Given a pre-resolved ModelMetadata.FieldPointer, resolve any present generic FieldPersistenceOptions.
      +
      static com.google.cloud.spanner.TypeSpannerUtil.maybeWrapType​(ModelMetadata.FieldPointer field, + com.google.cloud.spanner.Type inner) +
      If a concrete type is marked as repeated, wrap it in an array type.
      +
      static intSpannerUtil.resolveColumnIndex​(com.google.cloud.spanner.Struct source, + ModelMetadata.FieldPointer fieldPointer, + java.lang.String name) +
      Given a Spanner row result expressed as a Struct and a ModelMetadata.FieldPointer which is expected to be + present, with a pre-resolved column name, return the numeric column index.
      +
      static java.lang.StringSpannerUtil.resolveColumnName​(ModelMetadata.FieldPointer fieldPointer, + java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts, + java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts, + SpannerDriverSettings settings) +
      Given a resolved field pointer and set of annotations, resolve the expected/configured column name in Spanner for + a given typed model field.
      +
      static com.google.cloud.spanner.TypeSpannerUtil.resolveColumnType​(ModelMetadata.FieldPointer fieldPointer, + java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts, + java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts, + SpannerDriverSettings settings) +
      Given a resolved and eligible ModelMetadata.FieldPointer for a model field which should interact with Spanner, resolve + an expected Spanner Type, including any nested structure or complex objects, as mediated and regulated by + annotations on the model field.
      +
      static com.google.cloud.spanner.ValueSpannerUtil.resolveColumnValue​(com.google.cloud.spanner.Struct source, + ModelMetadata.FieldPointer fieldPointer, + java.util.Optional<tools.elide.core.SpannerFieldOptions> spannerOpts, + java.util.Optional<tools.elide.core.TableFieldOptions> columnOpts, + SpannerDriverSettings driverSettings) +
      Given a Spanner row result expressed as a Struct and a ModelMetadata.FieldPointer which is expected to be + present, resolve any present Value.
      +
      static com.google.cloud.spanner.TypeSpannerUtil.resolveDefaultType​(ModelMetadata.FieldPointer pointer, + com.google.protobuf.Descriptors.FieldDescriptor.Type protoType, + SpannerDriverSettings settings) +
      Resolve a default Spanner type for the provided field `pointer`.
      +
      static com.google.cloud.spanner.TypeSpannerUtil.resolveDefaultType​(ModelMetadata.FieldPointer pointer, + SpannerDriverSettings settings) +
      Resolve a default Spanner type for the provided field `pointer`.
      +
      static java.lang.StringSpannerUtil.resolveKeyColumn​(ModelMetadata.FieldPointer idField, + SpannerDriverSettings driverSettings) +
      For a given key field pointer, resolve the column name which should be used for the primary key in Spanner, + according to the annotation structure present on the key.
      +
      static com.google.cloud.spanner.TypeSpannerUtil.resolveKeyType​(ModelMetadata.FieldPointer idField) +
      For a given key field pointer, resolve the column type which should be used for the primary key in Spanner, + according to the annotation structure present on the key.
      +
      static com.google.cloud.spanner.TypeSpannerUtil.resolveType​(ModelMetadata.FieldPointer pointer, + tools.elide.core.SpannerOptions.SpannerType spannerType) +
      Resolve a normalized Spanner type for the provided field `pointer`, with the explicit provided `spannerType`.
      +
      static java.util.Optional<tools.elide.core.SpannerFieldOptions>SpannerUtil.spannerOpts​(ModelMetadata.FieldPointer fieldPointer) +
      Given a pre-resolved ModelMetadata.FieldPointer, resolve any present SpannerFieldOptions.
      +
      +
      +
    • +
    • +
      + + +

      Uses of ModelMetadata.FieldPointer in gust.backend.model

      + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.model that return ModelMetadata.FieldPointer 
      Modifier and TypeMethodDescription
      static ModelMetadata.FieldPointerModelMetadata.FieldPointer.fieldAtName​(com.google.protobuf.Descriptors.Descriptor model, + java.lang.String name) +
      Wrap the field at the specified name on the provided model.
      +
      ModelMetadata.FieldPointerModelMetadata.FieldContainer.getField() +
      Pointer to the field holding the specified value.
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.model that return types with arguments of type ModelMetadata.FieldPointer 
      Modifier and TypeMethodDescription
      static java.lang.Iterable<ModelMetadata.FieldPointer>ModelMetadata.allFields​(com.google.protobuf.Descriptors.Descriptor descriptor) +
      Crawl all fields, recursively, on the descriptor provided.
      +
      static java.lang.Iterable<ModelMetadata.FieldPointer>ModelMetadata.allFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate) +
      Crawl all fields, recursively, on the descriptor provided.
      +
      static java.lang.Iterable<ModelMetadata.FieldPointer>ModelMetadata.allFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.lang.Boolean recursive) +
      Crawl all fields, recursively, on the descriptor provided.
      +
      static java.lang.Iterable<ModelMetadata.FieldPointer>ModelMetadata.allFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.util.function.Predicate<ModelMetadata.FieldPointer> decider) +
      Crawl all fields, recursively, on the descriptor provided.
      +
      static <E> java.util.Optional<ModelMetadata.FieldPointer>ModelMetadata.annotatedField​(com.google.protobuf.Descriptors.Descriptor descriptor, + com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext) +
      Resolve a ModelMetadata.FieldPointer within the scope of the provided model descriptor, that holds values for the + specified metadata annotation ext.
      +
      static <E> java.util.Optional<ModelMetadata.FieldPointer>ModelMetadata.annotatedField​(com.google.protobuf.Descriptors.Descriptor descriptor, + com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext, + java.lang.Boolean recursive, + java.util.Optional<java.util.function.Function<E,​java.lang.Boolean>> filter) +
      Resolve a ModelMetadata.FieldPointer within the scope of the provided model descriptor, that holds values for the + specified metadata annotation ext.
      +
      static <E> java.util.Optional<ModelMetadata.FieldPointer>ModelMetadata.annotatedField​(com.google.protobuf.Descriptors.Descriptor descriptor, + com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext, + java.util.Optional<java.util.function.Function<E,​java.lang.Boolean>> filter) +
      Resolve a ModelMetadata.FieldPointer within the scope of the provided model descriptor, that holds values for the + specified metadata annotation ext.
      +
      static <E> java.util.Optional<ModelMetadata.FieldPointer>ModelMetadata.annotatedField​(com.google.protobuf.Message instance, + com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext) +
      Resolve a ModelMetadata.FieldPointer within the scope of instance, that holds values for the specified metadata + annotation ext.
      +
      static <E> java.util.Optional<ModelMetadata.FieldPointer>ModelMetadata.annotatedField​(com.google.protobuf.Message instance, + com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext, + java.lang.Boolean recursive) +
      Resolve a ModelMetadata.FieldPointer within the scope of instance, that holds values for the specified metadata + annotation ext.
      +
      static <E> java.util.Optional<ModelMetadata.FieldPointer>ModelMetadata.annotatedField​(com.google.protobuf.Message instance, + com.google.protobuf.GeneratedMessage.GeneratedExtension<com.google.protobuf.DescriptorProtos.FieldOptions,​E> ext, + java.lang.Boolean recursive, + java.util.Optional<java.util.function.Function<E,​java.lang.Boolean>> filter) +
      Resolve a ModelMetadata.FieldPointer within the scope of instance, that holds values for the specified metadata + annotation ext.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>ModelMetadata.forEachField​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate) +
      Crawl all fields, recursively, on the provided descriptor for a model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>ModelMetadata.forEachField​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + boolean recursive) +
      Crawl all fields, recursively, on the provided descriptor for a model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>ModelMetadata.forEachField​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.util.function.Predicate<ModelMetadata.FieldPointer> decider) +
      Crawl all fields, recursively, on the provided descriptor for a model instance.
      +
      static java.util.Optional<ModelMetadata.FieldPointer>ModelMetadata.idField​(com.google.protobuf.Descriptors.Descriptor descriptor) +
      Resolve a pointer to the provided schema type descriptor's ID field, whether or not it has a value.
      +
      static java.util.Optional<ModelMetadata.FieldPointer>ModelMetadata.idField​(com.google.protobuf.Message instance) +
      Resolve a pointer to the provided model instance's ID field, whether or not it has a value.
      +
      static java.util.Optional<ModelMetadata.FieldPointer>ModelMetadata.keyField​(com.google.protobuf.Descriptors.Descriptor descriptor) +
      Resolve a pointer to the provided schema type descriptor's KEY field, whether or not it has a value + assigned.
      +
      static java.util.Optional<ModelMetadata.FieldPointer>ModelMetadata.keyField​(com.google.protobuf.Message instance) +
      Resolve a pointer to the provided schema type descriptor's KEY field, whether or not it has a value + assigned.
      +
      static java.util.Optional<ModelMetadata.FieldPointer>ModelMetadata.resolveField​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.lang.String path) +
      Resolve an arbitrary field pointer from the provided model type escriptor, specified by the given + path to the property.
      +
      static java.util.Optional<ModelMetadata.FieldPointer>ModelMetadata.resolveField​(com.google.protobuf.Message instance, + java.lang.String path) +
      Resolve an arbitrary field pointer from the provided model instance, specified by the given path to + the property.
      +
      static <M extends com.google.protobuf.Message>
      java.util.stream.Stream<ModelMetadata.FieldPointer>
      ModelMetadata.streamFields​(com.google.protobuf.Descriptors.Descriptor descriptor) +
      Crawl all fields, recursively, on the descriptor associated with the provided model instance, and return them in + a stream.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>ModelMetadata.streamFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.function.Predicate<ModelMetadata.FieldPointer> predicate) +
      Crawl all fields, recursively, on the descriptor associated with the provided model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>ModelMetadata.streamFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate) +
      Crawl all fields, recursively, on the descriptor associated with the provided model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>ModelMetadata.streamFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.lang.Boolean recursive) +
      Crawl all fields, recursively, on the provided descriptor for a model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>ModelMetadata.streamFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.util.function.Predicate<ModelMetadata.FieldPointer> decider) +
      Crawl all fields, recursively, on the provided descriptor for a model instance.
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.model with parameters of type ModelMetadata.FieldPointer 
      Modifier and TypeMethodDescription
      intModelMetadata.FieldPointer.compareTo​(ModelMetadata.FieldPointer other)
      static <V> ModelMetadata.FieldContainer<V>ModelMetadata.pluck​(com.google.protobuf.Message instance, + ModelMetadata.FieldPointer fieldPointer) +
      Pluck a field value, addressed by a ModelMetadata.FieldPointer, from the provided instance.
      +
      static <Model extends com.google.protobuf.Message,​Value>
      Model
      ModelMetadata.splice​(com.google.protobuf.Message instance, + ModelMetadata.FieldPointer field, + java.util.Optional<Value> val) +
      Splice the provided optional value (or clear any existing value) at the specified field pointer, in the + provided model instance.
      +
      static <Builder extends com.google.protobuf.Message.Builder,​Value>
      Builder
      ModelMetadata.spliceBuilder​(com.google.protobuf.Message.Builder builder, + ModelMetadata.FieldPointer field, + java.util.Optional<Value> val) +
      Splice the provided optional value (or clear any existing value) at the specified field pointer, in the + provided model instance.
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Method parameters in gust.backend.model with type arguments of type ModelMetadata.FieldPointer 
      Modifier and TypeMethodDescription
      static java.lang.Iterable<ModelMetadata.FieldPointer>ModelMetadata.allFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate) +
      Crawl all fields, recursively, on the descriptor provided.
      +
      static java.lang.Iterable<ModelMetadata.FieldPointer>ModelMetadata.allFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.lang.Boolean recursive) +
      Crawl all fields, recursively, on the descriptor provided.
      +
      static java.lang.Iterable<ModelMetadata.FieldPointer>ModelMetadata.allFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.util.function.Predicate<ModelMetadata.FieldPointer> decider) +
      Crawl all fields, recursively, on the descriptor provided.
      +
      static java.lang.Iterable<ModelMetadata.FieldPointer>ModelMetadata.allFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.util.function.Predicate<ModelMetadata.FieldPointer> decider) +
      Crawl all fields, recursively, on the descriptor provided.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>ModelMetadata.forEachField​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate) +
      Crawl all fields, recursively, on the provided descriptor for a model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>ModelMetadata.forEachField​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + boolean recursive) +
      Crawl all fields, recursively, on the provided descriptor for a model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>ModelMetadata.forEachField​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.util.function.Predicate<ModelMetadata.FieldPointer> decider) +
      Crawl all fields, recursively, on the provided descriptor for a model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>ModelMetadata.forEachField​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.util.function.Predicate<ModelMetadata.FieldPointer> decider) +
      Crawl all fields, recursively, on the provided descriptor for a model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>ModelMetadata.streamFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.function.Predicate<ModelMetadata.FieldPointer> predicate) +
      Crawl all fields, recursively, on the descriptor associated with the provided model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>ModelMetadata.streamFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate) +
      Crawl all fields, recursively, on the descriptor associated with the provided model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>ModelMetadata.streamFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.lang.Boolean recursive) +
      Crawl all fields, recursively, on the provided descriptor for a model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>ModelMetadata.streamFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.util.function.Predicate<ModelMetadata.FieldPointer> decider) +
      Crawl all fields, recursively, on the provided descriptor for a model instance.
      +
      static java.util.stream.Stream<ModelMetadata.FieldPointer>ModelMetadata.streamFields​(com.google.protobuf.Descriptors.Descriptor descriptor, + java.util.Optional<java.util.function.Predicate<ModelMetadata.FieldPointer>> predicate, + java.util.function.Predicate<ModelMetadata.FieldPointer> decider) +
      Crawl all fields, recursively, on the provided descriptor for a model instance.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/ModelMetadata.html b/docs/java/gust/backend/model/class-use/ModelMetadata.html new file mode 100644 index 000000000..c177d81b2 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/ModelMetadata.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.model.ModelMetadata + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.ModelMetadata

+
+
No usage of gust.backend.model.ModelMetadata
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/ModelSerializer.EnumSerializeMode.html b/docs/java/gust/backend/model/class-use/ModelSerializer.EnumSerializeMode.html new file mode 100644 index 000000000..458dadaba --- /dev/null +++ b/docs/java/gust/backend/model/class-use/ModelSerializer.EnumSerializeMode.html @@ -0,0 +1,204 @@ + + + + + +Uses of Class gust.backend.model.ModelSerializer.EnumSerializeMode + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.ModelSerializer.EnumSerializeMode

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/ModelSerializer.InstantSerializeMode.html b/docs/java/gust/backend/model/class-use/ModelSerializer.InstantSerializeMode.html new file mode 100644 index 000000000..ebaa59664 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/ModelSerializer.InstantSerializeMode.html @@ -0,0 +1,204 @@ + + + + + +Uses of Class gust.backend.model.ModelSerializer.InstantSerializeMode + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.ModelSerializer.InstantSerializeMode

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/ModelSerializer.SerializationError.html b/docs/java/gust/backend/model/class-use/ModelSerializer.SerializationError.html new file mode 100644 index 000000000..ded6396f1 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/ModelSerializer.SerializationError.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.model.ModelSerializer.SerializationError + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.ModelSerializer.SerializationError

+
+
No usage of gust.backend.model.ModelSerializer.SerializationError
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/ModelSerializer.WriteDisposition.html b/docs/java/gust/backend/model/class-use/ModelSerializer.WriteDisposition.html new file mode 100644 index 000000000..505f2794a --- /dev/null +++ b/docs/java/gust/backend/model/class-use/ModelSerializer.WriteDisposition.html @@ -0,0 +1,204 @@ + + + + + +Uses of Class gust.backend.model.ModelSerializer.WriteDisposition + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.ModelSerializer.WriteDisposition

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/ModelSerializer.html b/docs/java/gust/backend/model/class-use/ModelSerializer.html new file mode 100644 index 000000000..10b0b0112 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/ModelSerializer.html @@ -0,0 +1,290 @@ + + + + + +Uses of Interface gust.backend.model.ModelSerializer + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.ModelSerializer

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/ModelWriteConflict.html b/docs/java/gust/backend/model/class-use/ModelWriteConflict.html new file mode 100644 index 000000000..d3c94713f --- /dev/null +++ b/docs/java/gust/backend/model/class-use/ModelWriteConflict.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.model.ModelWriteConflict + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.ModelWriteConflict

+
+
No usage of gust.backend.model.ModelWriteConflict
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/ModelWriteFailure.html b/docs/java/gust/backend/model/class-use/ModelWriteFailure.html new file mode 100644 index 000000000..6a083c957 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/ModelWriteFailure.html @@ -0,0 +1,196 @@ + + + + + +Uses of Class gust.backend.model.ModelWriteFailure + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.ModelWriteFailure

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/OperationOptions.html b/docs/java/gust/backend/model/class-use/OperationOptions.html new file mode 100644 index 000000000..5459a99de --- /dev/null +++ b/docs/java/gust/backend/model/class-use/OperationOptions.html @@ -0,0 +1,225 @@ + + + + + +Uses of Interface gust.backend.model.OperationOptions + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.OperationOptions

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use OperationOptions 
    PackageDescription
    gust.backend.model +
    Provides definitions and structure for logic dealing with business data.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of OperationOptions in gust.backend.model

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Subinterfaces of OperationOptions in gust.backend.model 
      Modifier and TypeInterfaceDescription
      interface CacheOptions +
      Specifies operational options related to caching.
      +
      interface DeleteOptions +
      Describes options specifically involved with deleting existing model entities.
      +
      interface FetchOptions +
      Specifies options which may be applied, generically, to model instance fetch operations implemented through the + PersistenceDriver interface.
      +
      interface UpdateOptions +
      Describes options specifically involved with updating existing model entities.
      +
      interface WriteOptions +
      Describes options involved with operations to persist model entities.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/PersistenceDriver.Internals.html b/docs/java/gust/backend/model/class-use/PersistenceDriver.Internals.html new file mode 100644 index 000000000..0903c02b5 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/PersistenceDriver.Internals.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.model.PersistenceDriver.Internals + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.PersistenceDriver.Internals

+
+
No usage of gust.backend.model.PersistenceDriver.Internals
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/PersistenceDriver.html b/docs/java/gust/backend/model/class-use/PersistenceDriver.html new file mode 100644 index 000000000..3142f06ca --- /dev/null +++ b/docs/java/gust/backend/model/class-use/PersistenceDriver.html @@ -0,0 +1,355 @@ + + + + + +Uses of Interface gust.backend.model.PersistenceDriver + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.PersistenceDriver

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/PersistenceException.html b/docs/java/gust/backend/model/class-use/PersistenceException.html new file mode 100644 index 000000000..af29dbe29 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/PersistenceException.html @@ -0,0 +1,281 @@ + + + + + +Uses of Class gust.backend.model.PersistenceException + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.PersistenceException

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use PersistenceException 
    PackageDescription
    gust.backend.model +
    Provides definitions and structure for logic dealing with business data.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of PersistenceException in gust.backend.model

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Subclasses of PersistenceException in gust.backend.model 
      Modifier and TypeClassDescription
      class InvalidModelType +
      Specifies an error, wherein a user has requested a data adapter or some other database object, for a model which is + not usable with data storage systems (via annotations).
      +
      class MissingAnnotatedField +
      Specifies that a model was missing a required annotated-field for a given operation.
      +
      class ModelDeflateException +
      Describes an error that occurred while serializing a model.
      +
      class ModelInflateException +
      Describes an error that occurred while de-serializing a model.
      +
      class ModelWriteConflict +
      Thrown when a write operation fails, because of some conflict situation.
      +
      class ModelWriteFailure +
      Thrown when a model fails to write.
      +
      class PersistenceOperationFailed +
      Describes a generic operational failure that occurred within the persistence engine.
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.model that throw PersistenceException 
      Modifier and TypeMethodDescription
      default ModelPersistenceDriver.fetch​(Key key) +
      Synchronously retrieve a data model instance from underlying storage, addressed by its unique ID.
      +
      default ModelPersistenceDriver.fetch​(Key key, + FetchOptions options) +
      Synchronously retrieve a data model instance from underlying storage, addressed by its unique ID.
      +
      default java.util.Optional<Model>PersistenceDriver.fetchSafe​(Key key) +
      Safely (and synchronously) retrieve a data model instance from storage, returning Optional.empty() if it + cannot be located, rather than null.
      +
      default java.util.Optional<Model>PersistenceDriver.fetchSafe​(Key key, + FetchOptions options) +
      Safely (and synchronously) retrieve a data model instance from storage, returning Optional.empty() if it + cannot be located, rather than null.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/PersistenceFailure.html b/docs/java/gust/backend/model/class-use/PersistenceFailure.html new file mode 100644 index 000000000..45d1bddf9 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/PersistenceFailure.html @@ -0,0 +1,209 @@ + + + + + +Uses of Class gust.backend.model.PersistenceFailure + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.PersistenceFailure

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/PersistenceManager.html b/docs/java/gust/backend/model/class-use/PersistenceManager.html new file mode 100644 index 000000000..bbbd89ac7 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/PersistenceManager.html @@ -0,0 +1,289 @@ + + + + + +Uses of Interface gust.backend.model.PersistenceManager + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.PersistenceManager

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/PersistenceOperationFailed.html b/docs/java/gust/backend/model/class-use/PersistenceOperationFailed.html new file mode 100644 index 000000000..d272b03b0 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/PersistenceOperationFailed.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.model.PersistenceOperationFailed + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.PersistenceOperationFailed

+
+
No usage of gust.backend.model.PersistenceOperationFailed
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/ProtoModelCodec.html b/docs/java/gust/backend/model/class-use/ProtoModelCodec.html new file mode 100644 index 000000000..f244570b3 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/ProtoModelCodec.html @@ -0,0 +1,213 @@ + + + + + +Uses of Class gust.backend.model.ProtoModelCodec + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.ProtoModelCodec

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use ProtoModelCodec 
    PackageDescription
    gust.backend.model +
    Provides definitions and structure for logic dealing with business data.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of ProtoModelCodec in gust.backend.model

      + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.model that return ProtoModelCodec 
      Modifier and TypeMethodDescription
      static <M extends com.google.protobuf.Message>
      ProtoModelCodec<M>
      ProtoModelCodec.forModel​(M instance) +
      Acquire a Protobuf model codec for the provided model instance.
      +
      static <M extends com.google.protobuf.Message>
      ProtoModelCodec<M>
      ProtoModelCodec.forModel​(M instance, + EncodingMode mode) +
      Acquire a Protobuf model codec for the provided model instance.
      +
      static <M extends com.google.protobuf.Message>
      ProtoModelCodec<M>
      ProtoModelCodec.forModel​(M instance, + EncodingMode mode, + java.util.Optional<com.google.protobuf.TypeRegistry> registry) +
      Acquire a Protobuf model codec for the provided model instance.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/SerializedModel.html b/docs/java/gust/backend/model/class-use/SerializedModel.html new file mode 100644 index 000000000..27b3b310c --- /dev/null +++ b/docs/java/gust/backend/model/class-use/SerializedModel.html @@ -0,0 +1,245 @@ + + + + + +Uses of Class gust.backend.model.SerializedModel + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.SerializedModel

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use SerializedModel 
    PackageDescription
    gust.backend.model +
    Provides definitions and structure for logic dealing with business data.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of SerializedModel in gust.backend.model

      + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.model that return SerializedModel 
      Modifier and TypeMethodDescription
      static SerializedModelSerializedModel.factory() +
      Create an empty serialized model, for use as a container.
      +
      static SerializedModelSerializedModel.factory​(java.util.SortedMap<java.lang.String,​com.google.firestore.v1.Value> data) +
      Create a serialized model, pre-filled with the provided backing data.
      +
      static SerializedModelSerializedModel.wrap​(java.util.SortedMap<java.lang.String,​com.google.firestore.v1.Value> data, + com.google.protobuf.Message proto) +
      Create a serialized model, pre-filled with the provided backing data.
      +
      + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.model with parameters of type SerializedModel 
      Modifier and TypeMethodDescription
      voidWriteProxy.create​(Reference reference, + SerializedModel message) +
      Create a collapsed `message` in underlying storage, referenced by `reference`.
      +
      voidWriteProxy.put​(Reference reference, + SerializedModel message) +
      Save a collapsed `message` in underlying storage, referenced by `reference`.
      +
      voidWriteProxy.update​(Reference reference, + SerializedModel message) +
      Update a collapsed `message` in underlying storage, referenced by `reference`.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/Transaction.html b/docs/java/gust/backend/model/class-use/Transaction.html new file mode 100644 index 000000000..05ab6534b --- /dev/null +++ b/docs/java/gust/backend/model/class-use/Transaction.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.model.Transaction + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.Transaction

+
+
No usage of gust.backend.model.Transaction
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/UpdateOptions.html b/docs/java/gust/backend/model/class-use/UpdateOptions.html new file mode 100644 index 000000000..8dfb05681 --- /dev/null +++ b/docs/java/gust/backend/model/class-use/UpdateOptions.html @@ -0,0 +1,224 @@ + + + + + +Uses of Interface gust.backend.model.UpdateOptions + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.UpdateOptions

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/WriteOptions.WriteDisposition.html b/docs/java/gust/backend/model/class-use/WriteOptions.WriteDisposition.html new file mode 100644 index 000000000..0cd70ae4e --- /dev/null +++ b/docs/java/gust/backend/model/class-use/WriteOptions.WriteDisposition.html @@ -0,0 +1,241 @@ + + + + + +Uses of Class gust.backend.model.WriteOptions.WriteDisposition + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.model.WriteOptions.WriteDisposition

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/WriteOptions.html b/docs/java/gust/backend/model/class-use/WriteOptions.html new file mode 100644 index 000000000..c1739a80e --- /dev/null +++ b/docs/java/gust/backend/model/class-use/WriteOptions.html @@ -0,0 +1,353 @@ + + + + + +Uses of Interface gust.backend.model.WriteOptions + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.WriteOptions

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/class-use/WriteProxy.html b/docs/java/gust/backend/model/class-use/WriteProxy.html new file mode 100644 index 000000000..cbb1c0a2c --- /dev/null +++ b/docs/java/gust/backend/model/class-use/WriteProxy.html @@ -0,0 +1,207 @@ + + + + + +Uses of Interface gust.backend.model.WriteProxy + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.model.WriteProxy

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use WriteProxy 
    PackageDescription
    gust.backend.model +
    Provides definitions and structure for logic dealing with business data.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of WriteProxy in gust.backend.model

      + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.model with parameters of type WriteProxy 
      Modifier and TypeMethodDescription
      voidCollapsedMessage.Operation.execute​(java.lang.String prefix, + WriteProxy<java.lang.Object> proxy, + java.util.Optional<gust.backend.model.CollapsedMessage.Parent> scope) +
      Execute the underlying operation against the provided write proxy.
      +
      voidCollapsedMessage.persist​(java.lang.String prefix, + WriteProxy<?> proxy) +
      Execute the collapsed message against the provided WriteProxy, which creates it in underlying storage (once + any wrapping transaction finishes).
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/package-summary.html b/docs/java/gust/backend/model/package-summary.html new file mode 100644 index 000000000..0e505e067 --- /dev/null +++ b/docs/java/gust/backend/model/package-summary.html @@ -0,0 +1,483 @@ + + + + + +gust.backend.model + + + + + + + + + + + + + + +
+ +
+
+
+

Package gust.backend.model

+
+
+
+ + +
Provides definitions and structure for logic dealing with business data.
+
+
    +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Interface Summary 
    InterfaceDescription
    CacheDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message> +
    Describes the surface of a cache driver, which is a partner object to a PersistenceDriver specifically + tailored to deal with caching engines.
    +
    CacheOptions +
    Specifies operational options related to caching.
    +
    CollapsedMessage.Operation +
    Describes a generic collapsed data store operation.
    +
    CollapsedMessageSerializer<Model extends com.google.protobuf.Message> 
    DatabaseAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadRecord,​WriteRecord> +
    Extends the standard ModelAdapter interface with rich persistence features, including querying, indexing, and + other stuff one would expect when interacting with a full database.
    +
    DatabaseDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadRecord,​WriteRecord> 
    DatabaseManager<Adapter extends DatabaseAdapter,​Driver extends DatabaseDriver> 
    DeleteOptions +
    Describes options specifically involved with deleting existing model entities.
    +
    FetchOptions +
    Specifies options which may be applied, generically, to model instance fetch operations implemented through the + PersistenceDriver interface.
    +
    ModelAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadIntermediate,​WriteIntermediate> +
    Specifies an adapter for data models.
    +
    ModelCodec<Model extends com.google.protobuf.Message,​WriteIntermediate,​ReadIntermediate> +
    Specifies the requisite interface for a data codec implementation.
    +
    ModelDeserializer<Input,​Model extends com.google.protobuf.Message> +
    Describes the interface for a de-serializer, which is responsible for transitioning (adapting) objects from a given + type (or tree of types) to Message instances, corresponding with the data record type managed by this object.
    +
    ModelSerializer<Model extends com.google.protobuf.Message,​Output> +
    Describes the surface interface of an object responsible for serializing business data objects (hereinafter, + "models").
    +
    OperationOptions +
    Operational options that can be applied to individual calls into the ModelAdapter framework.
    +
    PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadIntermediate,​WriteIntermediate> +
    Describes the surface of a generic persistence driver, which is capable of accepting arbitrary structured and typed + business data (also called "data models"), and managing them with regard to persistent storage, which includes + storing them when asked, and recalling them when subsequently asked to do so.
    +
    PersistenceManager<Driver extends PersistenceDriver> 
    UpdateOptions +
    Describes options specifically involved with updating existing model entities.
    +
    WriteOptions +
    Describes options involved with operations to persist model entities.
    +
    WriteProxy<Reference> +
    Provides an interface for virtualized object writes during transactions or hierarchical serialization.
    +
    +
  • +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Class Summary 
    ClassDescription
    CollapsedMessage +
    Describes a generic, collapsed message (i.e.
    +
    CollapsedMessageCodec<Model extends com.google.protobuf.Message,​ReadIntermediate> 
    EncodedModel +
    Container class for an encoded Protocol Buffer model.
    +
    ModelMetadata +
    Utility helper class, which is responsible for resolving metadata (based on the core framework annotations) from + arbitrary model definitions.
    +
    ModelMetadata.FieldContainer<V> +
    Utility class that holds a ModelMetadata.FieldPointer and matching field value.
    +
    ModelMetadata.FieldPointer +
    Utility class that points to a specific field, in a specific context.
    +
    PersistenceDriver.Internals +
    Default model adapter internals.
    +
    ProtoModelCodec<Model extends com.google.protobuf.Message> +
    Defines a ModelCodec which uses Protobuf serialization to export and import protos to and from from raw + byte-strings.
    +
    SerializedModel +
    Describes a model which has been serialized into a backing map of keys and properties.
    +
    Transaction 
    +
  • +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Enum Summary 
    EnumDescription
    CacheOptions.EvictionMode +
    Describes operating modes with regard to cache eviction.
    +
    EncodingMode +
    Wire format mode to apply when serializing or de-serializing.
    +
    FetchOptions.MaskMode +
    Enumerates ways the FieldMask may be applied.
    +
    ModelSerializer.EnumSerializeMode +
    Enumerates modes for encoding enums.
    +
    ModelSerializer.InstantSerializeMode +
    Enumerates modes for encoding timestamps.
    +
    ModelSerializer.WriteDisposition +
    Describes available write dispositions, each of which presents a strategy that governs how an individual + write operation is handled with regard to underlying storage.
    +
    PersistenceFailure +
    Enumerates common kinds of persistence failures.
    +
    WriteOptions.WriteDisposition +
    Enumerates write attitudes with regard to existing record collisions.
    +
    +
  • +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Exception Summary 
    ExceptionDescription
    InvalidModelType +
    Specifies an error, wherein a user has requested a data adapter or some other database object, for a model which is + not usable with data storage systems (via annotations).
    +
    MissingAnnotatedField +
    Specifies that a model was missing a required annotated-field for a given operation.
    +
    ModelDeflateException +
    Describes an error that occurred while serializing a model.
    +
    ModelDeserializer.DeserializationError +
    Describes errors that occur during model deserialization or inflation activities.
    +
    ModelInflateException +
    Describes an error that occurred while de-serializing a model.
    +
    ModelSerializer.SerializationError +
    Describes errors that occur during model serialization activities.
    +
    ModelWriteConflict +
    Thrown when a write operation fails, because of some conflict situation.
    +
    ModelWriteFailure +
    Thrown when a model fails to write.
    +
    PersistenceException +
    Defines a class of exceptions which can be encountered when interacting with persistence tools, including internal + (built-in) data adapters.
    +
    PersistenceOperationFailed +
    Describes a generic operational failure that occurred within the persistence engine.
    +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/package-tree.html b/docs/java/gust/backend/model/package-tree.html new file mode 100644 index 000000000..2c3e3de56 --- /dev/null +++ b/docs/java/gust/backend/model/package-tree.html @@ -0,0 +1,268 @@ + + + + + +gust.backend.model Class Hierarchy + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package gust.backend.model

+Package Hierarchies: + +
+
+
+

Class Hierarchy

+ +
+
+

Interface Hierarchy

+ +
+
+

Enum Hierarchy

+ +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/model/package-use.html b/docs/java/gust/backend/model/package-use.html new file mode 100644 index 000000000..585294311 --- /dev/null +++ b/docs/java/gust/backend/model/package-use.html @@ -0,0 +1,696 @@ + + + + + +Uses of Package gust.backend.model + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Package
gust.backend.model

+
+
+
    +
  • + + + + + + + + + + + + + + + + + + + + + + + + +
    Packages that use gust.backend.model 
    PackageDescription
    gust.backend.driver.firestore +
    Provides a DatabaseDriver implementation, using Google Cloud Firestore.
    +
    gust.backend.driver.inmemory +
    Provides a reference implementation of a persistence driver, backed by a concurrent map.
    +
    gust.backend.driver.spanner +
    Provides a DatabaseDriver implementation for integration with Google Cloud Spanner.
    +
    gust.backend.model +
    Provides definitions and structure for logic dealing with business data.
    +
    +
  • +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Classes in gust.backend.model used by gust.backend.driver.firestore 
    ClassDescription
    CacheDriver +
    Describes the surface of a cache driver, which is a partner object to a PersistenceDriver specifically + tailored to deal with caching engines.
    +
    CollapsedMessage +
    Describes a generic, collapsed message (i.e.
    +
    DatabaseAdapter +
    Extends the standard ModelAdapter interface with rich persistence features, including querying, indexing, and + other stuff one would expect when interacting with a full database.
    +
    DatabaseDriver 
    DatabaseManager 
    DeleteOptions +
    Describes options specifically involved with deleting existing model entities.
    +
    FetchOptions +
    Specifies options which may be applied, generically, to model instance fetch operations implemented through the + PersistenceDriver interface.
    +
    ModelAdapter +
    Specifies an adapter for data models.
    +
    ModelCodec +
    Specifies the requisite interface for a data codec implementation.
    +
    PersistenceDriver +
    Describes the surface of a generic persistence driver, which is capable of accepting arbitrary structured and typed + business data (also called "data models"), and managing them with regard to persistent storage, which includes + storing them when asked, and recalling them when subsequently asked to do so.
    +
    PersistenceManager 
    WriteOptions +
    Describes options involved with operations to persist model entities.
    +
    +
  • +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Classes in gust.backend.model used by gust.backend.driver.inmemory 
    ClassDescription
    CacheDriver +
    Describes the surface of a cache driver, which is a partner object to a PersistenceDriver specifically + tailored to deal with caching engines.
    +
    DeleteOptions +
    Describes options specifically involved with deleting existing model entities.
    +
    EncodedModel +
    Container class for an encoded Protocol Buffer model.
    +
    FetchOptions +
    Specifies options which may be applied, generically, to model instance fetch operations implemented through the + PersistenceDriver interface.
    +
    InvalidModelType +
    Specifies an error, wherein a user has requested a data adapter or some other database object, for a model which is + not usable with data storage systems (via annotations).
    +
    ModelAdapter +
    Specifies an adapter for data models.
    +
    ModelCodec +
    Specifies the requisite interface for a data codec implementation.
    +
    PersistenceDriver +
    Describes the surface of a generic persistence driver, which is capable of accepting arbitrary structured and typed + business data (also called "data models"), and managing them with regard to persistent storage, which includes + storing them when asked, and recalling them when subsequently asked to do so.
    +
    PersistenceManager 
    WriteOptions +
    Describes options involved with operations to persist model entities.
    +
    +
  • +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Classes in gust.backend.model used by gust.backend.driver.spanner 
    ClassDescription
    CacheDriver +
    Describes the surface of a cache driver, which is a partner object to a PersistenceDriver specifically + tailored to deal with caching engines.
    +
    DatabaseAdapter +
    Extends the standard ModelAdapter interface with rich persistence features, including querying, indexing, and + other stuff one would expect when interacting with a full database.
    +
    DatabaseDriver 
    DatabaseManager 
    DeleteOptions +
    Describes options specifically involved with deleting existing model entities.
    +
    FetchOptions +
    Specifies options which may be applied, generically, to model instance fetch operations implemented through the + PersistenceDriver interface.
    +
    ModelAdapter +
    Specifies an adapter for data models.
    +
    ModelCodec +
    Specifies the requisite interface for a data codec implementation.
    +
    ModelDeflateException +
    Describes an error that occurred while serializing a model.
    +
    ModelDeserializer +
    Describes the interface for a de-serializer, which is responsible for transitioning (adapting) objects from a given + type (or tree of types) to Message instances, corresponding with the data record type managed by this object.
    +
    ModelInflateException +
    Describes an error that occurred while de-serializing a model.
    +
    ModelMetadata.FieldPointer +
    Utility class that points to a specific field, in a specific context.
    +
    ModelSerializer +
    Describes the surface interface of an object responsible for serializing business data objects (hereinafter, + "models").
    +
    PersistenceDriver +
    Describes the surface of a generic persistence driver, which is capable of accepting arbitrary structured and typed + business data (also called "data models"), and managing them with regard to persistent storage, which includes + storing them when asked, and recalling them when subsequently asked to do so.
    +
    PersistenceManager 
    WriteOptions +
    Describes options involved with operations to persist model entities.
    +
    +
  • +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Classes in gust.backend.model used by gust.backend.model 
    ClassDescription
    CacheDriver +
    Describes the surface of a cache driver, which is a partner object to a PersistenceDriver specifically + tailored to deal with caching engines.
    +
    CacheOptions +
    Specifies operational options related to caching.
    +
    CacheOptions.EvictionMode +
    Describes operating modes with regard to cache eviction.
    +
    CollapsedMessage +
    Describes a generic, collapsed message (i.e.
    +
    CollapsedMessage.Operation +
    Describes a generic collapsed data store operation.
    +
    CollapsedMessageCodec 
    DatabaseAdapter +
    Extends the standard ModelAdapter interface with rich persistence features, including querying, indexing, and + other stuff one would expect when interacting with a full database.
    +
    DatabaseDriver 
    DeleteOptions +
    Describes options specifically involved with deleting existing model entities.
    +
    EncodedModel +
    Container class for an encoded Protocol Buffer model.
    +
    EncodingMode +
    Wire format mode to apply when serializing or de-serializing.
    +
    FetchOptions +
    Specifies options which may be applied, generically, to model instance fetch operations implemented through the + PersistenceDriver interface.
    +
    FetchOptions.MaskMode +
    Enumerates ways the FieldMask may be applied.
    +
    InvalidModelType +
    Specifies an error, wherein a user has requested a data adapter or some other database object, for a model which is + not usable with data storage systems (via annotations).
    +
    ModelAdapter +
    Specifies an adapter for data models.
    +
    ModelCodec +
    Specifies the requisite interface for a data codec implementation.
    +
    ModelDeflateException +
    Describes an error that occurred while serializing a model.
    +
    ModelDeserializer +
    Describes the interface for a de-serializer, which is responsible for transitioning (adapting) objects from a given + type (or tree of types) to Message instances, corresponding with the data record type managed by this object.
    +
    ModelInflateException +
    Describes an error that occurred while de-serializing a model.
    +
    ModelMetadata.FieldContainer +
    Utility class that holds a ModelMetadata.FieldPointer and matching field value.
    +
    ModelMetadata.FieldPointer +
    Utility class that points to a specific field, in a specific context.
    +
    ModelSerializer +
    Describes the surface interface of an object responsible for serializing business data objects (hereinafter, + "models").
    +
    ModelSerializer.EnumSerializeMode +
    Enumerates modes for encoding enums.
    +
    ModelSerializer.InstantSerializeMode +
    Enumerates modes for encoding timestamps.
    +
    ModelSerializer.WriteDisposition +
    Describes available write dispositions, each of which presents a strategy that governs how an individual + write operation is handled with regard to underlying storage.
    +
    ModelWriteFailure +
    Thrown when a model fails to write.
    +
    OperationOptions +
    Operational options that can be applied to individual calls into the ModelAdapter framework.
    +
    PersistenceDriver +
    Describes the surface of a generic persistence driver, which is capable of accepting arbitrary structured and typed + business data (also called "data models"), and managing them with regard to persistent storage, which includes + storing them when asked, and recalling them when subsequently asked to do so.
    +
    PersistenceException +
    Defines a class of exceptions which can be encountered when interacting with persistence tools, including internal + (built-in) data adapters.
    +
    PersistenceFailure +
    Enumerates common kinds of persistence failures.
    +
    PersistenceManager 
    ProtoModelCodec +
    Defines a ModelCodec which uses Protobuf serialization to export and import protos to and from from raw + byte-strings.
    +
    SerializedModel +
    Describes a model which has been serialized into a backing map of keys and properties.
    +
    UpdateOptions +
    Describes options specifically involved with updating existing model entities.
    +
    WriteOptions +
    Describes options involved with operations to persist model entities.
    +
    WriteOptions.WriteDisposition +
    Enumerates write attitudes with regard to existing record collisions.
    +
    WriteProxy +
    Provides an interface for virtualized object writes during transactions or hierarchical serialization.
    +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/package-summary.html b/docs/java/gust/backend/package-summary.html new file mode 100644 index 000000000..3d033c336 --- /dev/null +++ b/docs/java/gust/backend/package-summary.html @@ -0,0 +1,317 @@ + + + + + +gust.backend + + + + + + + + + + + + + + +
+ +
+
+
+

Package gust.backend

+
+
+
+ + +
Defines backend logic for Gust-based applications.
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/package-tree.html b/docs/java/gust/backend/package-tree.html new file mode 100644 index 000000000..78e7b7d2d --- /dev/null +++ b/docs/java/gust/backend/package-tree.html @@ -0,0 +1,200 @@ + + + + + +gust.backend Class Hierarchy + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package gust.backend

+Package Hierarchies: + +
+
+
+

Class Hierarchy

+ +
+
+

Interface Hierarchy

+ +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/package-use.html b/docs/java/gust/backend/package-use.html new file mode 100644 index 000000000..729485b3e --- /dev/null +++ b/docs/java/gust/backend/package-use.html @@ -0,0 +1,286 @@ + + + + + +Uses of Package gust.backend + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Package
gust.backend

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/AssetManager.ManagedAsset.html b/docs/java/gust/backend/runtime/AssetManager.ManagedAsset.html new file mode 100644 index 000000000..c0f2284a6 --- /dev/null +++ b/docs/java/gust/backend/runtime/AssetManager.ManagedAsset.html @@ -0,0 +1,402 @@ + + + + + +AssetManager.ManagedAsset + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class AssetManager.ManagedAsset<M extends com.google.protobuf.Message>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.runtime.AssetManager.ManagedAsset<M>
    • +
    +
  • +
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        equals

        +
        public boolean equals​(java.lang.Object o)
        +
        +
        Overrides:
        +
        equals in class java.lang.Object
        +
        +
      • +
      + + + +
        +
      • +

        hashCode

        +
        public int hashCode()
        +
        +
        Overrides:
        +
        hashCode in class java.lang.Object
        +
        +
      • +
      + + + +
        +
      • +

        compareTo

        +
        public int compareTo​(@Nonnull
        +                     AssetManager.ManagedAsset other)
        +
        +
        Specified by:
        +
        compareTo in interface java.lang.Comparable<M extends com.google.protobuf.Message>
        +
        +
      • +
      + + + +
        +
      • +

        getName

        +
        @Nonnull
        +public java.lang.String getName()
        +
        +
        Returns:
        +
        This module's assigned name.
        +
        +
      • +
      + + + + + + + +
        +
      • +

        getAssets

        +
        @Nonnull
        +public java.util.Collection<MgetAssets()
        +
        +
        Returns:
        +
        Collection of typed asset records constituting this bundle.
        +
        +
      • +
      + + + + +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/AssetManager.ManagedAssetContent.html b/docs/java/gust/backend/runtime/AssetManager.ManagedAssetContent.html new file mode 100644 index 000000000..5150428e0 --- /dev/null +++ b/docs/java/gust/backend/runtime/AssetManager.ManagedAssetContent.html @@ -0,0 +1,541 @@ + + + + + +AssetManager.ManagedAssetContent + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class AssetManager.ManagedAssetContent

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.runtime.AssetManager.ManagedAssetContent
    • +
    +
  • +
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        equals

        +
        public boolean equals​(java.lang.Object other)
        +
        +
        Overrides:
        +
        equals in class java.lang.Object
        +
        +
      • +
      + + + +
        +
      • +

        hashCode

        +
        public int hashCode()
        +
        +
        Overrides:
        +
        hashCode in class java.lang.Object
        +
        +
      • +
      + + + + + + + +
        +
      • +

        getToken

        +
        @Nonnull
        +public java.lang.String getToken()
        +
        +
        Returns:
        +
        Opaque token identifying this asset content.
        +
        +
      • +
      + + + +
        +
      • +

        getModule

        +
        @Nonnull
        +public java.lang.String getModule()
        +
        +
        Returns:
        +
        Module name for this content chunk.
        +
        +
      • +
      + + + +
        +
      • +

        getETag

        +
        @Nonnull
        +public java.lang.String getETag()
        +
        +
        Returns:
        +
        Pre-calculated ETag value for this asset.
        +
        +
      • +
      + + + +
        +
      • +

        getFilename

        +
        @Nonnull
        +public java.lang.String getFilename()
        +
        +
        Returns:
        +
        Original filename for the asset.
        +
        +
      • +
      + + + +
        +
      • +

        getLastModified

        +
        @Nonnull
        +public com.google.protobuf.Timestamp getLastModified()
        +
        +
        Returns:
        +
        Last-modified-timestamp for this asset.
        +
        +
      • +
      + + + +
        +
      • +

        getSize

        +
        @Nonnull
        +public java.lang.Long getSize()
        +
        +
        Returns:
        +
        Un-compressed size of the asset.
        +
        +
      • +
      + + + +
        +
      • +

        getOptimalCompression

        +
        @Nonnull
        +public tools.elide.core.data.CompressionMode getOptimalCompression()
        +
        +
        Returns:
        +
        Optimal compression mode.
        +
        +
      • +
      + + + +
        +
      • +

        getCompressedSize

        +
        @Nonnull
        +public java.lang.Long getCompressedSize()
        +
        +
        Returns:
        +
        Compressed size of the asset (optimal).
        +
        +
      • +
      + + + +
        +
      • +

        getVariantCount

        +
        @Nonnull
        +public java.lang.Integer getVariantCount()
        +
        +
        Returns:
        +
        Count of variants that exist for this asset.
        +
        +
      • +
      + + + +
        +
      • +

        getCompressionOptions

        +
        @Nonnull
        +public java.util.EnumSet<tools.elide.core.data.CompressionMode> getCompressionOptions()
        +
        +
        Returns:
        +
        Set of supported compression modes for this asset.
        +
        +
      • +
      + + + +
        +
      • +

        getContent

        +
        @Nonnull
        +public tools.elide.assets.AssetBundle.AssetContent getContent()
        +
        Retrieve the content backing this info record.
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/AssetManager.ModuleType.html b/docs/java/gust/backend/runtime/AssetManager.ModuleType.html new file mode 100644 index 000000000..5176aa658 --- /dev/null +++ b/docs/java/gust/backend/runtime/AssetManager.ModuleType.html @@ -0,0 +1,392 @@ + + + + + +AssetManager.ModuleType + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Enum AssetManager.ModuleType

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      CSS +
      The bundle contains style declarations.
      +
      JS +
      The bundle contains JavaScript code.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static AssetManager.ModuleTypevalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static AssetManager.ModuleType[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static AssetManager.ModuleType[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (AssetManager.ModuleType c : AssetManager.ModuleType.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static AssetManager.ModuleType valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/runtime/AssetManager.html b/docs/java/gust/backend/runtime/AssetManager.html new file mode 100644 index 000000000..dfd727f40 --- /dev/null +++ b/docs/java/gust/backend/runtime/AssetManager.html @@ -0,0 +1,421 @@ + + + + + +AssetManager + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class AssetManager

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.runtime.AssetManager
    • +
    +
  • +
+
+
    +
  • +
    +
    @Context
    +@ThreadSafe
    +@Infrastructure
    +public final class AssetManager
    +extends java.lang.Object
    +
    Manager class, which mediates interactions with the binary asset bundle. When managed assets are active, the content + and manifest are located in a binary proto file at the root of the JAR. + +

    This object acts as a singleton, and is responsible for the actual mechanics of initially reading the asset bundle + and interpreting its contents. Once we have established indexes and completed other prep work, the manager moves into + a read-only mode, where its primary job shifts to satisfying dynamic asset requests - either for referential metadata + which is used to embed an asset in the DOM, or content data, which is used to serve the asset itself.

    + +

    Multiple "variants" of an asset are stored in the bundle (if so configured). This includes one variant reflecting + the regular, un-modified content for the asset, and an additional variant for each caching strategy supported by the + framework (GZIP and BROTLI at the time of this writing).

    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Nested Class Summary

      + + + + + + + + + + + + + + + + + + + + + + +
      Nested Classes 
      Modifier and TypeClassDescription
      static class AssetManager.ManagedAsset<M extends com.google.protobuf.Message> +
      Public API surface for interacting with raw asset metadata.
      +
      static class AssetManager.ManagedAssetContent +
      Public API surface for interacting with raw asset content.
      +
      static class AssetManager.ModuleType +
      Enumerates types of asset modules.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static AssetManageracquire() +
      Acquire a new instance of the asset manager.
      +
      java.util.Optional<AssetManager.ManagedAssetContent>assetDataByToken​(java.lang.String token) +
      Resolve raw asset content by its opaque token.
      +
      <M extends com.google.protobuf.Message>
      java.util.Optional<AssetManager.ManagedAsset<M>>
      assetMetadataByModule​(java.lang.String module) +
      Resolve asset metadata by its module name.
      +
      static voidload() +
      Attempt to force-load the asset manifest, in a static context, preparing our indexed read-only data regarding the + data it contains.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        load

        +
        public static void load()
        +                 throws java.io.IOException,
        +                        com.google.protobuf.InvalidProtocolBufferException
        +
        Attempt to force-load the asset manifest, in a static context, preparing our indexed read-only data regarding the + data it contains. If we can't load the file, surface an exception so the invoking code can decide what to do.
        +
        +
        Throws:
        +
        java.io.IOException - If some otherwise unmentioned I/O error occurs.
        +
        java.io.FileNotFoundException - If the asset manifest could not be found.
        +
        com.google.protobuf.InvalidProtocolBufferException - If the enclosed Protocol Buffer data isn't recognizable.
        +
        +
      • +
      + + + +
        +
      • +

        acquire

        +
        public static AssetManager acquire()
        +                            throws java.io.IOException
        +
        Acquire a new instance of the asset manager. The instance provided by this method is not guaranteed to be fresh for + every invocation (it may be a shared object), but all operations on the asset manager are threadsafe nonetheless.
        +
        +
        Returns:
        +
        Asset manager instance.
        +
        Throws:
        +
        java.io.IOException
        +
        +
      • +
      + + + +
        +
      • +

        assetDataByToken

        +
        @Nonnull
        +public java.util.Optional<AssetManager.ManagedAssetContentassetDataByToken​(@Nonnull
        +                                                                             java.lang.String token)
        +
        Resolve raw asset content by its opaque token. This will hand back an object containing representations of the + asset for each enabled compression mode. + +

        The object also knows how to resolve the most-optimal representation, based on the accepted compression modes + indicated by the invoking client.

        +
        +
        Parameters:
        +
        token - Token uniquely identifying this asset (generated from the module name and content fingerprint).
        +
        Returns:
        +
        Optional, either Optional.empty() if the asset could not be found, or wrapping the result.
        +
        +
      • +
      + + + +
        +
      • +

        assetMetadataByModule

        +
        @Nonnull
        +public <M extends com.google.protobuf.Message> java.util.Optional<AssetManager.ManagedAsset<M>> assetMetadataByModule​(@Nonnull
        +                                                                                                                      java.lang.String module)
        +
        Resolve asset metadata by its module name. This will hand back an object specifying the type/name of the module, + and links to each of the content blocks that constitute it. + +

        This code path is generally used for resolving metadata for an asset so it can be referenced. The serving + for URL generated for the asset refers to a specific content block with an opaque token, rather than the module + name, which refers to a bundle of assets or content.

        +
        +
        Parameters:
        +
        module - Module name for which we should resolve asset metadata.
        +
        Returns:
        +
        Optional, either Optional.empty() if the asset group could not be found, or wrapping the result.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/Logging.html b/docs/java/gust/backend/runtime/Logging.html new file mode 100644 index 000000000..18a594576 --- /dev/null +++ b/docs/java/gust/backend/runtime/Logging.html @@ -0,0 +1,278 @@ + + + + + +Logging + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class Logging

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.runtime.Logging
    • +
    +
  • +
+
+
    +
  • +
    +
    public final class Logging
    +extends java.lang.Object
    +
    Sugar bridge to SLF4J.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static org.slf4j.Loggerlogger​(java.lang.Class cls) +
      Retrieve a logger for a particular Java class, named after the fully-qualified path to the class.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        logger

        +
        public static org.slf4j.Logger logger​(java.lang.Class cls)
        +
        Retrieve a logger for a particular Java class, named after the fully-qualified path to the class.
        +
        +
        Parameters:
        +
        cls - Java class to get a logger for.
        +
        Returns:
        +
        Logger for the specified class.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription.html b/docs/java/gust/backend/runtime/ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription.html new file mode 100644 index 000000000..fdf10377c --- /dev/null +++ b/docs/java/gust/backend/runtime/ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription.html @@ -0,0 +1,320 @@ + + + + + +ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    org.reactivestreams.Subscription
    +
    +
    +
    Enclosing class:
    +
    ReactiveFuture.CompletableFuturePublisher<T>
    +
    +
    +
    @Immutable
    +@ThreadSafe
    +public final class ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription
    +extends java.lang.Object
    +implements org.reactivestreams.Subscription
    +
    Models a Reactive Java Subscription, which is responsible for propagating events from a + Concurrent Java CompletableFuture to a Subscriber. + +

    This object is generally used internally by the ReactiveFuture.CompletableFuturePublisher, once a Subscriber + attaches itself to a Publisher that is actually a wrapped CompletableFuture. Error (exception) + events and value events are both propagated. Subscribers based on this wrapping will only ever receive a maximum + of one value or one error.

    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      voidcancel() +
      Request the publisher to stop sending data and clean up resources.
      +
      voidrequest​(long n) +
      Request the specified number of items from the underlying Subscription.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        request

        +
        public void request​(long n)
        +
        Request the specified number of items from the underlying Subscription. This must always be +
        1
        .
        +
        +
        Specified by:
        +
        request in interface org.reactivestreams.Subscription
        +
        Parameters:
        +
        n - Number of elements to request to the upstream (must always be
        1
        ).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If any value other than
        1
        is passed in.
        +
        +
      • +
      + + + +
        +
      • +

        cancel

        +
        public void cancel()
        +
        Request the publisher to stop sending data and clean up resources.
        +
        +
        Specified by:
        +
        cancel in interface org.reactivestreams.Subscription
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/ReactiveFuture.CompletableFuturePublisher.html b/docs/java/gust/backend/runtime/ReactiveFuture.CompletableFuturePublisher.html new file mode 100644 index 000000000..0809b03e0 --- /dev/null +++ b/docs/java/gust/backend/runtime/ReactiveFuture.CompletableFuturePublisher.html @@ -0,0 +1,1218 @@ + + + + + +ReactiveFuture.CompletableFuturePublisher + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class ReactiveFuture.CompletableFuturePublisher<T>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher<T>
    • +
    +
  • +
+
+
    +
  • +
    +
    Type Parameters:
    +
    T - Emit type for this adapter. Matches the future it wraps.
    +
    +
    +
    All Implemented Interfaces:
    +
    com.google.common.util.concurrent.ListenableFuture<T>, java.util.concurrent.CompletionStage<T>, java.util.concurrent.Future<T>, org.reactivestreams.Publisher<T>
    +
    +
    +
    Enclosing class:
    +
    ReactiveFuture<R>
    +
    +
    +
    public static final class ReactiveFuture.CompletableFuturePublisher<T>
    +extends java.lang.Object
    +implements org.reactivestreams.Publisher<T>, com.google.common.util.concurrent.ListenableFuture<T>, java.util.concurrent.CompletionStage<T>
    +
    Structure that adapts Java's CompletableFuture to a Reactive Java Publisher, which publishes one + item - either the result of the computation, or an error. + +

    This object is used in the specific circumstance that a CompletableFuture is wrapped by a + ReactiveFuture, and then used within the Reactive Java or Guava ecosystems as a Publisher or a + ListenableFuture (or ApiFuture), or a descendent thereof. As in ReactiveFuture.ListenableFuturePublisher, + we simply set the callback for the future value, upon item-request (one cycle is allowed), and propagate any events + received to the publisher.

    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      java.util.concurrent.CompletionStage<java.lang.Void>acceptEither​(java.util.concurrent.CompletionStage<? extends T> other, + java.util.function.Consumer<? super T> action) 
      java.util.concurrent.CompletionStage<java.lang.Void>acceptEitherAsync​(java.util.concurrent.CompletionStage<? extends T> other, + java.util.function.Consumer<? super T> action) 
      java.util.concurrent.CompletionStage<java.lang.Void>acceptEitherAsync​(java.util.concurrent.CompletionStage<? extends T> other, + java.util.function.Consumer<? super T> action, + java.util.concurrent.Executor executor) 
      voidaddListener​(java.lang.Runnable runnable, + java.util.concurrent.Executor executor) 
      <U> java.util.concurrent.CompletionStage<U>applyToEither​(java.util.concurrent.CompletionStage<? extends T> other, + java.util.function.Function<? super T,​U> fn) 
      <U> java.util.concurrent.CompletionStage<U>applyToEitherAsync​(java.util.concurrent.CompletionStage<? extends T> other, + java.util.function.Function<? super T,​U> fn) 
      <U> java.util.concurrent.CompletionStage<U>applyToEitherAsync​(java.util.concurrent.CompletionStage<? extends T> other, + java.util.function.Function<? super T,​U> fn, + java.util.concurrent.Executor executor) 
      booleancancel​(boolean mayInterruptIfRunning) 
      java.util.concurrent.CompletionStage<T>exceptionally​(java.util.function.Function<java.lang.Throwable,​? extends T> fn) 
      Tget() 
      Tget​(long timeout, + java.util.concurrent.TimeUnit unit) 
      <U> java.util.concurrent.CompletionStage<U>handle​(java.util.function.BiFunction<? super T,​java.lang.Throwable,​? extends U> fn) 
      <U> java.util.concurrent.CompletionStage<U>handleAsync​(java.util.function.BiFunction<? super T,​java.lang.Throwable,​? extends U> fn) 
      <U> java.util.concurrent.CompletionStage<U>handleAsync​(java.util.function.BiFunction<? super T,​java.lang.Throwable,​? extends U> fn, + java.util.concurrent.Executor executor) 
      booleanisCancelled() 
      booleanisDone() 
      java.util.concurrent.CompletionStage<java.lang.Void>runAfterBoth​(java.util.concurrent.CompletionStage<?> other, + java.lang.Runnable action) 
      java.util.concurrent.CompletionStage<java.lang.Void>runAfterBothAsync​(java.util.concurrent.CompletionStage<?> other, + java.lang.Runnable action) 
      java.util.concurrent.CompletionStage<java.lang.Void>runAfterBothAsync​(java.util.concurrent.CompletionStage<?> other, + java.lang.Runnable action, + java.util.concurrent.Executor executor) 
      java.util.concurrent.CompletionStage<java.lang.Void>runAfterEither​(java.util.concurrent.CompletionStage<?> other, + java.lang.Runnable action) 
      java.util.concurrent.CompletionStage<java.lang.Void>runAfterEitherAsync​(java.util.concurrent.CompletionStage<?> other, + java.lang.Runnable action) 
      java.util.concurrent.CompletionStage<java.lang.Void>runAfterEitherAsync​(java.util.concurrent.CompletionStage<?> other, + java.lang.Runnable action, + java.util.concurrent.Executor executor) 
      voidsubscribe​(org.reactivestreams.Subscriber<? super T> subscriber) 
      java.util.concurrent.CompletionStage<java.lang.Void>thenAccept​(java.util.function.Consumer<? super T> action) 
      java.util.concurrent.CompletionStage<java.lang.Void>thenAcceptAsync​(java.util.function.Consumer<? super T> action) 
      java.util.concurrent.CompletionStage<java.lang.Void>thenAcceptAsync​(java.util.function.Consumer<? super T> action, + java.util.concurrent.Executor executor) 
      <U> java.util.concurrent.CompletionStage<java.lang.Void>thenAcceptBoth​(java.util.concurrent.CompletionStage<? extends U> other, + java.util.function.BiConsumer<? super T,​? super U> action) 
      <U> java.util.concurrent.CompletionStage<java.lang.Void>thenAcceptBothAsync​(java.util.concurrent.CompletionStage<? extends U> other, + java.util.function.BiConsumer<? super T,​? super U> action) 
      <U> java.util.concurrent.CompletionStage<java.lang.Void>thenAcceptBothAsync​(java.util.concurrent.CompletionStage<? extends U> other, + java.util.function.BiConsumer<? super T,​? super U> action, + java.util.concurrent.Executor executor) 
      <U> java.util.concurrent.CompletionStage<U>thenApply​(java.util.function.Function<? super T,​? extends U> fn) 
      <U> java.util.concurrent.CompletionStage<U>thenApplyAsync​(java.util.function.Function<? super T,​? extends U> fn) 
      <U> java.util.concurrent.CompletionStage<U>thenApplyAsync​(java.util.function.Function<? super T,​? extends U> fn, + java.util.concurrent.Executor executor) 
      <U,​V>
      java.util.concurrent.CompletionStage<V>
      thenCombine​(java.util.concurrent.CompletionStage<? extends U> other, + java.util.function.BiFunction<? super T,​? super U,​? extends V> fn) 
      <U,​V>
      java.util.concurrent.CompletionStage<V>
      thenCombineAsync​(java.util.concurrent.CompletionStage<? extends U> other, + java.util.function.BiFunction<? super T,​? super U,​? extends V> fn) 
      <U,​V>
      java.util.concurrent.CompletionStage<V>
      thenCombineAsync​(java.util.concurrent.CompletionStage<? extends U> other, + java.util.function.BiFunction<? super T,​? super U,​? extends V> fn, + java.util.concurrent.Executor executor) 
      <U> java.util.concurrent.CompletionStage<U>thenCompose​(java.util.function.Function<? super T,​? extends java.util.concurrent.CompletionStage<U>> fn) 
      <U> java.util.concurrent.CompletionStage<U>thenComposeAsync​(java.util.function.Function<? super T,​? extends java.util.concurrent.CompletionStage<U>> fn) 
      <U> java.util.concurrent.CompletionStage<U>thenComposeAsync​(java.util.function.Function<? super T,​? extends java.util.concurrent.CompletionStage<U>> fn, + java.util.concurrent.Executor executor) 
      java.util.concurrent.CompletionStage<java.lang.Void>thenRun​(java.lang.Runnable action) 
      java.util.concurrent.CompletionStage<java.lang.Void>thenRunAsync​(java.lang.Runnable action) 
      java.util.concurrent.CompletionStage<java.lang.Void>thenRunAsync​(java.lang.Runnable action, + java.util.concurrent.Executor executor) 
      java.util.concurrent.CompletableFuture<T>toCompletableFuture() 
      java.util.concurrent.CompletionStage<T>whenComplete​(java.util.function.BiConsumer<? super T,​? super java.lang.Throwable> action) 
      java.util.concurrent.CompletionStage<T>whenCompleteAsync​(java.util.function.BiConsumer<? super T,​? super java.lang.Throwable> action) 
      java.util.concurrent.CompletionStage<T>whenCompleteAsync​(java.util.function.BiConsumer<? super T,​? super java.lang.Throwable> action, + java.util.concurrent.Executor executor) 
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        subscribe

        +
        public final void subscribe​(org.reactivestreams.Subscriber<? super T> subscriber)
        +
        +
        Specified by:
        +
        subscribe in interface org.reactivestreams.Publisher<T>
        +
        +
      • +
      + + + +
        +
      • +

        addListener

        +
        public void addListener​(java.lang.Runnable runnable,
        +                        java.util.concurrent.Executor executor)
        +
        +
        Specified by:
        +
        addListener in interface com.google.common.util.concurrent.ListenableFuture<T>
        +
        +
      • +
      + + + +
        +
      • +

        cancel

        +
        public boolean cancel​(boolean mayInterruptIfRunning)
        +
        +
        Specified by:
        +
        cancel in interface java.util.concurrent.Future<T>
        +
        +
      • +
      + + + +
        +
      • +

        isCancelled

        +
        public boolean isCancelled()
        +
        +
        Specified by:
        +
        isCancelled in interface java.util.concurrent.Future<T>
        +
        +
      • +
      + + + +
        +
      • +

        isDone

        +
        public boolean isDone()
        +
        +
        Specified by:
        +
        isDone in interface java.util.concurrent.Future<T>
        +
        +
      • +
      + + + +
        +
      • +

        get

        +
        public T get()
        +      throws java.lang.InterruptedException,
        +             java.util.concurrent.ExecutionException
        +
        +
        Specified by:
        +
        get in interface java.util.concurrent.Future<T>
        +
        Throws:
        +
        java.lang.InterruptedException
        +
        java.util.concurrent.ExecutionException
        +
        +
      • +
      + + + +
        +
      • +

        get

        +
        public T get​(long timeout,
        +             java.util.concurrent.TimeUnit unit)
        +      throws java.lang.InterruptedException,
        +             java.util.concurrent.ExecutionException,
        +             java.util.concurrent.TimeoutException
        +
        +
        Specified by:
        +
        get in interface java.util.concurrent.Future<T>
        +
        Throws:
        +
        java.lang.InterruptedException
        +
        java.util.concurrent.ExecutionException
        +
        java.util.concurrent.TimeoutException
        +
        +
      • +
      + + + +
        +
      • +

        thenApply

        +
        public <U> java.util.concurrent.CompletionStage<U> thenApply​(java.util.function.Function<? super T,​? extends U> fn)
        +
        +
        Specified by:
        +
        thenApply in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        thenApplyAsync

        +
        public <U> java.util.concurrent.CompletionStage<U> thenApplyAsync​(java.util.function.Function<? super T,​? extends U> fn)
        +
        +
        Specified by:
        +
        thenApplyAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        thenApplyAsync

        +
        public <U> java.util.concurrent.CompletionStage<U> thenApplyAsync​(java.util.function.Function<? super T,​? extends U> fn,
        +                                                                  java.util.concurrent.Executor executor)
        +
        +
        Specified by:
        +
        thenApplyAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        thenAccept

        +
        public java.util.concurrent.CompletionStage<java.lang.Void> thenAccept​(java.util.function.Consumer<? super T> action)
        +
        +
        Specified by:
        +
        thenAccept in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        thenAcceptAsync

        +
        public java.util.concurrent.CompletionStage<java.lang.Void> thenAcceptAsync​(java.util.function.Consumer<? super T> action)
        +
        +
        Specified by:
        +
        thenAcceptAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        thenAcceptAsync

        +
        public java.util.concurrent.CompletionStage<java.lang.Void> thenAcceptAsync​(java.util.function.Consumer<? super T> action,
        +                                                                            java.util.concurrent.Executor executor)
        +
        +
        Specified by:
        +
        thenAcceptAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        thenRun

        +
        public java.util.concurrent.CompletionStage<java.lang.Void> thenRun​(java.lang.Runnable action)
        +
        +
        Specified by:
        +
        thenRun in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        thenRunAsync

        +
        public java.util.concurrent.CompletionStage<java.lang.Void> thenRunAsync​(java.lang.Runnable action)
        +
        +
        Specified by:
        +
        thenRunAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        thenRunAsync

        +
        public java.util.concurrent.CompletionStage<java.lang.Void> thenRunAsync​(java.lang.Runnable action,
        +                                                                         java.util.concurrent.Executor executor)
        +
        +
        Specified by:
        +
        thenRunAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        thenCombine

        +
        public <U,​V> java.util.concurrent.CompletionStage<V> thenCombine​(java.util.concurrent.CompletionStage<? extends U> other,
        +                                                                       java.util.function.BiFunction<? super T,​? super U,​? extends V> fn)
        +
        +
        Specified by:
        +
        thenCombine in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        thenCombineAsync

        +
        public <U,​V> java.util.concurrent.CompletionStage<V> thenCombineAsync​(java.util.concurrent.CompletionStage<? extends U> other,
        +                                                                            java.util.function.BiFunction<? super T,​? super U,​? extends V> fn)
        +
        +
        Specified by:
        +
        thenCombineAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        thenCombineAsync

        +
        public <U,​V> java.util.concurrent.CompletionStage<V> thenCombineAsync​(java.util.concurrent.CompletionStage<? extends U> other,
        +                                                                            java.util.function.BiFunction<? super T,​? super U,​? extends V> fn,
        +                                                                            java.util.concurrent.Executor executor)
        +
        +
        Specified by:
        +
        thenCombineAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        thenAcceptBoth

        +
        public <U> java.util.concurrent.CompletionStage<java.lang.Void> thenAcceptBoth​(java.util.concurrent.CompletionStage<? extends U> other,
        +                                                                               java.util.function.BiConsumer<? super T,​? super U> action)
        +
        +
        Specified by:
        +
        thenAcceptBoth in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        thenAcceptBothAsync

        +
        public <U> java.util.concurrent.CompletionStage<java.lang.Void> thenAcceptBothAsync​(java.util.concurrent.CompletionStage<? extends U> other,
        +                                                                                    java.util.function.BiConsumer<? super T,​? super U> action)
        +
        +
        Specified by:
        +
        thenAcceptBothAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        thenAcceptBothAsync

        +
        public <U> java.util.concurrent.CompletionStage<java.lang.Void> thenAcceptBothAsync​(java.util.concurrent.CompletionStage<? extends U> other,
        +                                                                                    java.util.function.BiConsumer<? super T,​? super U> action,
        +                                                                                    java.util.concurrent.Executor executor)
        +
        +
        Specified by:
        +
        thenAcceptBothAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        runAfterBoth

        +
        public java.util.concurrent.CompletionStage<java.lang.Void> runAfterBoth​(java.util.concurrent.CompletionStage<?> other,
        +                                                                         java.lang.Runnable action)
        +
        +
        Specified by:
        +
        runAfterBoth in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        runAfterBothAsync

        +
        public java.util.concurrent.CompletionStage<java.lang.Void> runAfterBothAsync​(java.util.concurrent.CompletionStage<?> other,
        +                                                                              java.lang.Runnable action)
        +
        +
        Specified by:
        +
        runAfterBothAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        runAfterBothAsync

        +
        public java.util.concurrent.CompletionStage<java.lang.Void> runAfterBothAsync​(java.util.concurrent.CompletionStage<?> other,
        +                                                                              java.lang.Runnable action,
        +                                                                              java.util.concurrent.Executor executor)
        +
        +
        Specified by:
        +
        runAfterBothAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        applyToEither

        +
        public <U> java.util.concurrent.CompletionStage<U> applyToEither​(java.util.concurrent.CompletionStage<? extends T> other,
        +                                                                 java.util.function.Function<? super T,​U> fn)
        +
        +
        Specified by:
        +
        applyToEither in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        applyToEitherAsync

        +
        public <U> java.util.concurrent.CompletionStage<U> applyToEitherAsync​(java.util.concurrent.CompletionStage<? extends T> other,
        +                                                                      java.util.function.Function<? super T,​U> fn)
        +
        +
        Specified by:
        +
        applyToEitherAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        applyToEitherAsync

        +
        public <U> java.util.concurrent.CompletionStage<U> applyToEitherAsync​(java.util.concurrent.CompletionStage<? extends T> other,
        +                                                                      java.util.function.Function<? super T,​U> fn,
        +                                                                      java.util.concurrent.Executor executor)
        +
        +
        Specified by:
        +
        applyToEitherAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        acceptEither

        +
        public java.util.concurrent.CompletionStage<java.lang.Void> acceptEither​(java.util.concurrent.CompletionStage<? extends T> other,
        +                                                                         java.util.function.Consumer<? super T> action)
        +
        +
        Specified by:
        +
        acceptEither in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        acceptEitherAsync

        +
        public java.util.concurrent.CompletionStage<java.lang.Void> acceptEitherAsync​(java.util.concurrent.CompletionStage<? extends T> other,
        +                                                                              java.util.function.Consumer<? super T> action)
        +
        +
        Specified by:
        +
        acceptEitherAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        acceptEitherAsync

        +
        public java.util.concurrent.CompletionStage<java.lang.Void> acceptEitherAsync​(java.util.concurrent.CompletionStage<? extends T> other,
        +                                                                              java.util.function.Consumer<? super T> action,
        +                                                                              java.util.concurrent.Executor executor)
        +
        +
        Specified by:
        +
        acceptEitherAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        runAfterEither

        +
        public java.util.concurrent.CompletionStage<java.lang.Void> runAfterEither​(java.util.concurrent.CompletionStage<?> other,
        +                                                                           java.lang.Runnable action)
        +
        +
        Specified by:
        +
        runAfterEither in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        runAfterEitherAsync

        +
        public java.util.concurrent.CompletionStage<java.lang.Void> runAfterEitherAsync​(java.util.concurrent.CompletionStage<?> other,
        +                                                                                java.lang.Runnable action)
        +
        +
        Specified by:
        +
        runAfterEitherAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        runAfterEitherAsync

        +
        public java.util.concurrent.CompletionStage<java.lang.Void> runAfterEitherAsync​(java.util.concurrent.CompletionStage<?> other,
        +                                                                                java.lang.Runnable action,
        +                                                                                java.util.concurrent.Executor executor)
        +
        +
        Specified by:
        +
        runAfterEitherAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        thenCompose

        +
        public <U> java.util.concurrent.CompletionStage<U> thenCompose​(java.util.function.Function<? super T,​? extends java.util.concurrent.CompletionStage<U>> fn)
        +
        +
        Specified by:
        +
        thenCompose in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        thenComposeAsync

        +
        public <U> java.util.concurrent.CompletionStage<U> thenComposeAsync​(java.util.function.Function<? super T,​? extends java.util.concurrent.CompletionStage<U>> fn)
        +
        +
        Specified by:
        +
        thenComposeAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        thenComposeAsync

        +
        public <U> java.util.concurrent.CompletionStage<U> thenComposeAsync​(java.util.function.Function<? super T,​? extends java.util.concurrent.CompletionStage<U>> fn,
        +                                                                    java.util.concurrent.Executor executor)
        +
        +
        Specified by:
        +
        thenComposeAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        handle

        +
        public <U> java.util.concurrent.CompletionStage<U> handle​(java.util.function.BiFunction<? super T,​java.lang.Throwable,​? extends U> fn)
        +
        +
        Specified by:
        +
        handle in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        handleAsync

        +
        public <U> java.util.concurrent.CompletionStage<U> handleAsync​(java.util.function.BiFunction<? super T,​java.lang.Throwable,​? extends U> fn)
        +
        +
        Specified by:
        +
        handleAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        handleAsync

        +
        public <U> java.util.concurrent.CompletionStage<U> handleAsync​(java.util.function.BiFunction<? super T,​java.lang.Throwable,​? extends U> fn,
        +                                                               java.util.concurrent.Executor executor)
        +
        +
        Specified by:
        +
        handleAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        whenComplete

        +
        public java.util.concurrent.CompletionStage<TwhenComplete​(java.util.function.BiConsumer<? super T,​? super java.lang.Throwable> action)
        +
        +
        Specified by:
        +
        whenComplete in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        whenCompleteAsync

        +
        public java.util.concurrent.CompletionStage<TwhenCompleteAsync​(java.util.function.BiConsumer<? super T,​? super java.lang.Throwable> action)
        +
        +
        Specified by:
        +
        whenCompleteAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        whenCompleteAsync

        +
        public java.util.concurrent.CompletionStage<TwhenCompleteAsync​(java.util.function.BiConsumer<? super T,​? super java.lang.Throwable> action,
        +                                                                 java.util.concurrent.Executor executor)
        +
        +
        Specified by:
        +
        whenCompleteAsync in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        exceptionally

        +
        public java.util.concurrent.CompletionStage<Texceptionally​(java.util.function.Function<java.lang.Throwable,​? extends T> fn)
        +
        +
        Specified by:
        +
        exceptionally in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      + + + +
        +
      • +

        toCompletableFuture

        +
        public java.util.concurrent.CompletableFuture<TtoCompletableFuture()
        +
        +
        Specified by:
        +
        toCompletableFuture in interface java.util.concurrent.CompletionStage<T>
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription.html b/docs/java/gust/backend/runtime/ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription.html new file mode 100644 index 000000000..eae09855b --- /dev/null +++ b/docs/java/gust/backend/runtime/ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription.html @@ -0,0 +1,320 @@ + + + + + +ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.runtime.ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    org.reactivestreams.Subscription
    +
    +
    +
    Enclosing class:
    +
    ReactiveFuture.ListenableFuturePublisher<T>
    +
    +
    +
    @Immutable
    +@ThreadSafe
    +public final class ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription
    +extends java.lang.Object
    +implements org.reactivestreams.Subscription
    +
    Models a Reactive Java Subscription, which is responsible for propagating events from a + ListenableFuture to a Subscriber. + +

    This object is generally used internally by the ReactiveFuture.ListenableFuturePublisher, once a Subscriber + attaches itself to a Publisher that is actually a wrapped ListenableFuture. Error (exception) + events and value events are both propagated. Subscribers based on this wrapping will only ever receive a maximum + of one value or one error.

    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      voidcancel() +
      Request the publisher to stop sending data and clean up resources.
      +
      voidrequest​(long n) +
      Request the specified number of items from the underlying Subscription.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        request

        +
        public void request​(long n)
        +
        Request the specified number of items from the underlying Subscription. This must always be +
        1
        .
        +
        +
        Specified by:
        +
        request in interface org.reactivestreams.Subscription
        +
        Parameters:
        +
        n - Number of elements to request to the upstream (must always be
        1
        ).
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If any value other than
        1
        is passed in.
        +
        +
      • +
      + + + +
        +
      • +

        cancel

        +
        public void cancel()
        +
        Request the publisher to stop sending data and clean up resources.
        +
        +
        Specified by:
        +
        cancel in interface org.reactivestreams.Subscription
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/ReactiveFuture.ListenableFuturePublisher.html b/docs/java/gust/backend/runtime/ReactiveFuture.ListenableFuturePublisher.html new file mode 100644 index 000000000..897e69112 --- /dev/null +++ b/docs/java/gust/backend/runtime/ReactiveFuture.ListenableFuturePublisher.html @@ -0,0 +1,318 @@ + + + + + +ReactiveFuture.ListenableFuturePublisher + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class ReactiveFuture.ListenableFuturePublisher<T>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.runtime.ReactiveFuture.ListenableFuturePublisher<T>
    • +
    +
  • +
+
+
    +
  • +
    +
    Type Parameters:
    +
    T - Emit type for this adapter. Matches the publisher it wraps.
    +
    +
    +
    All Implemented Interfaces:
    +
    org.reactivestreams.Publisher<T>
    +
    +
    +
    Enclosing class:
    +
    ReactiveFuture<R>
    +
    +
    +
    public static final class ReactiveFuture.ListenableFuturePublisher<T>
    +extends java.lang.Object
    +implements org.reactivestreams.Publisher<T>
    +
    Structure that adapts Guava's ListenableFuture to a Reactive Java Publisher, which publishes one + item - either the result of the computation, or an error. + +

    This object is used in the specific circumstance that a ListenableFuture is wrapped by a + ReactiveFuture, and then used within the Reactive Java ecosystem as a Publisher. We simply set a + callback for the future value, upon item-request (one cycle is allowed), and propagate any events received to the + publisher.

    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      voidsubscribe​(org.reactivestreams.Subscriber<? super T> subscriber) 
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        subscribe

        +
        public final void subscribe​(org.reactivestreams.Subscriber<? super T> subscriber)
        +
        +
        Specified by:
        +
        subscribe in interface org.reactivestreams.Publisher<T>
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/ReactiveFuture.PublisherListenableFuture.html b/docs/java/gust/backend/runtime/ReactiveFuture.PublisherListenableFuture.html new file mode 100644 index 000000000..3c5545346 --- /dev/null +++ b/docs/java/gust/backend/runtime/ReactiveFuture.PublisherListenableFuture.html @@ -0,0 +1,424 @@ + + + + + +ReactiveFuture.PublisherListenableFuture + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class ReactiveFuture.PublisherListenableFuture<T>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.runtime.ReactiveFuture.PublisherListenableFuture<T>
    • +
    +
  • +
+
+
    +
  • +
    +
    Type Parameters:
    +
    T - Generic type returned by the future.
    +
    +
    +
    All Implemented Interfaces:
    +
    com.google.common.util.concurrent.ListenableFuture<T>, java.util.concurrent.Future<T>, org.reactivestreams.Publisher<T>
    +
    +
    +
    Enclosing class:
    +
    ReactiveFuture<R>
    +
    +
    +
    @Immutable
    +@ThreadSafe
    +public static final class ReactiveFuture.PublisherListenableFuture<T>
    +extends java.lang.Object
    +implements com.google.common.util.concurrent.ListenableFuture<T>, org.reactivestreams.Publisher<T>
    +
    Structure that adapts a Publisher to a ListenableFuture interface. We accomplish this by + immediately subscribing to the publisher with a callback that dispatches a SettableFuture. + +

    This object is used in the specific circumstance of wrapping a Publisher, and then using the wrapped + object as a ListenableFuture (or any descendent or compliant implementation thereof).

    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      voidaddListener​(java.lang.Runnable runnable, + java.util.concurrent.Executor executor) 
      booleancancel​(boolean mayInterruptIfRunning) 
      Tget() 
      Tget​(long timeout, + java.util.concurrent.TimeUnit unit) 
      booleanisCancelled() 
      booleanisDone() 
      voidsubscribe​(org.reactivestreams.Subscriber<? super T> s) 
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        subscribe

        +
        public void subscribe​(org.reactivestreams.Subscriber<? super T> s)
        +
        +
        Specified by:
        +
        subscribe in interface org.reactivestreams.Publisher<T>
        +
        +
      • +
      + + + +
        +
      • +

        addListener

        +
        public void addListener​(@Nonnull
        +                        java.lang.Runnable runnable,
        +                        @Nonnull
        +                        java.util.concurrent.Executor executor)
        +
        +
        Specified by:
        +
        addListener in interface com.google.common.util.concurrent.ListenableFuture<T>
        +
        +
      • +
      + + + +
        +
      • +

        cancel

        +
        public boolean cancel​(boolean mayInterruptIfRunning)
        +
        +
        Specified by:
        +
        cancel in interface java.util.concurrent.Future<T>
        +
        +
      • +
      + + + +
        +
      • +

        isCancelled

        +
        public boolean isCancelled()
        +
        +
        Specified by:
        +
        isCancelled in interface java.util.concurrent.Future<T>
        +
        +
      • +
      + + + +
        +
      • +

        isDone

        +
        public boolean isDone()
        +
        +
        Specified by:
        +
        isDone in interface java.util.concurrent.Future<T>
        +
        +
      • +
      + + + +
        +
      • +

        get

        +
        public T get()
        +      throws java.lang.InterruptedException,
        +             java.util.concurrent.ExecutionException
        +
        +
        Specified by:
        +
        get in interface java.util.concurrent.Future<T>
        +
        Throws:
        +
        java.lang.InterruptedException
        +
        java.util.concurrent.ExecutionException
        +
        +
      • +
      + + + +
        +
      • +

        get

        +
        public T get​(long timeout,
        +             @Nonnull
        +             java.util.concurrent.TimeUnit unit)
        +      throws java.lang.InterruptedException,
        +             java.util.concurrent.ExecutionException,
        +             java.util.concurrent.TimeoutException
        +
        +
        Specified by:
        +
        get in interface java.util.concurrent.Future<T>
        +
        Throws:
        +
        java.lang.InterruptedException
        +
        java.util.concurrent.ExecutionException
        +
        java.util.concurrent.TimeoutException
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/ReactiveFuture.html b/docs/java/gust/backend/runtime/ReactiveFuture.html new file mode 100644 index 000000000..034ba3c73 --- /dev/null +++ b/docs/java/gust/backend/runtime/ReactiveFuture.html @@ -0,0 +1,978 @@ + + + + + +ReactiveFuture + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class ReactiveFuture<R>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.runtime.ReactiveFuture<R>
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    com.google.api.core.ApiFuture<R>, com.google.common.util.concurrent.ListenableFuture<R>, java.util.concurrent.Future<R>, org.reactivestreams.Publisher<R>
    +
    +
    +
    @Immutable
    +@ThreadSafe
    +public final class ReactiveFuture<R>
    +extends java.lang.Object
    +implements org.reactivestreams.Publisher<R>, com.google.common.util.concurrent.ListenableFuture<R>, com.google.api.core.ApiFuture<R>
    +
    Adapts future/async value containers from different frameworks (namely, Reactive Java, Guava, and the JDK). + +

    Create a new ReactiveFuture by using any of the wrap) factory methods. The resulting object is + usable as a Publisher, ListenableFuture, or ApiFuture. This object simply wraps whatever + inner object is provided, and as such instances are lightweight; there is no default functionality after immediate + construction in most cases.

    + +

    Caveat: when using a Publisher as a ListenableFuture (i.e. wrapping a Publisher and + then using any of the typical future methods, like ListenableFuture.addListener(Runnable, Executor)), the + underlying publisher may not publish more than one value. This is to prevent dropping intermediate values on the + floor, silently, before dispatching the future's callbacks, which generally only accept one value. Other than this, + things should work "as expected" whether you're looking at them from a Guava, JDK, or Reactive perspective.

    +
    +
    See Also:
    +
    Reactive Java type adapted by this object., +Guava's extension of the JDK's basic , which adds listener support., +Lightweight Guava-like future meant to avoid dependencies on Java in API libraries., +To wrap a ., +To wrap a ., +To wrap an .
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Nested Class Summary

      + + + + + + + + + + + + + + + + + + + + + + +
      Nested Classes 
      Modifier and TypeClassDescription
      static class ReactiveFuture.CompletableFuturePublisher<T> +
      Structure that adapts Java's CompletableFuture to a Reactive Java Publisher, which publishes one + item - either the result of the computation, or an error.
      +
      static class ReactiveFuture.ListenableFuturePublisher<T> +
      Structure that adapts Guava's ListenableFuture to a Reactive Java Publisher, which publishes one + item - either the result of the computation, or an error.
      +
      static class ReactiveFuture.PublisherListenableFuture<T> +
      Structure that adapts a Publisher to a ListenableFuture interface.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      voidaddListener​(java.lang.Runnable listener, + java.util.concurrent.Executor executor) +
      Registers a listener to be run on the given executor.
      +
      booleancancel​(boolean mayInterruptIfRunning) +
      Attempts to cancel execution of this task.
      +
      static <R> ReactiveFuture<R>cancelled() +
      Create an already-cancelled future.
      +
      static <R> ReactiveFuture<R>done​(R value) +
      Create an already-resolved future, wrapping the provided value.
      +
      static <R> ReactiveFuture<R>failed​(java.lang.Throwable error) +
      Create an already-failed future, wrapping the provided exception instance.
      +
      Rget() +
      Waits if necessary for the computation to complete, and then retrieves its result.
      +
      Rget​(long timeout, + java.util.concurrent.TimeUnit unit) +
      Waits if necessary for at most the given time for the computation to complete, and then retrieves its result, if + available.
      +
      booleanisCancelled() +
      Returns true if this task was cancelled before it completed normally.
      +
      booleanisDone() +
      Returns true if this task completed.
      +
      voidsubscribe​(org.reactivestreams.Subscriber<? super R> subscriber) +
      Request Publisher to start streaming data.
      +
      static <R> ReactiveFuture<R>wrap​(com.google.api.core.ApiFuture<R> apiFuture) +
      Wrap a Google APIs ApiFuture in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value.
      +
      static <R> ReactiveFuture<R>wrap​(com.google.api.core.ApiFuture<R> apiFuture, + java.util.concurrent.Executor executor) +
      Wrap a Google APIs ApiFuture in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value.
      +
      static <R> ReactiveFuture<R>wrap​(com.google.common.util.concurrent.ListenableFuture<R> future) +
      Wrap a Guava ListenableFuture in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value.
      +
      static <R> ReactiveFuture<R>wrap​(com.google.common.util.concurrent.ListenableFuture<R> future, + java.util.concurrent.Executor executor) +
      Wrap a Guava ListenableFuture in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value.
      +
      static <R> ReactiveFuture<R>wrap​(java.util.concurrent.CompletableFuture<R> future) +
      Wrap a regular Java CompletableFuture in a universal ReactiveFuture, such that it may be used with + any interface requiring support for that class.
      +
      static <R> ReactiveFuture<R>wrap​(java.util.concurrent.CompletableFuture<R> future, + java.util.concurrent.Executor executor) +
      Wrap a regular Java CompletableFuture in a universal ReactiveFuture, such that it may be used with + any interface requiring support for that class.
      +
      static <R> ReactiveFuture<R>wrap​(org.reactivestreams.Publisher<R> publisher) +
      Wrap a Reactive Java Publisher in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        wrap

        +
        @Nonnull
        +public static <R> ReactiveFuture<R> wrap​(@Nonnull
        +                                         org.reactivestreams.Publisher<R> publisher)
        +
        Wrap a Reactive Java Publisher in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value. + +

        The resulting object is usable as any of ListenableFuture, Publisher, or ApiFuture. See + class docs for more information.

        + +

        Note: to use a Publisher as a Future (or any descendent thereof), the Publisher + may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and + accessed as a Future, to prevent silently dropping intermediate values on the floor.

        +
        +
        Type Parameters:
        +
        R - Return or emission type of the publisher.
        +
        Parameters:
        +
        publisher - Reactive publisher to wrap.
        +
        Returns:
        +
        Wrapped reactive future object.
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If the passed `publisher` is `null`.
        +
        See Also:
        +
        Wraps a from Guava.
        +
        +
      • +
      + + + +
        +
      • +

        wrap

        +
        @Nonnull
        +public static <R> ReactiveFuture<R> wrap​(@Nonnull
        +                                         java.util.concurrent.CompletableFuture<R> future)
        +
        Wrap a regular Java CompletableFuture in a universal ReactiveFuture, such that it may be used with + any interface requiring support for that class. + +

        The resulting object is usable as any of ListenableFuture, Publisher, or ApiFuture. See + class docs for more information.

        + +

        Note: to use a Publisher as a Future (or any descendent thereof), the Publisher + may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and + accessed as a Future, to prevent silently dropping intermediate values on the floor.

        + +

        Warning: this method uses MoreExecutors.directExecutor() for callback execution. You should only + do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily + recommended to use the variants of wrap that accept an Executor. For instance, the corresponding + method to this one is wrap(ListenableFuture, Executor).

        +
        +
        Type Parameters:
        +
        R - Return or emission type of the future.
        +
        Parameters:
        +
        future - Completable future to wrap.
        +
        Returns:
        +
        Wrapped reactive future object.
        +
        +
      • +
      + + + +
        +
      • +

        wrap

        +
        @Nonnull
        +public static <R> ReactiveFuture<R> wrap​(@Nonnull
        +                                         java.util.concurrent.CompletableFuture<R> future,
        +                                         @Nonnull
        +                                         java.util.concurrent.Executor executor)
        +
        Wrap a regular Java CompletableFuture in a universal ReactiveFuture, such that it may be used with + any interface requiring support for that class. + +

        The resulting object is usable as any of ListenableFuture, Publisher, or ApiFuture. See + class docs for more information.

        + +

        Note: to use a Publisher as a Future (or any descendent thereof), the Publisher + may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and + accessed as a Future, to prevent silently dropping intermediate values on the floor.

        +
        +
        Type Parameters:
        +
        R - Return or emission type of the future.
        +
        Parameters:
        +
        future - Completable future to wrap.
        +
        executor - Executor to use.
        +
        Returns:
        +
        Wrapped reactive future object.
        +
        +
      • +
      + + + +
        +
      • +

        wrap

        +
        @Nonnull
        +public static <R> ReactiveFuture<R> wrap​(@Nonnull
        +                                         com.google.common.util.concurrent.ListenableFuture<R> future)
        +
        Wrap a Guava ListenableFuture in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value. + +

        Warning: this method uses MoreExecutors.directExecutor() for callback execution. You should only + do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily + recommended to use the variants of wrap that accept an Executor. For instance, the corresponding + method to this one is wrap(ListenableFuture, Executor).

        + +

        The resulting object is usable as any of ListenableFuture, Publisher, or ApiFuture. See + class docs for more information.

        + +

        Note: to use a Publisher as a Future (or any descendent thereof), the Publisher + may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and + accessed as a Future, to prevent silently dropping intermediate values on the floor.

        +
        +
        Type Parameters:
        +
        R - Return value type for the future.
        +
        Parameters:
        +
        future - Future value to wrap.
        +
        Returns:
        +
        Wrapped reactive future object.
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If the passed `future` is `null`.
        +
        See Also:
        +
        Wraps a Reactive Java .
        +
        +
      • +
      + + + +
        +
      • +

        wrap

        +
        @Nonnull
        +public static <R> ReactiveFuture<R> wrap​(@Nonnull
        +                                         com.google.common.util.concurrent.ListenableFuture<R> future,
        +                                         @Nonnull
        +                                         java.util.concurrent.Executor executor)
        +
        Wrap a Guava ListenableFuture in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value. + +

        The resulting object is usable as any of ListenableFuture, Publisher, or ApiFuture. See + class docs for more information.

        + +

        Note: to use a Publisher as a Future (or any descendent thereof), the Publisher + may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and + accessed as a Future, to prevent silently dropping intermediate values on the floor.

        +
        +
        Type Parameters:
        +
        R - Return value type for the future.
        +
        Parameters:
        +
        future - Future value to wrap.
        +
        executor - Executor to dispatch callbacks with.
        +
        Returns:
        +
        Wrapped reactive future object.
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If the passed `future` is `null`.
        +
        See Also:
        +
        Wraps a Reactive Java .
        +
        +
      • +
      + + + +
        +
      • +

        wrap

        +
        @Nonnull
        +public static <R> ReactiveFuture<R> wrap​(@Nonnull
        +                                         com.google.api.core.ApiFuture<R> apiFuture,
        +                                         @Nonnull
        +                                         java.util.concurrent.Executor executor)
        +
        Wrap a Google APIs ApiFuture in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value. + +

        The resulting object is usable as any of ListenableFuture, Publisher, or ApiFuture. See + class docs for more information.

        + +

        Note: to use a Publisher as a Future (or any descendent thereof), the Publisher + may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and + accessed as a Future, to prevent silently dropping intermediate values on the floor.

        +
        +
        Type Parameters:
        +
        R - Return value type for the future.
        +
        Parameters:
        +
        apiFuture - API future to wrap.
        +
        executor - Executor to run callbacks with.
        +
        Returns:
        +
        Wrapped reactive future object.
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If the passed `apiFuture` is `null`.
        +
        See Also:
        +
        Wraps a Reactive Java ., +Wraps a regular Guava .
        +
        +
      • +
      + + + +
        +
      • +

        wrap

        +
        @Nonnull
        +public static <R> ReactiveFuture<R> wrap​(@Nonnull
        +                                         com.google.api.core.ApiFuture<R> apiFuture)
        +
        Wrap a Google APIs ApiFuture in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value. + +

        Warning: this method uses MoreExecutors.directExecutor() for callback execution. You should only + do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily + recommended to use the variants of wrap that accept an Executor. For instance, the corresponding + method to this one is wrap(ListenableFuture, Executor).

        + +

        The resulting object is usable as any of ListenableFuture, Publisher, or ApiFuture. See + class docs for more information.

        + +

        Note: to use a Publisher as a Future (or any descendent thereof), the Publisher + may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and + accessed as a Future, to prevent silently dropping intermediate values on the floor.

        +
        +
        Type Parameters:
        +
        R - Return value type for the future.
        +
        Parameters:
        +
        apiFuture - API future to wrap.
        +
        Returns:
        +
        Wrapped reactive future object.
        +
        Throws:
        +
        java.lang.IllegalArgumentException - If the passed `apiFuture` is `null`.
        +
        See Also:
        +
        Wraps a Reactive Java ., +Wraps a regular Guava .
        +
        +
      • +
      + + + + + +
        +
      • +

        done

        +
        @Nonnull
        +public static <R> ReactiveFuture<R> done​(@Nonnull
        +                                         R value)
        +
        Create an already-resolved future, wrapping the provided value. The future will present as done as soon as it is + returned from this method. + +

        Under the hood, this is simply a ReactiveFuture wrapping a call to + Futures.immediateFuture(Object).

        +
        +
        Type Parameters:
        +
        R - Return value generic type.
        +
        Parameters:
        +
        value - Value to wrap in an already-completed future.
        +
        Returns:
        +
        Reactive future wrapping a finished value.
        +
        +
      • +
      + + + +
        +
      • +

        failed

        +
        @Nonnull
        +public static <R> ReactiveFuture<R> failed​(@Nonnull
        +                                           java.lang.Throwable error)
        +
        Create an already-failed future, wrapping the provided exception instance. The future will present as one as soon + as it is returned from this method. + +

        Calling Future.get(long, TimeUnit) or Future.get() on a failed future will surface the + associated exception where invocation occurs. Under the hood, this is simply a ReactiveFuture wrapping a + call to Futures.immediateFailedFuture(Throwable).

        +
        +
        Type Parameters:
        +
        R - Return value generic type.
        +
        Parameters:
        +
        error - Error to wrap in an already-failed future.
        +
        Returns:
        +
        Reactive future wrapping a finished value.
        +
        +
      • +
      + + + +
        +
      • +

        cancelled

        +
        @Nonnull
        +public static <R> ReactiveFuture<R> cancelled()
        +
        Create an already-cancelled future. The future will present as both done and cancelled as soon as it is returned + from this method. + +

        Under the hood, this is simply a ReactiveFuture wrapping a call to + Futures.immediateCancelledFuture().

        +
        +
        Type Parameters:
        +
        R - Return value generic type.
        +
        Returns:
        +
        Reactive future wrapping a cancelled operation.
        +
        +
      • +
      + + + +
        +
      • +

        subscribe

        +
        public void subscribe​(org.reactivestreams.Subscriber<? super R> subscriber)
        +
        Request Publisher to start streaming data. + +

        This is a "factory method" and can be called multiple times, each time starting a new Subscription. Each + Subscription will work for only a single Subscriber. A Subscriber should only subscribe + once to a single Publisher. If the Publisher rejects the subscription attempt or otherwise fails it + will signal the error via Subscriber.onError(java.lang.Throwable).

        +
        +
        Specified by:
        +
        subscribe in interface org.reactivestreams.Publisher<R>
        +
        Parameters:
        +
        subscriber - the Subscriber that will consume signals from this Publisher.
        +
        +
      • +
      + + + +
        +
      • +

        addListener

        +
        public void addListener​(@Nonnull
        +                        java.lang.Runnable listener,
        +                        @Nonnull
        +                        java.util.concurrent.Executor executor)
        +                 throws java.util.concurrent.RejectedExecutionException
        +
        Registers a listener to be run on the given executor. The listener will run + when the Future's computation is complete or, if the computation is already + complete, immediately. + +

        There is no guaranteed ordering of execution of listeners, but any listener added through this method is + guaranteed to be called once the computation is complete.

        + +

        Exceptions thrown by a listener will be propagated up to the executor. Any exception thrown during + Executor.execute (e.g., a RejectedExecutionException or an exception thrown by + direct execution) will be caught and logged.

        + +

        Note: For fast, lightweight listeners that would be safe to execute in any thread, consider + MoreExecutors.directExecutor(). Otherwise, avoid it. Heavyweight directExecutor listeners can cause + problems, and these problems can be difficult to reproduce because they depend on timing. For example:

        +
          +
        • The listener may be executed by the caller of addListener. That caller may be a + UI thread or other latency-sensitive thread. This can harm UI responsiveness. +
        • The listener may be executed by the thread that completes this Future. That + thread may be an internal system thread such as an RPC network thread. Blocking that + thread may stall progress of the whole system. It may even cause a deadlock. +
        • The listener may delay other listeners, even listeners that are not themselves + directExecutor listeners. +
        + +

        This is the most general listener interface. For common operations performed using listeners, see + Futures. For a simplified but general listener interface, see + addCallback().

        + +

        Memory consistency effects: Actions in a thread prior to adding a listener happen-before its + execution begins, perhaps in another thread.

        + +

        Guava implementations of ListenableFuture promptly release references to listeners after executing + them.

        +
        +
        Specified by:
        +
        addListener in interface com.google.api.core.ApiFuture<R>
        +
        Specified by:
        +
        addListener in interface com.google.common.util.concurrent.ListenableFuture<R>
        +
        Parameters:
        +
        listener - the listener to run when the computation is complete.
        +
        executor - the executor to run the listener in
        +
        Throws:
        +
        java.util.concurrent.RejectedExecutionException - if we tried to execute the listener immediately but the executor rejecte it.
        +
        +
      • +
      + + + +
        +
      • +

        cancel

        +
        public boolean cancel​(boolean mayInterruptIfRunning)
        +
        Attempts to cancel execution of this task. This attempt will fail if the task has already completed, has already + been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when + cancel is called, this task should never run. If the task has already started, then the + mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in + an attempt to stop the task. + +

        After this method returns, subsequent calls to isDone() will always return true. Subsequent + calls to isCancelled() will always return true if this method returned true.

        +
        +
        Specified by:
        +
        cancel in interface java.util.concurrent.Future<R>
        +
        Parameters:
        +
        mayInterruptIfRunning - true if the thread executing this task should be interrupted; otherwise, + in-progress tasks are allowed to complete
        +
        Returns:
        +
        false if the task could not be cancelled, typically because it has already completed normally; + true otherwise.
        +
        +
      • +
      + + + +
        +
      • +

        isCancelled

        +
        public boolean isCancelled()
        +
        Returns true if this task was cancelled before it completed normally. This defers to the underlying future, + or a wrapped object if using a Publisher.
        +
        +
        Specified by:
        +
        isCancelled in interface java.util.concurrent.Future<R>
        +
        Returns:
        +
        true if this task was cancelled before it completed
        +
        +
      • +
      + + + +
        +
      • +

        isDone

        +
        public boolean isDone()
        +
        Returns true if this task completed. This defers to the underlying future, or a wrapped object if using a + Reactive Java Publisher. + + Completion may be due to normal termination, an exception, or cancellation -- in all of these cases, this method + will return true.
        +
        +
        Specified by:
        +
        isDone in interface java.util.concurrent.Future<R>
        +
        Returns:
        +
        true if this task completed.
        +
        +
      • +
      + + + +
        +
      • +

        get

        +
        public R get()
        +      throws java.lang.InterruptedException,
        +             java.util.concurrent.ExecutionException
        +
        Waits if necessary for the computation to complete, and then retrieves its result. + +

        It is generally recommended to use the variant of this method which specifies a timeout - one must handle the + additional TimeoutException, but on the other hand the computation can never infinitely block if an async + value does not materialize.

        +
        +
        Specified by:
        +
        get in interface java.util.concurrent.Future<R>
        +
        Returns:
        +
        the computed result.
        +
        Throws:
        +
        java.util.concurrent.CancellationException - if the computation was cancelled
        +
        java.util.concurrent.ExecutionException - if the computation threw an exception
        +
        java.lang.InterruptedException - if the current thread was interrupted while waiting
        +
        See Also:
        +
        For a safer version of this method, which allows specifying a timeout.
        +
        +
      • +
      + + + +
        +
      • +

        get

        +
        public R get​(long timeout,
        +             @Nonnull
        +             java.util.concurrent.TimeUnit unit)
        +      throws java.lang.InterruptedException,
        +             java.util.concurrent.ExecutionException,
        +             java.util.concurrent.TimeoutException
        +
        Waits if necessary for at most the given time for the computation to complete, and then retrieves its result, if + available.
        +
        +
        Specified by:
        +
        get in interface java.util.concurrent.Future<R>
        +
        Parameters:
        +
        timeout - the maximum time to wait
        +
        unit - the time unit of the timeout argument
        +
        Returns:
        +
        the computed result
        +
        Throws:
        +
        java.util.concurrent.CancellationException - if the computation was cancelled
        +
        java.util.concurrent.ExecutionException - if the computation threw an exception
        +
        java.lang.InterruptedException - if the current thread was interrupted while waiting
        +
        java.util.concurrent.TimeoutException - if the wait timed out
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/class-use/AssetManager.ManagedAsset.html b/docs/java/gust/backend/runtime/class-use/AssetManager.ManagedAsset.html new file mode 100644 index 000000000..ece5f9a8f --- /dev/null +++ b/docs/java/gust/backend/runtime/class-use/AssetManager.ManagedAsset.html @@ -0,0 +1,211 @@ + + + + + +Uses of Class gust.backend.runtime.AssetManager.ManagedAsset + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.runtime.AssetManager.ManagedAsset

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/class-use/AssetManager.ManagedAssetContent.html b/docs/java/gust/backend/runtime/class-use/AssetManager.ManagedAssetContent.html new file mode 100644 index 000000000..e05b23f09 --- /dev/null +++ b/docs/java/gust/backend/runtime/class-use/AssetManager.ManagedAssetContent.html @@ -0,0 +1,216 @@ + + + + + +Uses of Class gust.backend.runtime.AssetManager.ManagedAssetContent + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.runtime.AssetManager.ManagedAssetContent

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/class-use/AssetManager.ModuleType.html b/docs/java/gust/backend/runtime/class-use/AssetManager.ModuleType.html new file mode 100644 index 000000000..31361676d --- /dev/null +++ b/docs/java/gust/backend/runtime/class-use/AssetManager.ModuleType.html @@ -0,0 +1,209 @@ + + + + + +Uses of Class gust.backend.runtime.AssetManager.ModuleType + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.runtime.AssetManager.ModuleType

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/class-use/AssetManager.html b/docs/java/gust/backend/runtime/class-use/AssetManager.html new file mode 100644 index 000000000..164a4e40c --- /dev/null +++ b/docs/java/gust/backend/runtime/class-use/AssetManager.html @@ -0,0 +1,196 @@ + + + + + +Uses of Class gust.backend.runtime.AssetManager + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.runtime.AssetManager

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/class-use/Logging.html b/docs/java/gust/backend/runtime/class-use/Logging.html new file mode 100644 index 000000000..5f60230bc --- /dev/null +++ b/docs/java/gust/backend/runtime/class-use/Logging.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.runtime.Logging + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.runtime.Logging

+
+
No usage of gust.backend.runtime.Logging
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/class-use/ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription.html b/docs/java/gust/backend/runtime/class-use/ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription.html new file mode 100644 index 000000000..4e4f389b2 --- /dev/null +++ b/docs/java/gust/backend/runtime/class-use/ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription

+
+
No usage of gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/class-use/ReactiveFuture.CompletableFuturePublisher.html b/docs/java/gust/backend/runtime/class-use/ReactiveFuture.CompletableFuturePublisher.html new file mode 100644 index 000000000..34c597feb --- /dev/null +++ b/docs/java/gust/backend/runtime/class-use/ReactiveFuture.CompletableFuturePublisher.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher

+
+
No usage of gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/class-use/ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription.html b/docs/java/gust/backend/runtime/class-use/ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription.html new file mode 100644 index 000000000..e5e62a978 --- /dev/null +++ b/docs/java/gust/backend/runtime/class-use/ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.runtime.ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.runtime.ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription

+
+
No usage of gust.backend.runtime.ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/class-use/ReactiveFuture.ListenableFuturePublisher.html b/docs/java/gust/backend/runtime/class-use/ReactiveFuture.ListenableFuturePublisher.html new file mode 100644 index 000000000..736b0c9fd --- /dev/null +++ b/docs/java/gust/backend/runtime/class-use/ReactiveFuture.ListenableFuturePublisher.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.runtime.ReactiveFuture.ListenableFuturePublisher + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.runtime.ReactiveFuture.ListenableFuturePublisher

+
+
No usage of gust.backend.runtime.ReactiveFuture.ListenableFuturePublisher
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/class-use/ReactiveFuture.PublisherListenableFuture.html b/docs/java/gust/backend/runtime/class-use/ReactiveFuture.PublisherListenableFuture.html new file mode 100644 index 000000000..79822c799 --- /dev/null +++ b/docs/java/gust/backend/runtime/class-use/ReactiveFuture.PublisherListenableFuture.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.runtime.ReactiveFuture.PublisherListenableFuture + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.runtime.ReactiveFuture.PublisherListenableFuture

+
+
No usage of gust.backend.runtime.ReactiveFuture.PublisherListenableFuture
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/class-use/ReactiveFuture.html b/docs/java/gust/backend/runtime/class-use/ReactiveFuture.html new file mode 100644 index 000000000..45f6450f2 --- /dev/null +++ b/docs/java/gust/backend/runtime/class-use/ReactiveFuture.html @@ -0,0 +1,680 @@ + + + + + +Uses of Class gust.backend.runtime.ReactiveFuture + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.runtime.ReactiveFuture

+
+
+
    +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Packages that use ReactiveFuture 
    PackageDescription
    gust.backend.driver.firestore +
    Provides a DatabaseDriver implementation, using Google Cloud Firestore.
    +
    gust.backend.driver.inmemory +
    Provides a reference implementation of a persistence driver, backed by a concurrent map.
    +
    gust.backend.driver.spanner +
    Provides a DatabaseDriver implementation for integration with Google Cloud Spanner.
    +
    gust.backend.model +
    Provides definitions and structure for logic dealing with business data.
    +
    gust.backend.runtime +
    Supplies core backend runtime logic, like execution/scheduling, logging, and so on.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of ReactiveFuture in gust.backend.driver.firestore

      + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.driver.firestore that return ReactiveFuture 
      Modifier and TypeMethodDescription
      ReactiveFuture<Key>FirestoreDriver.delete​(Key key, + DeleteOptions options) +
      Low-level record delete method.
      +
      ReactiveFuture<Model>FirestoreDriver.persist​(Key key, + Model model, + WriteOptions options) +
      Low-level record persistence method.
      +
      ReactiveFuture<java.util.Optional<Model>>FirestoreDriver.retrieve​(Key key, + FetchOptions opts) +
      Low-level record retrieval method.
      +
      +
      +
    • +
    • +
      + + +

      Uses of ReactiveFuture in gust.backend.driver.inmemory

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.driver.inmemory that return ReactiveFuture 
      Modifier and TypeMethodDescription
      ReactiveFuture<Key>InMemoryDriver.delete​(Key key, + DeleteOptions options) +
      Low-level record delete method.
      +
      ReactiveFutureInMemoryCache.evict​(K key, + com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Force-evict any cached record at the provided key in the cache managed by this driver.
      +
      ReactiveFuture<java.util.Optional<M>>InMemoryCache.fetch​(K key, + FetchOptions options, + com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Attempt to resolve a known model, addressed by key, from the cache powered/backed by this driver, according + to options and making use of executor.
      +
      ReactiveFutureInMemoryCache.flush​(com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Flush the entire cache managed by this driver.
      +
      ReactiveFuture<Model>InMemoryDriver.persist​(Key key, + Model model, + WriteOptions options) +
      Low-level record persistence method.
      +
      ReactiveFutureInMemoryCache.put​(com.google.protobuf.Message key, + com.google.protobuf.Message model, + com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Write a record (model) at key into the cache, overwriting any model currently stored at the same + key, if applicable.
      +
      ReactiveFuture<java.util.Optional<Model>>InMemoryDriver.retrieve​(Key key, + FetchOptions options) +
      Low-level record retrieval method.
      +
      +
      +
    • +
    • +
      + + +

      Uses of ReactiveFuture in gust.backend.driver.spanner

      + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.driver.spanner that return ReactiveFuture 
      Modifier and TypeMethodDescription
      ReactiveFuture<Key>SpannerDriver.delete​(Key key, + DeleteOptions baseOptions) 
      ReactiveFuture<Model>SpannerDriver.persist​(Key key, + Model model, + WriteOptions options) 
      ReactiveFuture<java.util.Optional<Model>>SpannerDriver.retrieve​(Key key, + FetchOptions options) 
      +
      +
    • +
    • +
      + + +

      Uses of ReactiveFuture in gust.backend.model

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.model that return ReactiveFuture 
      Modifier and TypeMethodDescription
      default ReactiveFuture<Model>PersistenceDriver.create​(Key key, + Model model) +
      Create the record specified by model using the optional pre-fabricated key, in underlying storage.
      +
      default ReactiveFuture<Model>PersistenceDriver.create​(Key key, + Model model, + WriteOptions options) +
      Create the record specified by model using the optional pre-fabricated key, and making use of the + specified options, in underlying storage.
      +
      default ReactiveFuture<Model>PersistenceDriver.create​(Model model) +
      Create the record specified by model in underlying storage, provisioning a key or ID for the record if + needed.
      +
      default ReactiveFuture<Model>PersistenceDriver.create​(Model model, + WriteOptions options) +
      Create the record specified by model using the specified set of options, in underlying storage.
      +
      default ReactiveFuture<Key>ModelAdapter.delete​(Key key, + DeleteOptions options) +
      Low-level record delete method.
      +
      default ReactiveFuture<Key>PersistenceDriver.delete​(Key key) +
      Delete and fully erase the record referenced by key from underlying storage, permanently.
      +
      ReactiveFuture<Key>PersistenceDriver.delete​(Key key, + DeleteOptions options) +
      Low-level record delete method.
      +
      default ReactiveFuture<Key>PersistenceDriver.deleteRecord​(Model model) +
      Delete and fully erase the supplied model from underlying storage, permanently.
      +
      default ReactiveFuture<Key>PersistenceDriver.deleteRecord​(Model model, + DeleteOptions options) +
      Delete and fully erase the supplied model from underlying storage, permanently.
      +
      default ReactiveFutureCacheDriver.evict​(java.lang.Iterable<Key> keys, + com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Force-evict the set of cached records specified by keys, in the cache managed by this driver.
      +
      ReactiveFuture<Key>CacheDriver.evict​(Key key, + com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Force-evict any cached record at the provided key in the cache managed by this driver.
      +
      ReactiveFuture<java.util.Optional<Model>>CacheDriver.fetch​(Key key, + FetchOptions options, + com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Attempt to resolve a known model, addressed by key, from the cache powered/backed by this driver, according + to options and making use of executor.
      +
      default ReactiveFuture<java.util.Optional<Model>>PersistenceDriver.fetchAsync​(Key key) +
      Asynchronously retrieve a data model instance from storage, which will populate the provided Future value.
      +
      default ReactiveFuture<java.util.Optional<Model>>PersistenceDriver.fetchAsync​(Key key, + FetchOptions options) +
      Asynchronously retrieve a data model instance from storage, which will populate the provided Future value.
      +
      default ReactiveFuture<java.util.Optional<Model>>PersistenceDriver.fetchReactive​(Key key) +
      Reactively retrieve a data model instance from storage, emitting it over a Publisher wrapped in an + Optional.
      +
      default ReactiveFuture<java.util.Optional<Model>>PersistenceDriver.fetchReactive​(Key key, + FetchOptions options) +
      Reactively retrieve a data model instance from storage, emitting it over a Publisher wrapped in an + Optional.
      +
      ReactiveFutureCacheDriver.flush​(com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Flush the entire cache managed by this driver.
      +
      default ReactiveFuture<Model>ModelAdapter.persist​(Key key, + Model model, + WriteOptions options) +
      Low-level record persistence method.
      +
      ReactiveFuture<Model>PersistenceDriver.persist​(Key key, + Model model, + WriteOptions options) +
      Low-level record persistence method.
      +
      ReactiveFutureCacheDriver.put​(Key key, + Model model, + com.google.common.util.concurrent.ListeningScheduledExecutorService executor) +
      Write a record (model) at key into the cache, overwriting any model currently stored at the same + key, if applicable.
      +
      default ReactiveFuture<java.util.Optional<Model>>ModelAdapter.retrieve​(Key key, + FetchOptions options) +
      Low-level record retrieval method.
      +
      ReactiveFuture<java.util.Optional<Model>>PersistenceDriver.retrieve​(Key key, + FetchOptions options) +
      Low-level record retrieval method.
      +
      default ReactiveFuture<Model>PersistenceDriver.update​(Key key, + Model model) +
      Update the record specified by model, and addressed by key, in underlying storage.
      +
      default ReactiveFuture<Model>PersistenceDriver.update​(Key key, + Model model, + UpdateOptions options) +
      Update the record specified by model, and addressed by key, in underlying storage.
      +
      default ReactiveFuture<Model>PersistenceDriver.update​(Model model) +
      Update the record specified by model in underlying storage, using the existing key or ID value affixed to + the model.
      +
      default ReactiveFuture<Model>PersistenceDriver.update​(Model model, + UpdateOptions options) +
      Update the record specified by model in underlying storage, making use of the specified options, + using the existing key or ID value affixed to the model.
      +
      +
      +
    • +
    • +
      + + +

      Uses of ReactiveFuture in gust.backend.runtime

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Methods in gust.backend.runtime that return ReactiveFuture 
      Modifier and TypeMethodDescription
      static <R> ReactiveFuture<R>ReactiveFuture.cancelled() +
      Create an already-cancelled future.
      +
      static <R> ReactiveFuture<R>ReactiveFuture.done​(R value) +
      Create an already-resolved future, wrapping the provided value.
      +
      static <R> ReactiveFuture<R>ReactiveFuture.failed​(java.lang.Throwable error) +
      Create an already-failed future, wrapping the provided exception instance.
      +
      static <R> ReactiveFuture<R>ReactiveFuture.wrap​(com.google.api.core.ApiFuture<R> apiFuture) +
      Wrap a Google APIs ApiFuture in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value.
      +
      static <R> ReactiveFuture<R>ReactiveFuture.wrap​(com.google.api.core.ApiFuture<R> apiFuture, + java.util.concurrent.Executor executor) +
      Wrap a Google APIs ApiFuture in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value.
      +
      static <R> ReactiveFuture<R>ReactiveFuture.wrap​(com.google.common.util.concurrent.ListenableFuture<R> future) +
      Wrap a Guava ListenableFuture in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value.
      +
      static <R> ReactiveFuture<R>ReactiveFuture.wrap​(com.google.common.util.concurrent.ListenableFuture<R> future, + java.util.concurrent.Executor executor) +
      Wrap a Guava ListenableFuture in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value.
      +
      static <R> ReactiveFuture<R>ReactiveFuture.wrap​(java.util.concurrent.CompletableFuture<R> future) +
      Wrap a regular Java CompletableFuture in a universal ReactiveFuture, such that it may be used with + any interface requiring support for that class.
      +
      static <R> ReactiveFuture<R>ReactiveFuture.wrap​(java.util.concurrent.CompletableFuture<R> future, + java.util.concurrent.Executor executor) +
      Wrap a regular Java CompletableFuture in a universal ReactiveFuture, such that it may be used with + any interface requiring support for that class.
      +
      static <R> ReactiveFuture<R>ReactiveFuture.wrap​(org.reactivestreams.Publisher<R> publisher) +
      Wrap a Reactive Java Publisher in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/package-summary.html b/docs/java/gust/backend/runtime/package-summary.html new file mode 100644 index 000000000..5d4fbf229 --- /dev/null +++ b/docs/java/gust/backend/runtime/package-summary.html @@ -0,0 +1,235 @@ + + + + + +gust.backend.runtime + + + + + + + + + + + + + + +
+ +
+
+
+

Package gust.backend.runtime

+
+
+
+ + +
Supplies core backend runtime logic, like execution/scheduling, logging, and so on.
+
+
    +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Class Summary 
    ClassDescription
    AssetManager +
    Manager class, which mediates interactions with the binary asset bundle.
    +
    AssetManager.ManagedAsset<M extends com.google.protobuf.Message> +
    Public API surface for interacting with raw asset metadata.
    +
    AssetManager.ManagedAssetContent +
    Public API surface for interacting with raw asset content.
    +
    Logging +
    Sugar bridge to SLF4J.
    +
    ReactiveFuture<R> +
    Adapts future/async value containers from different frameworks (namely, Reactive Java, Guava, and the JDK).
    +
    ReactiveFuture.CompletableFuturePublisher<T> +
    Structure that adapts Java's CompletableFuture to a Reactive Java Publisher, which publishes one + item - either the result of the computation, or an error.
    +
    ReactiveFuture.ListenableFuturePublisher<T> +
    Structure that adapts Guava's ListenableFuture to a Reactive Java Publisher, which publishes one + item - either the result of the computation, or an error.
    +
    ReactiveFuture.PublisherListenableFuture<T> +
    Structure that adapts a Publisher to a ListenableFuture interface.
    +
    +
  • +
  • + + + + + + + + + + + + +
    Enum Summary 
    EnumDescription
    AssetManager.ModuleType +
    Enumerates types of asset modules.
    +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/package-tree.html b/docs/java/gust/backend/runtime/package-tree.html new file mode 100644 index 000000000..d5f6591f4 --- /dev/null +++ b/docs/java/gust/backend/runtime/package-tree.html @@ -0,0 +1,186 @@ + + + + + +gust.backend.runtime Class Hierarchy + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package gust.backend.runtime

+Package Hierarchies: + +
+
+
+

Class Hierarchy

+ +
+
+

Enum Hierarchy

+
    +
  • java.lang.Object +
      +
    • java.lang.Enum<E> (implements java.lang.Comparable<T>, java.io.Serializable) + +
    • +
    +
  • +
+
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/runtime/package-use.html b/docs/java/gust/backend/runtime/package-use.html new file mode 100644 index 000000000..3f8679aee --- /dev/null +++ b/docs/java/gust/backend/runtime/package-use.html @@ -0,0 +1,311 @@ + + + + + +Uses of Package gust.backend.runtime + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Package
gust.backend.runtime

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/GoogleAPIChannel.html b/docs/java/gust/backend/transport/GoogleAPIChannel.html new file mode 100644 index 000000000..bdf83e8e9 --- /dev/null +++ b/docs/java/gust/backend/transport/GoogleAPIChannel.html @@ -0,0 +1,272 @@ + + + + + +GoogleAPIChannel + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Annotation Type GoogleAPIChannel

+
+
+
+
    +
  • +
    +
    @Bean
    +@Documented
    +@Introduction
    +@Target(PARAMETER)
    +@Retention(RUNTIME)
    +@Type(GoogleTransportManager.class)
    +public @interface GoogleAPIChannel
    +
    Specifies an injection qualifier for a managed transport supporting the service specified by this annotation. + +

    To use this annotation, decorate a ManagedChannel parameter with @GoogleAPIChannel, + and pass in the enumerated service you wish to receive a connection for. The connection will be initialized and kept + in a pool according to current settings and load.

    + +

    For more information about how connections are managed and integrated with Micronaut, + see GoogleTransportManager.

    +
    +
    See Also:
    +
    GoogleTransportManager
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Required Element Summary

      + + + + + + + + + + + + +
      Required Elements 
      Modifier and TypeRequired ElementDescription
      GoogleServiceservice +
      Service for which this annotation is requesting a ManagedChannel instance.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Element Detail

      + + + +
        +
      • +

        service

        +
        @Nonnull
        +GoogleService service
        +
        Service for which this annotation is requesting a ManagedChannel instance. + +

        If no connection to the requested service exists, one will be initialized before being handed back to the user. + Otherwise, the user may get an existing singleton connection instance, or a connection instance from a pool of + connections, depending on the implementing TransportManager (usually GoogleTransportManager).

        +
        +
        Returns:
        +
        Service to return a managed connection for.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/GoogleService.html b/docs/java/gust/backend/transport/GoogleService.html new file mode 100644 index 000000000..af3352129 --- /dev/null +++ b/docs/java/gust/backend/transport/GoogleService.html @@ -0,0 +1,460 @@ + + + + + +GoogleService + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Enum GoogleService

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Enum<GoogleService>
    • +
    • +
        +
      • gust.backend.transport.GoogleService
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Comparable<GoogleService>
    +
    +
    +
    public enum GoogleService
    +extends java.lang.Enum<GoogleService>
    +
    Enumerates Google Cloud services with built-in managed transport support.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      FIRESTORE +
      Google Cloud Firestore (token:
      +
      PUBSUB +
      Google Cloud Pub-Sub (token:
      +
      SPANNER +
      Google Cloud Firestore (token:
      +
      STORAGE +
      Google Cloud Storage (token:
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      java.util.Optional<java.lang.Class<GoogleTransportConfig>>getConfigType() 
      java.lang.StringgetToken() 
      static GoogleServicevalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static GoogleService[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static GoogleService[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (GoogleService c : GoogleService.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static GoogleService valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      + + + +
        +
      • +

        getToken

        +
        @Nonnull
        +public java.lang.String getToken()
        +
        +
        Returns:
        +
        Prefix at which the specified service can be configured.
        +
        +
      • +
      + + + +
        +
      • +

        getConfigType

        +
        @Nonnull
        +public java.util.Optional<java.lang.Class<GoogleTransportConfig>> getConfigType()
        +
        +
        Returns:
        +
        Configuration bindings class for the provided service, if supported.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/backend/transport/GoogleTransportConfig.html b/docs/java/gust/backend/transport/GoogleTransportConfig.html new file mode 100644 index 000000000..f6ecd9ebe --- /dev/null +++ b/docs/java/gust/backend/transport/GoogleTransportConfig.html @@ -0,0 +1,349 @@ + + + + + +GoogleTransportConfig + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface GoogleTransportConfig

+
+
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + + + + + +
        +
      • +

        credentialsProvider

        +
        @Nonnull
        +default java.util.Optional<com.google.api.gax.core.CredentialsProvider> credentialsProvider​(@Nonnull
        +                                                                                            java.util.Optional<java.util.List<java.lang.String>> scopes)
        +
        Resolve a credentials provider bound to the specified auth requirements.
        +
        +
        Specified by:
        +
        credentialsProvider in interface GrpcTransportCredentials
        +
        Parameters:
        +
        scopes - Authorization scopes to request.
        +
        Returns:
        +
        Credentials provider that should be active for managed RPC channel calls. By default, for configurations + that inherit from GoogleTransportConfig, this will read and use Application Default Credentials.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/GoogleTransportManager.html b/docs/java/gust/backend/transport/GoogleTransportManager.html new file mode 100644 index 000000000..8bd7177d0 --- /dev/null +++ b/docs/java/gust/backend/transport/GoogleTransportManager.html @@ -0,0 +1,458 @@ + + + + + +GoogleTransportManager + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class GoogleTransportManager

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.backend.transport.GoogleTransportManager
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    TransportManager<GoogleAPIChannel,​GoogleService,​io.grpc.Channel>, io.micronaut.aop.Interceptor<java.lang.Object,​io.grpc.Channel>, io.micronaut.aop.MethodInterceptor<java.lang.Object,​io.grpc.Channel>, io.micronaut.core.order.Ordered
    +
    +
    +
    @Context
    +@Singleton
    +@Immutable
    +@ThreadSafe
    +@Refreshable
    +@ConfigurationProperties("transport.google")
    +public final class GoogleTransportManager
    +extends java.lang.Object
    +implements TransportManager<GoogleAPIChannel,​GoogleService,​io.grpc.Channel>
    +
    Supplies a TransportManager implementation for dealing with Google Cloud APIs via gRPC and Protobuf. In this + object, we make heavy use of the Google API Extensions for Java ("GAX"), in order to centrally manage channels, on- + demand, for downstream service use. + +

    Connection instances may be requested from this object like any other transport manager, but if there is an + existing managed channel for the provided service ID, it will provide the existing instance (or one from a pool of + existing instances) rather than creating a new one.

    + +

    To request a connection instance from this transport manager, annotate an injectable method parameter (or + constructor parameter) of type Channel with GoogleAPIChannel. For example: + + public void doSomething(@GoogleAPIChannel(Service.PUBSUB) Channel pubsubChannel) { + // ... + } +

    + +

    Configuration: Each Google service has a specified name, included on the docs in + GoogleService, at which that service may be configured in

    application.yml
    . For example, the + following code configures the Cloud Pubsub client's keepalive and pool size settings: + + transport: + google: + pubsub: + poolSize: 3 + keepAliveTime: 15s + keepAliveTimeout: 30s + keepAliveWithoutCalls: true +

    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + +
      Fields 
      Modifier and TypeFieldDescription
      static java.lang.StringCONFIG_PREFIX +
      Prefix under which Google services may be configured.
      +
      +
        +
      • + + +

        Fields inherited from interface io.micronaut.aop.Interceptor

        +HOTSWAP, LAZY, PROXY_TARGET
      • +
      +
        +
      • + + +

        Fields inherited from interface io.micronaut.core.order.Ordered

        +HIGHEST_PRECEDENCE, LOWEST_PRECEDENCE
      • +
      + +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      io.grpc.Channelacquire​(GoogleService type) +
      Acquire a connection from this transport manager.
      +
      intgetMaxPoolSize() 
      io.grpc.Channelintercept​(io.micronaut.aop.MethodInvocationContext<java.lang.Object,​io.grpc.Channel> context) +
      Provide acquired connections via injection-annotated Channel method or constructor parameters.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
        +
      • + + +

        Methods inherited from interface io.micronaut.aop.MethodInterceptor

        +intercept
      • +
      +
        +
      • + + +

        Methods inherited from interface io.micronaut.core.order.Ordered

        +getOrder
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getMaxPoolSize

        +
        public int getMaxPoolSize()
        +
        +
        Returns:
        +
        Maximum size of any one service connection pool.
        +
        +
      • +
      + + + +
        +
      • +

        acquire

        +
        @Nonnull
        +public io.grpc.Channel acquire​(@Nonnull
        +                               GoogleService type)
        +                        throws TransportException
        +
        Acquire a connection from this transport manager. The connection provided may or may not be freshly-created, + depending on the underlying implementation, but it should never be
        null
        (exceptions are raised instead).
        +
        +
        Specified by:
        +
        acquire in interface TransportManager<GoogleAPIChannel,​GoogleService,​io.grpc.Channel>
        +
        Parameters:
        +
        type - Type of connection to acquire. Defined by the implementation.
        +
        Returns:
        +
        Connection instance for the desired service, potentially fresh, potentially re-used.
        +
        Throws:
        +
        TransportException - If the connection could not be acquired.
        +
        +
      • +
      + + + +
        +
      • +

        intercept

        +
        public io.grpc.Channel intercept​(io.micronaut.aop.MethodInvocationContext<java.lang.Object,​io.grpc.Channel> context)
        +
        Provide acquired connections via injection-annotated Channel method or constructor parameters. This + essentially proxies to acquire(GoogleService), passing in the service specified in the context of the + GoogleAPIChannel annotation.
        +
        +
        Specified by:
        +
        intercept in interface io.micronaut.aop.MethodInterceptor<java.lang.Object,​io.grpc.Channel>
        +
        Parameters:
        +
        context - Intercepted method execution context.
        +
        Returns:
        +
        Resulting object.
        +
        Throws:
        +
        TransportException - If the connection could not be acquired.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/GrpcTransportConfig.html b/docs/java/gust/backend/transport/GrpcTransportConfig.html new file mode 100644 index 000000000..4aba1c1cb --- /dev/null +++ b/docs/java/gust/backend/transport/GrpcTransportConfig.html @@ -0,0 +1,481 @@ + + + + + +GrpcTransportConfig + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface GrpcTransportConfig

+
+
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        DEFAULT_MAX_INBOUND_MESSAGE_SIZE

        +
        static final java.lang.Integer DEFAULT_MAX_INBOUND_MESSAGE_SIZE
        +
        Default maximum inbound message size, if no other value is specified.
        +
      • +
      + + + +
        +
      • +

        DEFAULT_MAX_INBOUND_METADATA_SIZE

        +
        static final java.lang.Integer DEFAULT_MAX_INBOUND_METADATA_SIZE
        +
        Default maximum inbound metadata size, if no other value is specified.
        +
      • +
      + + + +
        +
      • +

        DEFAULT_PRIME_CONNECTIONS

        +
        static final java.lang.Boolean DEFAULT_PRIME_CONNECTIONS
        +
        Whether to prime managed gRPC connections by default.
        +
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        endpoint

        +
        @Nonnull
        +java.lang.String endpoint()
        +
        +
        Returns:
        +
        gRPC endpoint at which to connect to the target service.
        +
        +
      • +
      + + + +
        +
      • +

        getKeepAliveNoActivity

        +
        @Nonnull
        +java.lang.Boolean getKeepAliveNoActivity()
        +
        +
        Returns:
        +
        Whether to keep-alive even when there is no activity.
        +
        +
      • +
      + + + +
        +
      • +

        maxInboundMessageSize

        +
        @Nonnull
        +default java.lang.Integer maxInboundMessageSize()
        +
        +
        Returns:
        +
        Max inbound message size, in bytes.
        +
        +
      • +
      + + + +
        +
      • +

        maxInboundMetadataSize

        +
        @Nonnull
        +default java.lang.Integer maxInboundMetadataSize()
        +
        +
        Returns:
        +
        Max inbound metadata stanza size, in bytes.
        +
        +
      • +
      + + + +
        +
      • +

        getExtraInterceptors

        +
        @Nonnull
        +default java.util.Optional<java.util.List<io.grpc.ClientInterceptor>> getExtraInterceptors()
        +
        +
        Returns:
        +
        Additional client-side call interceptors to install.
        +
        +
      • +
      + + + +
        +
      • +

        enablePrimer

        +
        @Nonnull
        +default java.lang.Boolean enablePrimer()
        +
        +
        Returns:
        +
        Whether to prime managed gRPC connections.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/GrpcTransportCredentials.html b/docs/java/gust/backend/transport/GrpcTransportCredentials.html new file mode 100644 index 000000000..a67483c43 --- /dev/null +++ b/docs/java/gust/backend/transport/GrpcTransportCredentials.html @@ -0,0 +1,299 @@ + + + + + +GrpcTransportCredentials + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface GrpcTransportCredentials

+
+
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        credentialsProvider

        +
        @Nonnull
        +default java.util.Optional<com.google.api.gax.core.CredentialsProvider> credentialsProvider()
        +
        +
        Returns:
        +
        Credentials provider that should be active for managed RPC channel calls, with an empty set of scopes.
        +
        +
      • +
      + + + +
        +
      • +

        credentialsProvider

        +
        @Nonnull
        +default java.util.Optional<com.google.api.gax.core.CredentialsProvider> credentialsProvider​(@Nonnull
        +                                                                                            java.util.Optional<java.util.List<java.lang.String>> scopes)
        +
        +
        Returns:
        +
        Credentials provider that should be active for managed RPC channel calls.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/PooledTransportConfig.html b/docs/java/gust/backend/transport/PooledTransportConfig.html new file mode 100644 index 000000000..9176f393a --- /dev/null +++ b/docs/java/gust/backend/transport/PooledTransportConfig.html @@ -0,0 +1,278 @@ + + + + + +PooledTransportConfig + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface PooledTransportConfig

+
+
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getPoolSize

        +
        @Nonnull
        +java.lang.Integer getPoolSize()
        +
        +
        Returns:
        +
        Retrieve the desired connection pool size.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/TransportConfig.html b/docs/java/gust/backend/transport/TransportConfig.html new file mode 100644 index 000000000..8125a08d0 --- /dev/null +++ b/docs/java/gust/backend/transport/TransportConfig.html @@ -0,0 +1,307 @@ + + + + + +TransportConfig + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface TransportConfig

+
+
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getKeepaliveEnabled

        +
        @Nonnull
        +java.lang.Boolean getKeepaliveEnabled()
        +
        +
        Returns:
        +
        Whether to enable keepalive features.
        +
        +
      • +
      + + + +
        +
      • +

        getKeepaliveTime

        +
        @Nonnull
        +java.time.Duration getKeepaliveTime()
        +
        +
        Returns:
        +
        Keep-alive time.
        +
        +
      • +
      + + + +
        +
      • +

        getKeepaliveTimeout

        +
        @Nonnull
        +java.time.Duration getKeepaliveTimeout()
        +
        +
        Returns:
        +
        Keep-alive timeout.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/TransportCredentials.html b/docs/java/gust/backend/transport/TransportCredentials.html new file mode 100644 index 000000000..89db581e3 --- /dev/null +++ b/docs/java/gust/backend/transport/TransportCredentials.html @@ -0,0 +1,267 @@ + + + + + +TransportCredentials + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface TransportCredentials

+
+
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        requiresCredentials

        +
        @Nonnull
        +default java.lang.Boolean requiresCredentials()
        +
        +
        Returns:
        +
        Whether a transport requires credentials. This defaults to false.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/TransportException.html b/docs/java/gust/backend/transport/TransportException.html new file mode 100644 index 000000000..61d0a223b --- /dev/null +++ b/docs/java/gust/backend/transport/TransportException.html @@ -0,0 +1,255 @@ + + + + + +TransportException + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class TransportException

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Throwable
    • +
    • +
        +
      • java.lang.Exception
      • +
      • +
          +
        • java.lang.RuntimeException
        • +
        • +
            +
          • gust.backend.transport.TransportException
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable
    +
    +
    +
    public abstract class TransportException
    +extends java.lang.RuntimeException
    +
    Defines an error case that was encountered while dealing with managed transport logic. This could include connection + acquisition, name resolution failures, and so on.
    +
    +
    See Also:
    +
    Serialized Form
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/TransportManager.html b/docs/java/gust/backend/transport/TransportManager.html new file mode 100644 index 000000000..abb35d1ea --- /dev/null +++ b/docs/java/gust/backend/transport/TransportManager.html @@ -0,0 +1,367 @@ + + + + + +TransportManager + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface TransportManager<A extends java.lang.annotation.Annotation,​E extends java.lang.Enum<E>,​C>

+
+
+
+
    +
  • +
    +
    Type Parameters:
    +
    A - Annotation qualifier type, which is responsible for marking types that need injection from a given + implementing manager.
    +
    E - Enumerated connection type, or service type. An instance of this enumerated type is required when + acquiring a connection.
    +
    C - Connection implementation. Should match the object that a given manager hands back when a connection is + acquired for use.
    +
    +
    +
    All Superinterfaces:
    +
    io.micronaut.aop.Interceptor<java.lang.Object,​C>, io.micronaut.aop.MethodInterceptor<java.lang.Object,​C>, io.micronaut.core.order.Ordered
    +
    +
    +
    All Known Implementing Classes:
    +
    GoogleTransportManager
    +
    +
    +
    public interface TransportManager<A extends java.lang.annotation.Annotation,​E extends java.lang.Enum<E>,​C>
    +extends io.micronaut.aop.MethodInterceptor<java.lang.Object,​C>
    +
    Defines the interface by which "transport manager" objects must comply. These objects are used to construct and + manage connections to other machines or systems, usually via higher-order service layers like gRPC.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + +
      Fields 
      Modifier and TypeFieldDescription
      static java.lang.StringROOT_CONFIG_PREFIX +
      Config prefix under which transport settings are specified.
      +
      +
        +
      • + + +

        Fields inherited from interface io.micronaut.aop.Interceptor

        +HOTSWAP, LAZY, PROXY_TARGET
      • +
      +
        +
      • + + +

        Fields inherited from interface io.micronaut.core.order.Ordered

        +HIGHEST_PRECEDENCE, LOWEST_PRECEDENCE
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods 
      Modifier and TypeMethodDescription
      Cacquire​(E type) +
      Acquire a connection from this transport manager.
      +
      +
        +
      • + + +

        Methods inherited from interface io.micronaut.aop.MethodInterceptor

        +intercept, intercept
      • +
      +
        +
      • + + +

        Methods inherited from interface io.micronaut.core.order.Ordered

        +getOrder
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + + + +
        +
      • +

        acquire

        +
        @Nonnull
        +C acquire​(@Nonnull
        +          E type)
        +   throws TransportException
        +
        Acquire a connection from this transport manager. The connection provided may or may not be freshly-created, + depending on the underlying implementation, but it should never be
        null
        (exceptions are raised instead).
        +
        +
        Parameters:
        +
        type - Type of connection to acquire. Defined by the implementation.
        +
        Returns:
        +
        Connection instance.
        +
        Throws:
        +
        TransportException - If the connection could not be acquired.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/class-use/GoogleAPIChannel.html b/docs/java/gust/backend/transport/class-use/GoogleAPIChannel.html new file mode 100644 index 000000000..2f2d8c596 --- /dev/null +++ b/docs/java/gust/backend/transport/class-use/GoogleAPIChannel.html @@ -0,0 +1,244 @@ + + + + + +Uses of Class gust.backend.transport.GoogleAPIChannel + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.transport.GoogleAPIChannel

+
+
+
    +
  • + + + + + + + + + + + + + + + + +
    Packages that use GoogleAPIChannel 
    PackageDescription
    gust.backend.driver.firestore +
    Provides a DatabaseDriver implementation, using Google Cloud Firestore.
    +
    gust.backend.driver.spanner +
    Provides a DatabaseDriver implementation for integration with Google Cloud Spanner.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of GoogleAPIChannel in gust.backend.driver.firestore

      + + + + + + + + + + + + + + +
      Method parameters in gust.backend.driver.firestore with annotations of type GoogleAPIChannel 
      Modifier and TypeMethodDescription
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      FirestoreAdapter<K,​M>
      FirestoreAdapter.acquire​(com.google.cloud.firestore.FirestoreOptions.Builder baseOptions, + com.google.api.gax.rpc.TransportChannelProvider firestoreChannel, + com.google.api.gax.core.CredentialsProvider credentialsProvider, + com.google.cloud.grpc.GrpcTransportOptions transportOptions, + com.google.common.util.concurrent.ListeningScheduledExecutorService executorService, + K keyInstance, + M messageInstance) +
      Acquire an instance of the FirestoreAdapter and FirestoreDriver, customized for the provided + `modelInstance` and `keyInstance`.
      +
      +
      +
    • +
    • +
      + + +

      Uses of GoogleAPIChannel in gust.backend.driver.spanner

      + + + + + + + + + + + + + + +
      Method parameters in gust.backend.driver.spanner with annotations of type GoogleAPIChannel 
      Modifier and TypeMethodDescription
      static <K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message>
      SpannerAdapter<K,​M>
      SpannerAdapter.acquire​(com.google.cloud.spanner.SpannerOptions.Builder baseOptions, + com.google.cloud.spanner.DatabaseId defaultDatabase, + com.google.api.gax.rpc.TransportChannelProvider spannerChannel, + java.util.Optional<com.google.api.gax.core.CredentialsProvider> credentialsProvider, + java.util.Optional<com.google.cloud.spanner.SpannerOptions.CallCredentialsProvider> callCredentialProvider, + com.google.cloud.grpc.GrpcTransportOptions transportOptions, + com.google.common.util.concurrent.ListeningScheduledExecutorService executorService, + K keyInstance, + M messageInstance, + SpannerDriverSettings driverSettings, + java.util.Optional<CacheDriver<K,​M>> cacheDriver) +
      Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/class-use/GoogleService.html b/docs/java/gust/backend/transport/class-use/GoogleService.html new file mode 100644 index 000000000..bc3314368 --- /dev/null +++ b/docs/java/gust/backend/transport/class-use/GoogleService.html @@ -0,0 +1,228 @@ + + + + + +Uses of Class gust.backend.transport.GoogleService + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.transport.GoogleService

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/class-use/GoogleTransportConfig.html b/docs/java/gust/backend/transport/class-use/GoogleTransportConfig.html new file mode 100644 index 000000000..c69169658 --- /dev/null +++ b/docs/java/gust/backend/transport/class-use/GoogleTransportConfig.html @@ -0,0 +1,194 @@ + + + + + +Uses of Interface gust.backend.transport.GoogleTransportConfig + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.transport.GoogleTransportConfig

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/class-use/GoogleTransportManager.html b/docs/java/gust/backend/transport/class-use/GoogleTransportManager.html new file mode 100644 index 000000000..f7f294326 --- /dev/null +++ b/docs/java/gust/backend/transport/class-use/GoogleTransportManager.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.backend.transport.GoogleTransportManager + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.transport.GoogleTransportManager

+
+
No usage of gust.backend.transport.GoogleTransportManager
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/class-use/GrpcTransportConfig.html b/docs/java/gust/backend/transport/class-use/GrpcTransportConfig.html new file mode 100644 index 000000000..a5bde095d --- /dev/null +++ b/docs/java/gust/backend/transport/class-use/GrpcTransportConfig.html @@ -0,0 +1,227 @@ + + + + + +Uses of Interface gust.backend.transport.GrpcTransportConfig + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.transport.GrpcTransportConfig

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/class-use/GrpcTransportCredentials.html b/docs/java/gust/backend/transport/class-use/GrpcTransportCredentials.html new file mode 100644 index 000000000..cfbbb24ee --- /dev/null +++ b/docs/java/gust/backend/transport/class-use/GrpcTransportCredentials.html @@ -0,0 +1,234 @@ + + + + + +Uses of Interface gust.backend.transport.GrpcTransportCredentials + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.transport.GrpcTransportCredentials

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/class-use/PooledTransportConfig.html b/docs/java/gust/backend/transport/class-use/PooledTransportConfig.html new file mode 100644 index 000000000..af69f1018 --- /dev/null +++ b/docs/java/gust/backend/transport/class-use/PooledTransportConfig.html @@ -0,0 +1,234 @@ + + + + + +Uses of Interface gust.backend.transport.PooledTransportConfig + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.transport.PooledTransportConfig

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/class-use/TransportConfig.html b/docs/java/gust/backend/transport/class-use/TransportConfig.html new file mode 100644 index 000000000..d8f412db3 --- /dev/null +++ b/docs/java/gust/backend/transport/class-use/TransportConfig.html @@ -0,0 +1,241 @@ + + + + + +Uses of Interface gust.backend.transport.TransportConfig + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.transport.TransportConfig

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/class-use/TransportCredentials.html b/docs/java/gust/backend/transport/class-use/TransportCredentials.html new file mode 100644 index 000000000..7fdcaf13a --- /dev/null +++ b/docs/java/gust/backend/transport/class-use/TransportCredentials.html @@ -0,0 +1,241 @@ + + + + + +Uses of Interface gust.backend.transport.TransportCredentials + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.transport.TransportCredentials

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/class-use/TransportException.html b/docs/java/gust/backend/transport/class-use/TransportException.html new file mode 100644 index 000000000..e1a79f5ef --- /dev/null +++ b/docs/java/gust/backend/transport/class-use/TransportException.html @@ -0,0 +1,203 @@ + + + + + +Uses of Class gust.backend.transport.TransportException + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.backend.transport.TransportException

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/class-use/TransportManager.html b/docs/java/gust/backend/transport/class-use/TransportManager.html new file mode 100644 index 000000000..23161108a --- /dev/null +++ b/docs/java/gust/backend/transport/class-use/TransportManager.html @@ -0,0 +1,196 @@ + + + + + +Uses of Interface gust.backend.transport.TransportManager + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.backend.transport.TransportManager

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/package-summary.html b/docs/java/gust/backend/transport/package-summary.html new file mode 100644 index 000000000..4f19a0c85 --- /dev/null +++ b/docs/java/gust/backend/transport/package-summary.html @@ -0,0 +1,279 @@ + + + + + +gust.backend.transport + + + + + + + + + + + + + + +
+ +
+
+
+

Package gust.backend.transport

+
+
+
+ + +
Provides tools for managing data in motion between computers.
+
+
    +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Interface Summary 
    InterfaceDescription
    GoogleTransportConfig +
    Provides sensible defaults and additional configuration when applying transport settings specifically to Google- + provided or hosted services.
    +
    GrpcTransportConfig +
    Specifies transport configuration properties specific to gRPC ManagedChannel objects.
    +
    GrpcTransportCredentials +
    Transport-layer credentials configuration for use with managed gRPC connections.
    +
    PooledTransportConfig +
    Specifies configuration properties related to pooling of managed transport connections.
    +
    TransportConfig +
    Specifies base configuration properties generally supported by all transport configurations.
    +
    TransportCredentials +
    Specifies the generic notion of "transport credentials," as configuration or logic.
    +
    TransportManager<A extends java.lang.annotation.Annotation,​E extends java.lang.Enum<E>,​C> +
    Defines the interface by which "transport manager" objects must comply.
    +
    +
  • +
  • + + + + + + + + + + + + +
    Class Summary 
    ClassDescription
    GoogleTransportManager +
    Supplies a TransportManager implementation for dealing with Google Cloud APIs via gRPC and Protobuf.
    +
    +
  • +
  • + + + + + + + + + + + + +
    Enum Summary 
    EnumDescription
    GoogleService +
    Enumerates Google Cloud services with built-in managed transport support.
    +
    +
  • +
  • + + + + + + + + + + + + +
    Exception Summary 
    ExceptionDescription
    TransportException +
    Defines an error case that was encountered while dealing with managed transport logic.
    +
    +
  • +
  • + + + + + + + + + + + + +
    Annotation Types Summary 
    Annotation TypeDescription
    GoogleAPIChannel +
    Specifies an injection qualifier for a managed transport supporting the service specified by this annotation.
    +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/package-tree.html b/docs/java/gust/backend/transport/package-tree.html new file mode 100644 index 000000000..efa43eb25 --- /dev/null +++ b/docs/java/gust/backend/transport/package-tree.html @@ -0,0 +1,240 @@ + + + + + +gust.backend.transport Class Hierarchy + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package gust.backend.transport

+Package Hierarchies: + +
+
+
+

Class Hierarchy

+
    +
  • java.lang.Object +
      +
    • gust.backend.transport.GoogleTransportManager (implements gust.backend.transport.TransportManager<A,​E,​C>)
    • +
    • java.lang.Throwable (implements java.io.Serializable) +
        +
      • java.lang.Exception + +
      • +
      +
    • +
    +
  • +
+
+
+

Interface Hierarchy

+ +
+
+

Annotation Type Hierarchy

+
    +
  • gust.backend.transport.GoogleAPIChannel (implements java.lang.annotation.Annotation)
  • +
+
+
+

Enum Hierarchy

+
    +
  • java.lang.Object +
      +
    • java.lang.Enum<E> (implements java.lang.Comparable<T>, java.io.Serializable) + +
    • +
    +
  • +
+
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/backend/transport/package-use.html b/docs/java/gust/backend/transport/package-use.html new file mode 100644 index 000000000..7339ad366 --- /dev/null +++ b/docs/java/gust/backend/transport/package-use.html @@ -0,0 +1,316 @@ + + + + + +Uses of Package gust.backend.transport + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Package
gust.backend.transport

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/class-use/Core.html b/docs/java/gust/class-use/Core.html new file mode 100644 index 000000000..16fc07637 --- /dev/null +++ b/docs/java/gust/class-use/Core.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.Core + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.Core

+
+
No usage of gust.Core
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/package-summary.html b/docs/java/gust/package-summary.html new file mode 100644 index 000000000..ebbffc281 --- /dev/null +++ b/docs/java/gust/package-summary.html @@ -0,0 +1,175 @@ + + + + + +gust + + + + + + + + + + + + + + +
+ +
+
+
+

Package gust

+
+
+
+ + +
Provides logic and framework implementation structure for Gust.
+
+
    +
  • + + + + + + + + + + + + +
    Class Summary 
    ClassDescription
    Core +
    Provides core values, utility methods, etc, which can be used throughout the back- and front-end of a Gust-based + application.
    +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/package-tree.html b/docs/java/gust/package-tree.html new file mode 100644 index 000000000..69f479daa --- /dev/null +++ b/docs/java/gust/package-tree.html @@ -0,0 +1,163 @@ + + + + + +gust Class Hierarchy + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package gust

+Package Hierarchies: + +
+
+
+

Class Hierarchy

+
    +
  • java.lang.Object + +
  • +
+
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/package-use.html b/docs/java/gust/package-use.html new file mode 100644 index 000000000..62f5e7168 --- /dev/null +++ b/docs/java/gust/package-use.html @@ -0,0 +1,148 @@ + + + + + +Uses of Package gust + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Package
gust

+
+
No usage of gust
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/Hex.html b/docs/java/gust/util/Hex.html new file mode 100644 index 000000000..a3699e8e8 --- /dev/null +++ b/docs/java/gust/util/Hex.html @@ -0,0 +1,306 @@ + + + + + +Hex + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Class Hex

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.util.Hex
    • +
    +
  • +
+
+
    +
  • +
    +
    public final class Hex
    +extends java.lang.Object
    +
    Provides utilities for encoding values into hex, or decoding values from hex.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static java.lang.StringbytesToHex​(byte[] bytes) +
      Convert a byte array to hex.
      +
      static java.lang.StringbytesToHex​(byte[] bytes, + int maxChars) +
      Convert a byte array to hex, optionally limiting the number of characters returned and cycles performed.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        bytesToHex

        +
        @Nonnull
        +public static java.lang.String bytesToHex​(byte[] bytes)
        +
        Convert a byte array to hex.
        +
        +
        Parameters:
        +
        bytes - Raw bytes to encode.
        +
        Returns:
        +
        Resulting hex-encoded string.
        +
        +
      • +
      + + + +
        +
      • +

        bytesToHex

        +
        @Nonnull
        +public static java.lang.String bytesToHex​(byte[] bytes,
        +                                          int maxChars)
        +
        Convert a byte array to hex, optionally limiting the number of characters returned and cycles performed.
        +
        +
        Parameters:
        +
        bytes - Raw bytes to encode.
        +
        maxChars - Max number of output characters to care about. Pass `-1` to encode the whole thing.
        +
        Returns:
        +
        Resulting hex-encoded string.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/InstantFactory.html b/docs/java/gust/util/InstantFactory.html new file mode 100644 index 000000000..9ebe6172d --- /dev/null +++ b/docs/java/gust/util/InstantFactory.html @@ -0,0 +1,320 @@ + + + + + +InstantFactory + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Class InstantFactory

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.util.InstantFactory
    • +
    +
  • +
+
+
    +
  • +
    +
    public final class InstantFactory
    +extends java.lang.Object
    +
    Utilities to convert between different temporal instant records.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + +
      Constructors 
      ConstructorDescription
      InstantFactory() 
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static java.time.Instantinstant​(com.google.protobuf.Timestamp subject) +
      Convert a Protocol Buffers Timestamp record to a Java Instant record.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        instant

        +
        @Nonnull
        +public static java.time.Instant instant​(@Nonnull
        +                                        com.google.protobuf.Timestamp subject)
        +
        Convert a Protocol Buffers Timestamp record to a Java Instant record.
        +
        +
        Parameters:
        +
        subject - Subject timestamp to convert.
        +
        Returns:
        +
        Converted Java Instant.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/MessageDifferencer.Builder.html b/docs/java/gust/util/MessageDifferencer.Builder.html new file mode 100644 index 000000000..3b08749d4 --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.Builder.html @@ -0,0 +1,538 @@ + + + + + +MessageDifferencer.Builder + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Class MessageDifferencer.Builder

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.util.MessageDifferencer.Builder
    • +
    +
  • +
+
+ +
+
+ +
+
+ +
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/MessageDifferencer.DefaultFieldComparator.html b/docs/java/gust/util/MessageDifferencer.DefaultFieldComparator.html new file mode 100644 index 000000000..2c6983452 --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.DefaultFieldComparator.html @@ -0,0 +1,367 @@ + + + + + +MessageDifferencer.DefaultFieldComparator + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Class MessageDifferencer.DefaultFieldComparator

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.util.MessageDifferencer.DefaultFieldComparator
    • +
    +
  • +
+
+ +
+
+ +
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        compare

        +
        public MessageDifferencer.FieldComparator.ComparisonResult compare​(com.google.protobuf.Message message1,
        +                                                                   com.google.protobuf.Message message2,
        +                                                                   com.google.protobuf.Descriptors.FieldDescriptor field,
        +                                                                   int index1,
        +                                                                   int index2,
        +                                                                   com.google.common.collect.ImmutableList<MessageDifferencer.SpecificField> parentFields)
        +
        Description copied from interface: MessageDifferencer.FieldComparator
        +
        Compares the values of a field in two protocol buffer messages.
        +
        +
        Specified by:
        +
        compare in interface MessageDifferencer.FieldComparator
        +
        Parameters:
        +
        message1 - the first message.
        +
        message2 - the second message.
        +
        field - field descriptor of the field where need to be compared.
        +
        index1 - the index of first message. In case the given FieldDescriptor points to a + repeated field, the indices need to be valid. Otherwise they should be ignored.
        +
        index2 - the index of second message. In case the given FieldDescriptor points to a + repeated field, the indices need to be valid. Otherwise they should be ignored.
        +
        parentFields - an immutable list of fields that was taken to find the current field (not + include current field).
        +
        Returns:
        +
        Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for + submessages. Returning RECURSE for fields not being submessages is illegal.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/util/MessageDifferencer.FieldComparator.ComparisonResult.html b/docs/java/gust/util/MessageDifferencer.FieldComparator.ComparisonResult.html new file mode 100644 index 000000000..56e022bf6 --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.FieldComparator.ComparisonResult.html @@ -0,0 +1,434 @@ + + + + + +MessageDifferencer.FieldComparator.ComparisonResult + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Enum MessageDifferencer.FieldComparator.ComparisonResult

+
+
+ + +
+ +
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static MessageDifferencer.FieldComparator.ComparisonResult[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (MessageDifferencer.FieldComparator.ComparisonResult c : MessageDifferencer.FieldComparator.ComparisonResult.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static MessageDifferencer.FieldComparator.ComparisonResult valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      + + + + +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/util/MessageDifferencer.FieldComparator.html b/docs/java/gust/util/MessageDifferencer.FieldComparator.html new file mode 100644 index 000000000..b03eb8213 --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.FieldComparator.html @@ -0,0 +1,315 @@ + + + + + +MessageDifferencer.FieldComparator + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Interface MessageDifferencer.FieldComparator

+
+
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        compare

        +
        MessageDifferencer.FieldComparator.ComparisonResult compare​(com.google.protobuf.Message message1,
        +                                                            com.google.protobuf.Message message2,
        +                                                            com.google.protobuf.Descriptors.FieldDescriptor field,
        +                                                            int index1,
        +                                                            int index2,
        +                                                            com.google.common.collect.ImmutableList<MessageDifferencer.SpecificField> parentFields)
        +
        Compares the values of a field in two protocol buffer messages.
        +
        +
        Parameters:
        +
        message1 - the first message.
        +
        message2 - the second message.
        +
        field - field descriptor of the field where need to be compared.
        +
        index1 - the index of first message. In case the given FieldDescriptor points to a + repeated field, the indices need to be valid. Otherwise they should be ignored.
        +
        index2 - the index of second message. In case the given FieldDescriptor points to a + repeated field, the indices need to be valid. Otherwise they should be ignored.
        +
        parentFields - an immutable list of fields that was taken to find the current field (not + include current field).
        +
        Returns:
        +
        Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for + submessages. Returning RECURSE for fields not being submessages is illegal.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/MessageDifferencer.FloatComparison.html b/docs/java/gust/util/MessageDifferencer.FloatComparison.html new file mode 100644 index 000000000..e2bb3c62e --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.FloatComparison.html @@ -0,0 +1,392 @@ + + + + + +MessageDifferencer.FloatComparison + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Enum MessageDifferencer.FloatComparison

+
+
+ +
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      APPROXIMATE +
      Floats and doubles are compared using an equivalent of C++ MathUtil::AlmostEqual.
      +
      EXACT +
      Floats and doubles are compared exactly.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static MessageDifferencer.FloatComparisonvalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static MessageDifferencer.FloatComparison[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static MessageDifferencer.FloatComparison[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (MessageDifferencer.FloatComparison c : MessageDifferencer.FloatComparison.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static MessageDifferencer.FloatComparison valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/util/MessageDifferencer.IgnoreCriteria.html b/docs/java/gust/util/MessageDifferencer.IgnoreCriteria.html new file mode 100644 index 000000000..f51799fc3 --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.IgnoreCriteria.html @@ -0,0 +1,279 @@ + + + + + +MessageDifferencer.IgnoreCriteria + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Interface MessageDifferencer.IgnoreCriteria

+
+
+
+
    +
  • +
    +
    Enclosing class:
    +
    MessageDifferencer
    +
    +
    +
    public static interface MessageDifferencer.IgnoreCriteria
    +
    IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is + called on each criterion until one returns true or all return false. isIgnored is called for + fields where at least one side has a value.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods 
      Modifier and TypeMethodDescription
      booleanisIgnored​(com.google.protobuf.Message message1, + com.google.protobuf.Message message2, + com.google.protobuf.Descriptors.FieldDescriptor fieldDescriptor, + java.util.List<MessageDifferencer.SpecificField> fieldPath) +
      Should this field be ignored during the comparison.
      +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        isIgnored

        +
        boolean isIgnored​(com.google.protobuf.Message message1,
        +                  com.google.protobuf.Message message2,
        +                  @Nullable
        +                  com.google.protobuf.Descriptors.FieldDescriptor fieldDescriptor,
        +                  java.util.List<MessageDifferencer.SpecificField> fieldPath)
        +
        Should this field be ignored during the comparison.
        +
        +
        Parameters:
        +
        message1 - the message containing the field being compared
        +
        message2 - the message containing the field being compared
        +
        fieldDescriptor - the field being compared (null for unknown fields). More details about + unknown field is available in the last entry of fieldPath.
        +
        fieldPath - an unmodifiable view of the path from the root message to this field
        +
        Returns:
        +
        whether this field should be ignored in the comparison.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/MessageDifferencer.MapKeyComparator.html b/docs/java/gust/util/MessageDifferencer.MapKeyComparator.html new file mode 100644 index 000000000..13ff70359 --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.MapKeyComparator.html @@ -0,0 +1,274 @@ + + + + + +MessageDifferencer.MapKeyComparator + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Interface MessageDifferencer.MapKeyComparator

+
+
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        isMatch

        +
        boolean isMatch​(MessageDifferencer messageDifferencer,
        +                com.google.protobuf.Message message1,
        +                com.google.protobuf.Message message2,
        +                java.util.List<MessageDifferencer.SpecificField> parentFields)
        +
        Decides whether the given messages match with respect to the keys of the map entries they + represent.
        +
        +
        Parameters:
        +
        parentFields - the stack of SpecificFields corresponding to the proto path to the given + messages.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/MessageDifferencer.MessageFieldComparison.html b/docs/java/gust/util/MessageDifferencer.MessageFieldComparison.html new file mode 100644 index 000000000..cd9c3531c --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.MessageFieldComparison.html @@ -0,0 +1,395 @@ + + + + + +MessageDifferencer.MessageFieldComparison + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Enum MessageDifferencer.MessageFieldComparison

+
+
+ +
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      EQUAL +
      Fields must be present in both messages for the messages to be considered the same.
      +
      EQUIVALENT +
      Fields with default values are considered set for comparison purposes even if not explicitly + set in the messages themselves.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static MessageDifferencer.MessageFieldComparisonvalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static MessageDifferencer.MessageFieldComparison[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static MessageDifferencer.MessageFieldComparison[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (MessageDifferencer.MessageFieldComparison c : MessageDifferencer.MessageFieldComparison.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static MessageDifferencer.MessageFieldComparison valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/util/MessageDifferencer.RepeatedFieldComparison.html b/docs/java/gust/util/MessageDifferencer.RepeatedFieldComparison.html new file mode 100644 index 000000000..9d903a4d9 --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.RepeatedFieldComparison.html @@ -0,0 +1,394 @@ + + + + + +MessageDifferencer.RepeatedFieldComparison + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Enum MessageDifferencer.RepeatedFieldComparison

+
+
+ +
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      AS_LIST +
      Repeated fields are compared in order.
      +
      AS_SET +
      Treat all the repeated fields as sets by default.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static MessageDifferencer.RepeatedFieldComparisonvalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static MessageDifferencer.RepeatedFieldComparison[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+ +
+
+
+ + + + diff --git a/docs/java/gust/util/MessageDifferencer.ReportType.html b/docs/java/gust/util/MessageDifferencer.ReportType.html new file mode 100644 index 000000000..9fbc8b1ef --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.ReportType.html @@ -0,0 +1,455 @@ + + + + + +MessageDifferencer.ReportType + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Enum MessageDifferencer.ReportType

+
+
+ +
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      ADDED +
      A field has been added to message2.
      +
      DELETED +
      A field has been deleted in message2.
      +
      IGNORED 
      MATCHED +
      Reports that two fields match.
      +
      MODIFIED +
      A field has been modified.
      +
      MOVED +
      A repeated field has been moved to another location.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static MessageDifferencer.ReportTypevalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static MessageDifferencer.ReportType[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+ +
+
+
+ + + + diff --git a/docs/java/gust/util/MessageDifferencer.Reporter.html b/docs/java/gust/util/MessageDifferencer.Reporter.html new file mode 100644 index 000000000..4d60d660a --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.Reporter.html @@ -0,0 +1,280 @@ + + + + + +MessageDifferencer.Reporter + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Interface MessageDifferencer.Reporter

+
+
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        report

        +
        void report​(MessageDifferencer.ReportType type,
        +            com.google.protobuf.Message message1,
        +            com.google.protobuf.Message message2,
        +            com.google.common.collect.ImmutableList<MessageDifferencer.SpecificField> fieldPath)
        +
        Reports information about a specific field.
        +
        +
        Parameters:
        +
        type - the type of difference
        +
        message1 - the first message
        +
        message2 - the second message
        +
        fieldPath - an immutable list of fields that was taken to find the current field. For + example, for a field found in an embedded message, the list will contain two field + descriptors. The first will be the field of the embedded message itself and the second + will be the actual field in the embedded message that was added/deleted/modified.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/MessageDifferencer.Scope.html b/docs/java/gust/util/MessageDifferencer.Scope.html new file mode 100644 index 000000000..de753d9ac --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.Scope.html @@ -0,0 +1,394 @@ + + + + + +MessageDifferencer.Scope + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Enum MessageDifferencer.Scope

+
+
+ +
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      FULL +
      All fields of both messages are considered in the comparison.
      +
      PARTIAL +
      Only fields present in the first message are considered; fields set only in the second + message will be skipped during comparison.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static MessageDifferencer.ScopevalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static MessageDifferencer.Scope[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Detail

      + + + + + + + +
        +
      • +

        PARTIAL

        +
        public static final MessageDifferencer.Scope PARTIAL
        +
        Only fields present in the first message are considered; fields set only in the second + message will be skipped during comparison.
        +
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static MessageDifferencer.Scope[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (MessageDifferencer.Scope c : MessageDifferencer.Scope.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static MessageDifferencer.Scope valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/util/MessageDifferencer.SpecificField.html b/docs/java/gust/util/MessageDifferencer.SpecificField.html new file mode 100644 index 000000000..9e01aedf5 --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.SpecificField.html @@ -0,0 +1,377 @@ + + + + + +MessageDifferencer.SpecificField + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Class MessageDifferencer.SpecificField

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.util.MessageDifferencer.SpecificField
    • +
    +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + +
      Constructors 
      ConstructorDescription
      SpecificField() 
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods 
      Modifier and TypeMethodDescription
      abstract com.google.protobuf.Descriptors.FieldDescriptorgetField() +
      Returns the descriptor for known fields, or null for unknown fields.
      +
      abstract intgetIndex() +
      Returns the field index.
      +
      abstract intgetNewIndex() +
      Returns the new field index.
      +
      abstract MessageDifferencer.UnknownDescriptorgetUnknown() +
      Returns the descriptor for unknown fields, or null for known fields.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+ +
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/MessageDifferencer.StreamReporter.StreamException.html b/docs/java/gust/util/MessageDifferencer.StreamReporter.StreamException.html new file mode 100644 index 000000000..9a9d78c82 --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.StreamReporter.StreamException.html @@ -0,0 +1,258 @@ + + + + + +MessageDifferencer.StreamReporter.StreamException + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Class MessageDifferencer.StreamReporter.StreamException

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Throwable
    • +
    • +
        +
      • java.lang.Exception
      • +
      • +
          +
        • java.lang.RuntimeException
        • +
        • +
            +
          • gust.util.MessageDifferencer.StreamReporter.StreamException
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/MessageDifferencer.StreamReporter.html b/docs/java/gust/util/MessageDifferencer.StreamReporter.html new file mode 100644 index 000000000..e5ae27b10 --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.StreamReporter.html @@ -0,0 +1,394 @@ + + + + + +MessageDifferencer.StreamReporter + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Class MessageDifferencer.StreamReporter

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.util.MessageDifferencer.StreamReporter
    • +
    +
  • +
+
+ +
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + + + + + +
      Constructors 
      ConstructorDescription
      StreamReporter​(java.lang.Appendable output) +
      Equivalent to new StreamReporter(output, false).
      +
      StreamReporter​(java.lang.Appendable output, + boolean reportModifiedAggregates) +
      Creates a new reporter.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      voidreport​(MessageDifferencer.ReportType type, + com.google.protobuf.Message message1, + com.google.protobuf.Message message2, + com.google.common.collect.ImmutableList<MessageDifferencer.SpecificField> fieldPath) +
      Reports information about a specific field.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        StreamReporter

        +
        public StreamReporter​(java.lang.Appendable output)
        +
        Equivalent to new StreamReporter(output, false).
        +
      • +
      + + + +
        +
      • +

        StreamReporter

        +
        public StreamReporter​(java.lang.Appendable output,
        +                      boolean reportModifiedAggregates)
        +
        Creates a new reporter.
        +
        +
        Parameters:
        +
        output - where to write the output to
        +
        reportModifiedAggregates - when set to true, the stream reporter will also output + aggregates nodes (i.e. messages and groups) whose subfields have been modified. When + false, will only report the individual subfields. Defaults to false.
        +
        +
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        report

        +
        public void report​(MessageDifferencer.ReportType type,
        +                   com.google.protobuf.Message message1,
        +                   com.google.protobuf.Message message2,
        +                   com.google.common.collect.ImmutableList<MessageDifferencer.SpecificField> fieldPath)
        +
        Description copied from interface: MessageDifferencer.Reporter
        +
        Reports information about a specific field.
        +
        +
        Specified by:
        +
        report in interface MessageDifferencer.Reporter
        +
        Parameters:
        +
        type - the type of difference
        +
        message1 - the first message
        +
        message2 - the second message
        +
        fieldPath - an immutable list of fields that was taken to find the current field. For + example, for a field found in an embedded message, the list will contain two field + descriptors. The first will be the field of the embedded message itself and the second + will be the actual field in the embedded message that was added/deleted/modified.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/util/MessageDifferencer.UnknownDescriptor.html b/docs/java/gust/util/MessageDifferencer.UnknownDescriptor.html new file mode 100644 index 000000000..996f19f6b --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.UnknownDescriptor.html @@ -0,0 +1,335 @@ + + + + + +MessageDifferencer.UnknownDescriptor + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Class MessageDifferencer.UnknownDescriptor

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.util.MessageDifferencer.UnknownDescriptor
    • +
    +
  • +
+
+ +
+
+ +
+
+ +
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/MessageDifferencer.UnknownFieldType.html b/docs/java/gust/util/MessageDifferencer.UnknownFieldType.html new file mode 100644 index 000000000..a0d635d35 --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.UnknownFieldType.html @@ -0,0 +1,476 @@ + + + + + +MessageDifferencer.UnknownFieldType + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Enum MessageDifferencer.UnknownFieldType

+
+
+ +
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Enum Constants 
      Enum ConstantDescription
      FIXED32 +
      Fixed32.
      +
      FIXED64 +
      Fixed64.
      +
      GROUP +
      Group.
      +
      LENGTH_DELIMITED +
      Length delimited.
      +
      VARINT +
      Varint.
      +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Abstract Methods Concrete Methods 
      Modifier and TypeMethodDescription
      abstract java.util.List<?>getValues​(com.google.protobuf.UnknownFieldSet.Field field) +
      Returns the corresponding values from the given field.
      +
      intgetWireFormat() +
      Returns the wire format for this unknown field type.
      +
      static MessageDifferencer.UnknownFieldTypevalueOf​(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static MessageDifferencer.UnknownFieldType[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static MessageDifferencer.UnknownFieldType[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (MessageDifferencer.UnknownFieldType c : MessageDifferencer.UnknownFieldType.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static MessageDifferencer.UnknownFieldType valueOf​(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      + + + +
        +
      • +

        getWireFormat

        +
        public int getWireFormat()
        +
        Returns the wire format for this unknown field type.
        +
      • +
      + + + +
        +
      • +

        getValues

        +
        public abstract java.util.List<?> getValues​(com.google.protobuf.UnknownFieldSet.Field field)
        +
        Returns the corresponding values from the given field.
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/docs/java/gust/util/MessageDifferencer.html b/docs/java/gust/util/MessageDifferencer.html new file mode 100644 index 000000000..dc6e62fca --- /dev/null +++ b/docs/java/gust/util/MessageDifferencer.html @@ -0,0 +1,622 @@ + + + + + +MessageDifferencer + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Class MessageDifferencer

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.util.MessageDifferencer
    • +
    +
  • +
+
+
    +
  • +
    +
    @Immutable
    +public final class MessageDifferencer
    +extends java.lang.Object
    +
    Static methods and classes for comparing Protocol Messages. + +

    Taken from: com.google.common.truth.extensions.proto.MessageDifferencer

    +
  • +
+
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static booleanapproximatelyEquals​(com.google.protobuf.Message message1, + com.google.protobuf.Message message2) +
      Determines whether the supplied messages are approximately equal.
      +
      static booleanapproximatelyEquivalent​(com.google.protobuf.Message message1, + com.google.protobuf.Message message2) +
      Determines whether the supplied messages are approximately equivalent.
      +
      booleancompare​(com.google.protobuf.Message message1, + com.google.protobuf.Message message2) +
      Compares the two specified messages, returning true if they are the same.
      +
      booleancompare​(com.google.protobuf.Message message1, + com.google.protobuf.Message message2, + MessageDifferencer.Reporter reporter) +
      Compares the two specified messages, returning true if they are the same.
      +
      booleancompareWithFields​(com.google.protobuf.Message message1, + com.google.protobuf.Message message2, + java.util.Set<com.google.protobuf.Descriptors.FieldDescriptor> message1Fields, + java.util.Set<com.google.protobuf.Descriptors.FieldDescriptor> message2Fields) +
      Same as above, except comparing only the given sets of field descriptors, using only the given + message fields.
      +
      booleancompareWithFields​(com.google.protobuf.Message message1, + com.google.protobuf.Message message2, + java.util.Set<com.google.protobuf.Descriptors.FieldDescriptor> message1Fields, + java.util.Set<com.google.protobuf.Descriptors.FieldDescriptor> message2Fields, + MessageDifferencer.Reporter reporter) +
      Compares the two specified messages, returning true if they are the same, using only the given + message fields.
      +
      static booleanequals​(com.google.protobuf.Message message1, + com.google.protobuf.Message message2) +
      Determines whether the supplied messages are equal.
      +
      static booleanequivalent​(com.google.protobuf.Message message1, + com.google.protobuf.Message message2) +
      Determines whether the supplied messages are equivalent.
      +
      static MessageDifferencer.BuildernewBuilder() +
      Creates a new builder.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + + + + + +
        +
      • +

        equals

        +
        public static boolean equals​(com.google.protobuf.Message message1,
        +                             com.google.protobuf.Message message2)
        +
        Determines whether the supplied messages are equal. Equality is defined as all fields within + the two messages being set to the same value. Primitive fields and strings are compared by + value while embedded messages/groups are compared as if via a recursive call.
        +
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if the messages have different descriptors
        +
        +
      • +
      + + + +
        +
      • +

        equivalent

        +
        public static boolean equivalent​(com.google.protobuf.Message message1,
        +                                 com.google.protobuf.Message message2)
        +
        Determines whether the supplied messages are equivalent. Equivalency is defined as all fields + within the two messages having the same value. This differs from the equals(Message, + Message) method above in that fields with default values are considered set to said value + automatically. This method also ignores unknown fields.
        +
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if the messages have different descriptors
        +
        +
      • +
      + + + +
        +
      • +

        approximatelyEquals

        +
        public static boolean approximatelyEquals​(com.google.protobuf.Message message1,
        +                                          com.google.protobuf.Message message2)
        +
        Determines whether the supplied messages are approximately equal. Approximate equality is + defined as all fields within the two messages being approximately equal. Primitive (non-float) + fields and strings are compared by value, floats are compared using an equivalent of C++ + MathUtil::AlmostEquals and embedded messages/groups are compared as if via a recursive call.
        +
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if the messages have different descriptors
        +
        +
      • +
      + + + +
        +
      • +

        approximatelyEquivalent

        +
        public static boolean approximatelyEquivalent​(com.google.protobuf.Message message1,
        +                                              com.google.protobuf.Message message2)
        +
        Determines whether the supplied messages are approximately equivalent. Approximate equivalency + is defined as all fields within the two messages being approximately equivalent. As in approximatelyEquals(com.google.protobuf.Message, com.google.protobuf.Message), primitive (non-float) fields and strings are compared by value, floats + are compared using an equivalent of C++ MathUtil::AlmostEquals and embedded + messages/groups are compared as if via a recursive call. However, fields with default values + are considered set to said value, as per equivalent(com.google.protobuf.Message, com.google.protobuf.Message).
        +
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if the messages have different descriptors
        +
        +
      • +
      + + + +
        +
      • +

        compare

        +
        public boolean compare​(com.google.protobuf.Message message1,
        +                       com.google.protobuf.Message message2)
        +
        Compares the two specified messages, returning true if they are the same.
        +
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if the messages have different descriptors
        +
        +
      • +
      + + + +
        +
      • +

        compare

        +
        public boolean compare​(com.google.protobuf.Message message1,
        +                       com.google.protobuf.Message message2,
        +                       @Nullable
        +                       MessageDifferencer.Reporter reporter)
        +
        Compares the two specified messages, returning true if they are the same. Reports differences + to the reporter if it is non-null.
        +
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if the messages have different descriptors
        +
        +
      • +
      + + + +
        +
      • +

        compareWithFields

        +
        public boolean compareWithFields​(com.google.protobuf.Message message1,
        +                                 com.google.protobuf.Message message2,
        +                                 java.util.Set<com.google.protobuf.Descriptors.FieldDescriptor> message1Fields,
        +                                 java.util.Set<com.google.protobuf.Descriptors.FieldDescriptor> message2Fields)
        +
        Same as above, except comparing only the given sets of field descriptors, using only the given + message fields.
        +
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if the messages have different descriptors
        +
        +
      • +
      + + + +
        +
      • +

        compareWithFields

        +
        public boolean compareWithFields​(com.google.protobuf.Message message1,
        +                                 com.google.protobuf.Message message2,
        +                                 java.util.Set<com.google.protobuf.Descriptors.FieldDescriptor> message1Fields,
        +                                 java.util.Set<com.google.protobuf.Descriptors.FieldDescriptor> message2Fields,
        +                                 @Nullable
        +                                 MessageDifferencer.Reporter reporter)
        +
        Compares the two specified messages, returning true if they are the same, using only the given + message fields. Reports differences to the reporter if it is non-null.
        +
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if the messages have different descriptors
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/Pair.html b/docs/java/gust/util/Pair.html new file mode 100644 index 000000000..bbb8ffd85 --- /dev/null +++ b/docs/java/gust/util/Pair.html @@ -0,0 +1,334 @@ + + + + + +Pair + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
Package gust.util
+

Class Pair<K,​V>

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • gust.util.Pair<K,​V>
    • +
    +
  • +
+
+
    +
  • +
    +
    Type Parameters:
    +
    K - Key type.
    +
    V - Value type.
    +
    +
    +
    @Immutable
    +public final class Pair<K,​V>
    +extends java.lang.Object
    +
    Simple container for a pair of values, one assigned to the name "key," and one assigned to the name "value."
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethodDescription
      KgetKey() 
      VgetValue() 
      static <K,​V>
      Pair<K,​V>
      of​(K key, + V value) +
      Spawn a new K-V pair.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + + + +
        +
      • +

        of

        +
        public static <K,​V> Pair<K,​V> of​(@Nonnull
        +                                             K key,
        +                                             @Nonnull
        +                                             V value)
        +
        Spawn a new K-V pair.
        +
        +
        Type Parameters:
        +
        K - Generic key type.
        +
        V - Generic value type.
        +
        Parameters:
        +
        key - Key to use for the pair.
        +
        value - Value to use for the pair.
        +
        Returns:
        +
        Pair instance wrapping the provided key and value.
        +
        +
      • +
      + + + +
        +
      • +

        getKey

        +
        @Nonnull
        +public K getKey()
        +
        +
        Returns:
        +
        Key.
        +
        +
      • +
      + + + +
        +
      • +

        getValue

        +
        @Nonnull
        +public V getValue()
        +
        +
        Returns:
        +
        Value.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/Hex.html b/docs/java/gust/util/class-use/Hex.html new file mode 100644 index 000000000..1335e2dd1 --- /dev/null +++ b/docs/java/gust/util/class-use/Hex.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.util.Hex + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.util.Hex

+
+
No usage of gust.util.Hex
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/InstantFactory.html b/docs/java/gust/util/class-use/InstantFactory.html new file mode 100644 index 000000000..5f2ca8de6 --- /dev/null +++ b/docs/java/gust/util/class-use/InstantFactory.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.util.InstantFactory + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.util.InstantFactory

+
+
No usage of gust.util.InstantFactory
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.Builder.html b/docs/java/gust/util/class-use/MessageDifferencer.Builder.html new file mode 100644 index 000000000..2b5e758a0 --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.Builder.html @@ -0,0 +1,285 @@ + + + + + +Uses of Class gust.util.MessageDifferencer.Builder + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.util.MessageDifferencer.Builder

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.DefaultFieldComparator.html b/docs/java/gust/util/class-use/MessageDifferencer.DefaultFieldComparator.html new file mode 100644 index 000000000..3dcbfaf16 --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.DefaultFieldComparator.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.util.MessageDifferencer.DefaultFieldComparator + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.util.MessageDifferencer.DefaultFieldComparator

+
+
No usage of gust.util.MessageDifferencer.DefaultFieldComparator
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.FieldComparator.ComparisonResult.html b/docs/java/gust/util/class-use/MessageDifferencer.FieldComparator.ComparisonResult.html new file mode 100644 index 000000000..f44a5482e --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.FieldComparator.ComparisonResult.html @@ -0,0 +1,233 @@ + + + + + +Uses of Class gust.util.MessageDifferencer.FieldComparator.ComparisonResult + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.util.MessageDifferencer.FieldComparator.ComparisonResult

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.FieldComparator.html b/docs/java/gust/util/class-use/MessageDifferencer.FieldComparator.html new file mode 100644 index 000000000..9c5786dc0 --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.FieldComparator.html @@ -0,0 +1,214 @@ + + + + + +Uses of Interface gust.util.MessageDifferencer.FieldComparator + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.util.MessageDifferencer.FieldComparator

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.FloatComparison.html b/docs/java/gust/util/class-use/MessageDifferencer.FloatComparison.html new file mode 100644 index 000000000..36eb097aa --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.FloatComparison.html @@ -0,0 +1,235 @@ + + + + + +Uses of Class gust.util.MessageDifferencer.FloatComparison + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.util.MessageDifferencer.FloatComparison

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.IgnoreCriteria.html b/docs/java/gust/util/class-use/MessageDifferencer.IgnoreCriteria.html new file mode 100644 index 000000000..3d8fcfbaa --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.IgnoreCriteria.html @@ -0,0 +1,194 @@ + + + + + +Uses of Interface gust.util.MessageDifferencer.IgnoreCriteria + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.util.MessageDifferencer.IgnoreCriteria

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.MapKeyComparator.html b/docs/java/gust/util/class-use/MessageDifferencer.MapKeyComparator.html new file mode 100644 index 000000000..535a320fd --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.MapKeyComparator.html @@ -0,0 +1,195 @@ + + + + + +Uses of Interface gust.util.MessageDifferencer.MapKeyComparator + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.util.MessageDifferencer.MapKeyComparator

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.MessageFieldComparison.html b/docs/java/gust/util/class-use/MessageDifferencer.MessageFieldComparison.html new file mode 100644 index 000000000..f6555d5ae --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.MessageFieldComparison.html @@ -0,0 +1,222 @@ + + + + + +Uses of Class gust.util.MessageDifferencer.MessageFieldComparison + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.util.MessageDifferencer.MessageFieldComparison

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.RepeatedFieldComparison.html b/docs/java/gust/util/class-use/MessageDifferencer.RepeatedFieldComparison.html new file mode 100644 index 000000000..6b347a025 --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.RepeatedFieldComparison.html @@ -0,0 +1,222 @@ + + + + + +Uses of Class gust.util.MessageDifferencer.RepeatedFieldComparison + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.util.MessageDifferencer.RepeatedFieldComparison

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.ReportType.html b/docs/java/gust/util/class-use/MessageDifferencer.ReportType.html new file mode 100644 index 000000000..023a73986 --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.ReportType.html @@ -0,0 +1,232 @@ + + + + + +Uses of Class gust.util.MessageDifferencer.ReportType + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.util.MessageDifferencer.ReportType

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.Reporter.html b/docs/java/gust/util/class-use/MessageDifferencer.Reporter.html new file mode 100644 index 000000000..96dc0425a --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.Reporter.html @@ -0,0 +1,228 @@ + + + + + +Uses of Interface gust.util.MessageDifferencer.Reporter + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
gust.util.MessageDifferencer.Reporter

+
+
+
    +
  • + + + + + + + + + + + + +
    Packages that use MessageDifferencer.Reporter 
    PackageDescription
    gust.util +
    Provides generic pure-Java utility objects and classes.
    +
    +
  • +
  • +
      +
    • +
      + + +

      Uses of MessageDifferencer.Reporter in gust.util

      + + + + + + + + + + + + + + +
      Classes in gust.util that implement MessageDifferencer.Reporter 
      Modifier and TypeClassDescription
      static class MessageDifferencer.StreamReporter +
      A message difference reporter that writes a textual description of the differences to a + character stream.
      +
      + + + + + + + + + + + + + + + + + + + +
      Methods in gust.util with parameters of type MessageDifferencer.Reporter 
      Modifier and TypeMethodDescription
      booleanMessageDifferencer.compare​(com.google.protobuf.Message message1, + com.google.protobuf.Message message2, + MessageDifferencer.Reporter reporter) +
      Compares the two specified messages, returning true if they are the same.
      +
      booleanMessageDifferencer.compareWithFields​(com.google.protobuf.Message message1, + com.google.protobuf.Message message2, + java.util.Set<com.google.protobuf.Descriptors.FieldDescriptor> message1Fields, + java.util.Set<com.google.protobuf.Descriptors.FieldDescriptor> message2Fields, + MessageDifferencer.Reporter reporter) +
      Compares the two specified messages, returning true if they are the same, using only the given + message fields.
      +
      +
      +
    • +
    +
  • +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.Scope.html b/docs/java/gust/util/class-use/MessageDifferencer.Scope.html new file mode 100644 index 000000000..6a14df73b --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.Scope.html @@ -0,0 +1,222 @@ + + + + + +Uses of Class gust.util.MessageDifferencer.Scope + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.util.MessageDifferencer.Scope

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.SpecificField.html b/docs/java/gust/util/class-use/MessageDifferencer.SpecificField.html new file mode 100644 index 000000000..d221e4c3e --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.SpecificField.html @@ -0,0 +1,250 @@ + + + + + +Uses of Class gust.util.MessageDifferencer.SpecificField + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.util.MessageDifferencer.SpecificField

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.StreamReporter.StreamException.html b/docs/java/gust/util/class-use/MessageDifferencer.StreamReporter.StreamException.html new file mode 100644 index 000000000..ccd61d96f --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.StreamReporter.StreamException.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.util.MessageDifferencer.StreamReporter.StreamException + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.util.MessageDifferencer.StreamReporter.StreamException

+
+
No usage of gust.util.MessageDifferencer.StreamReporter.StreamException
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.StreamReporter.html b/docs/java/gust/util/class-use/MessageDifferencer.StreamReporter.html new file mode 100644 index 000000000..5212757ed --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.StreamReporter.html @@ -0,0 +1,148 @@ + + + + + +Uses of Class gust.util.MessageDifferencer.StreamReporter + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.util.MessageDifferencer.StreamReporter

+
+
No usage of gust.util.MessageDifferencer.StreamReporter
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.UnknownDescriptor.html b/docs/java/gust/util/class-use/MessageDifferencer.UnknownDescriptor.html new file mode 100644 index 000000000..57c392b12 --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.UnknownDescriptor.html @@ -0,0 +1,196 @@ + + + + + +Uses of Class gust.util.MessageDifferencer.UnknownDescriptor + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.util.MessageDifferencer.UnknownDescriptor

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.UnknownFieldType.html b/docs/java/gust/util/class-use/MessageDifferencer.UnknownFieldType.html new file mode 100644 index 000000000..da3720d8f --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.UnknownFieldType.html @@ -0,0 +1,211 @@ + + + + + +Uses of Class gust.util.MessageDifferencer.UnknownFieldType + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.util.MessageDifferencer.UnknownFieldType

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/MessageDifferencer.html b/docs/java/gust/util/class-use/MessageDifferencer.html new file mode 100644 index 000000000..d4568232f --- /dev/null +++ b/docs/java/gust/util/class-use/MessageDifferencer.html @@ -0,0 +1,217 @@ + + + + + +Uses of Class gust.util.MessageDifferencer + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.util.MessageDifferencer

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/class-use/Pair.html b/docs/java/gust/util/class-use/Pair.html new file mode 100644 index 000000000..6b35d4540 --- /dev/null +++ b/docs/java/gust/util/class-use/Pair.html @@ -0,0 +1,228 @@ + + + + + +Uses of Class gust.util.Pair + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
gust.util.Pair

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/package-summary.html b/docs/java/gust/util/package-summary.html new file mode 100644 index 000000000..6324dcdf4 --- /dev/null +++ b/docs/java/gust/util/package-summary.html @@ -0,0 +1,330 @@ + + + + + +gust.util + + + + + + + + + + + + + + +
+ +
+
+
+

Package gust.util

+
+
+
+ + +
Provides generic pure-Java utility objects and classes.
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/package-tree.html b/docs/java/gust/util/package-tree.html new file mode 100644 index 000000000..758f2afce --- /dev/null +++ b/docs/java/gust/util/package-tree.html @@ -0,0 +1,213 @@ + + + + + +gust.util Class Hierarchy + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package gust.util

+Package Hierarchies: + +
+
+
+

Class Hierarchy

+ +
+
+

Interface Hierarchy

+ +
+
+

Enum Hierarchy

+ +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/gust/util/package-use.html b/docs/java/gust/util/package-use.html new file mode 100644 index 000000000..d3f18d0c6 --- /dev/null +++ b/docs/java/gust/util/package-use.html @@ -0,0 +1,304 @@ + + + + + +Uses of Package gust.util + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Package
gust.util

+
+
+ +
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/help-doc.html b/docs/java/help-doc.html new file mode 100644 index 000000000..b6ef7d0b1 --- /dev/null +++ b/docs/java/help-doc.html @@ -0,0 +1,280 @@ + + + + + +API Help + + + + + + + + + + + + + +
+ +
+
+
+

How This API Document Is Organized

+
This API (Application Programming Interface) document has pages corresponding to the items in the navigation bar, described as follows.
+
+
+
    +
  • +
    +

    Overview

    +

    The Overview page is the front page of this API document and provides a list of all packages with a summary for each. This page can also contain an overall description of the set of packages.

    +
    +
  • +
  • +
    +

    Package

    +

    Each package has a page that contains a list of its classes and interfaces, with a summary for each. These pages may contain six categories:

    +
      +
    • Interfaces
    • +
    • Classes
    • +
    • Enums
    • +
    • Exceptions
    • +
    • Errors
    • +
    • Annotation Types
    • +
    +
    +
  • +
  • +
    +

    Class or Interface

    +

    Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a class/interface description, summary tables, and detailed member descriptions:

    +
      +
    • Class Inheritance Diagram
    • +
    • Direct Subclasses
    • +
    • All Known Subinterfaces
    • +
    • All Known Implementing Classes
    • +
    • Class or Interface Declaration
    • +
    • Class or Interface Description
    • +
    +
    +
      +
    • Nested Class Summary
    • +
    • Field Summary
    • +
    • Property Summary
    • +
    • Constructor Summary
    • +
    • Method Summary
    • +
    +
    +
      +
    • Field Detail
    • +
    • Property Detail
    • +
    • Constructor Detail
    • +
    • Method Detail
    • +
    +

    Each summary entry contains the first sentence from the detailed description for that item. The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.

    +
    +
  • +
  • +
    +

    Annotation Type

    +

    Each annotation type has its own separate page with the following sections:

    +
      +
    • Annotation Type Declaration
    • +
    • Annotation Type Description
    • +
    • Required Element Summary
    • +
    • Optional Element Summary
    • +
    • Element Detail
    • +
    +
    +
  • +
  • +
    +

    Enum

    +

    Each enum has its own separate page with the following sections:

    +
      +
    • Enum Declaration
    • +
    • Enum Description
    • +
    • Enum Constant Summary
    • +
    • Enum Constant Detail
    • +
    +
    +
  • +
  • +
    +

    Use

    +

    Each documented package, class and interface has its own Use page. This page describes what packages, classes, methods, constructors and fields use any part of the given class or package. Given a class or interface A, its "Use" page includes subclasses of A, fields declared as A, methods that return A, and methods and constructors with parameters of type A. You can access this page by first going to the package, class or interface, then clicking on the "Use" link in the navigation bar.

    +
    +
  • +
  • +
    +

    Tree (Class Hierarchy)

    +

    There is a Class Hierarchy page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. Classes are organized by inheritance structure starting with java.lang.Object. Interfaces do not inherit from java.lang.Object.

    +
      +
    • When viewing the Overview page, clicking on "Tree" displays the hierarchy for all packages.
    • +
    • When viewing a particular package, class or interface page, clicking on "Tree" displays the hierarchy for only that package.
    • +
    +
    +
  • +
  • +
    +

    Deprecated API

    +

    The Deprecated API page lists all of the API that have been deprecated. A deprecated API is not recommended for use, generally due to improvements, and a replacement API is usually given. Deprecated APIs may be removed in future implementations.

    +
    +
  • +
  • +
    +

    Index

    +

    The Index contains an alphabetic index of all classes, interfaces, constructors, methods, and fields, as well as lists of all packages and all classes.

    +
    +
  • +
  • +
    +

    All Classes

    +

    The All Classes link shows all classes and interfaces except non-static nested types.

    +
    +
  • +
  • +
    +

    Serialized Form

    +

    Each serializable or externalizable class has a description of its serialization fields and methods. This information is of interest to re-implementors, not to developers using the API. While there is no link in the navigation bar, you can get to this information by going to any serialized class and clicking "Serialized Form" in the "See also" section of the class description.

    +
    +
  • +
  • +
    +

    Constant Field Values

    +

    The Constant Field Values page lists the static final fields and their values.

    +
    +
  • +
  • +
    +

    Search

    +

    You can search for definitions of modules, packages, types, fields, methods and other terms defined in the API, using some or all of the name. "Camel-case" abbreviations are supported: for example, "InpStr" will find "InputStream" and "InputStreamReader".

    +
    +
  • +
+
+This help file applies to API documentation generated by the standard doclet.
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/index-all.html b/docs/java/index-all.html new file mode 100644 index 000000000..7326b75e9 --- /dev/null +++ b/docs/java/index-all.html @@ -0,0 +1,4123 @@ + + + + + +Index + + + + + + + + + + + + + +
+ +
+
+
A B C D E F G H I J K L M N O P R S T U V W X 
All Classes All Packages + + +

A

+
+
accept() - Method in interface gust.backend.AssetConfiguration.AssetVarianceConfiguration
+
+
Whether to vary based on Accept.
+
+
accept() - Method in interface gust.backend.DynamicServingConfiguration.DynamicVarianceConfiguration
+
+
Whether to indicate response variance by Accept.
+
+
acceptEither(CompletionStage<? extends T>, Consumer<? super T>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
acceptEitherAsync(CompletionStage<? extends T>, Consumer<? super T>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
acceptEitherAsync(CompletionStage<? extends T>, Consumer<? super T>, Executor) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
acquire() - Static method in class gust.backend.driver.inmemory.InMemoryCache
+
+
Acquire an instance of the in-memory caching driver, generalized to support the provided key type K and + model instance type M.
+
+
acquire() - Static method in class gust.backend.driver.spanner.SpannerManager
+
+
Acquire a singleton instance of the Spanner manager, which can be used safely across threads to interact with + Google Cloud Spanner, driven by Message-generated models.
+
+
acquire() - Static method in class gust.backend.runtime.AssetManager
+
+
Acquire a new instance of the asset manager.
+
+
acquire(FirestoreOptions.Builder, TransportChannelProvider, CredentialsProvider, GrpcTransportOptions, ListeningScheduledExecutorService, K, M) - Static method in class gust.backend.driver.firestore.FirestoreAdapter
+
+
Acquire an instance of the FirestoreAdapter and FirestoreDriver, customized for the provided + `modelInstance` and `keyInstance`.
+
+
acquire(SpannerOptions.Builder, DatabaseId, TransportChannelProvider, Optional<CredentialsProvider>, Optional<SpannerOptions.CallCredentialsProvider>, GrpcTransportOptions, ListeningScheduledExecutorService, K, M, SpannerDriverSettings, Optional<CacheDriver<K, M>>) - Static method in class gust.backend.driver.spanner.SpannerAdapter
+
+
Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId.
+
+
acquire(E) - Method in interface gust.backend.transport.TransportManager
+
+
Acquire a connection from this transport manager.
+
+
acquire(GoogleService) - Method in class gust.backend.transport.GoogleTransportManager
+
+
Acquire a connection from this transport manager.
+
+
acquire(K, M, FirestoreOptions.Builder, ListeningScheduledExecutorService) - Static method in class gust.backend.driver.firestore.FirestoreAdapter
+
+
Acquire an instance of the FirestoreAdapter and FirestoreDriver, customized for the provided + `modelInstance` and `keyInstance`.
+
+
acquire(K, M, DatabaseId) - Static method in class gust.backend.driver.spanner.SpannerAdapter
+
+
Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId.
+
+
acquire(K, M, DatabaseId, SpannerOptions.Builder, ListeningScheduledExecutorService) - Static method in class gust.backend.driver.spanner.SpannerAdapter
+
+
Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId.
+
+
acquire(K, M, DatabaseId, ListeningScheduledExecutorService) - Static method in class gust.backend.driver.spanner.SpannerAdapter
+
+
Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId.
+
+
acquire(K, M, DatabaseId, Optional<ListeningScheduledExecutorService>, Optional<SpannerDriverSettings>, Optional<SpannerOptions.Builder>, Optional<CacheDriver<K, M>>) - Static method in class gust.backend.driver.spanner.SpannerAdapter
+
+
Create or acquire a SpannerAdapter and matching SpannerDriver for the provided generated model + key and object structures, working against the provided Spanner DatabaseId.
+
+
acquire(K, M, ListeningScheduledExecutorService) - Static method in class gust.backend.driver.firestore.FirestoreAdapter
+
+
Acquire an instance of the FirestoreAdapter and FirestoreDriver, customized for the provided + `modelInstance` and `keyInstance`.
+
+
acquire(K, M, ListeningScheduledExecutorService) - Static method in class gust.backend.driver.inmemory.InMemoryAdapter
+
+
Acquire an instance of the InMemoryAdapter, specialized for the provided empty model instance.
+
+
acquire(K, M, Optional<CacheDriver<K, M>>, ListeningScheduledExecutorService) - Static method in class gust.backend.driver.inmemory.InMemoryAdapter
+
+
Acquire an instance of the InMemoryAdapter, specialized for the provided empty model instance, optionally + specifying a CacheDriver to use.
+
+
adapter(Key, Model) - Method in class gust.backend.driver.spanner.SpannerManager.ConfiguredSpannerManager
+
+
Acquire a typed adapter instance specialized to the provided key and model types, which should derive from + schema-driven Message classes.
+
+
ADDED - gust.util.MessageDifferencer.ReportType
+
+
A field has been added to message2.
+
+
addFeaturePolicy(String...) - Method in class gust.backend.PageContextManager
+
+
Add a `Feature-Policy` entry for the current response render flow.
+
+
addIgnoreCriteria(MessageDifferencer.IgnoreCriteria) - Method in class gust.util.MessageDifferencer.Builder
+
 
+
additionalDirectives() - Method in interface gust.backend.AssetConfiguration.AssetCachingConfiguration
+
+
Additional directives to inject into the HTTP caching header.
+
+
additionalHeaders() - Method in interface gust.backend.DynamicServingConfiguration
+
+
Arbitrary headers to add to all responses.
+
+
addKeyword(String...) - Method in class gust.backend.PageContextManager
+
+
Add the provided page keywords for the current render flow.
+
+
addLink(String, URI, Optional<String>) - Method in class gust.backend.PageContextManager
+
+
Add a regular HTML metadata link to the current render flow, specified by the provided method parameters.
+
+
addLink(Context.PageLink.Builder) - Method in class gust.backend.PageContextManager
+
+
Add a regular HTML metadata link to the current render flow, specified by a Context.PageLink proto record.
+
+
addListener(Runnable, Executor) - Method in class gust.backend.runtime.ReactiveFuture
+
+
Registers a listener to be run on the given executor.
+
+
addListener(Runnable, Executor) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
addListener(Runnable, Executor) - Method in class gust.backend.runtime.ReactiveFuture.PublisherListenableFuture
+
 
+
affixHeaders(MutableHttpResponse<T>) - Method in class gust.backend.AppController
+
+
Affix headers to the provided response, according to current app configuration for dynamic serving.
+
+
affixHeaders(MutableHttpResponse<T>, DynamicServingConfiguration) - Method in class gust.backend.AppController
+
+
Affix headers to the provided response, according to the provided app config for dynamic serving.
+
+
allAdapters() - Method in class gust.backend.driver.spanner.SpannerManager.ConfiguredSpannerManager
+
+
Returns the full set of known configured Spanner managers, JVM-wide.
+
+
allFields(Descriptors.Descriptor) - Static method in class gust.backend.model.ModelMetadata
+
+
Crawl all fields, recursively, on the descriptor provided.
+
+
allFields(Descriptors.Descriptor, Optional<Predicate<ModelMetadata.FieldPointer>>) - Static method in class gust.backend.model.ModelMetadata
+
+
Crawl all fields, recursively, on the descriptor provided.
+
+
allFields(Descriptors.Descriptor, Optional<Predicate<ModelMetadata.FieldPointer>>, Boolean) - Static method in class gust.backend.model.ModelMetadata
+
+
Crawl all fields, recursively, on the descriptor provided.
+
+
allFields(Descriptors.Descriptor, Optional<Predicate<ModelMetadata.FieldPointer>>, Predicate<ModelMetadata.FieldPointer>) - Static method in class gust.backend.model.ModelMetadata
+
+
Crawl all fields, recursively, on the descriptor provided.
+
+
allManagers() - Static method in class gust.backend.driver.spanner.SpannerManager
+
+
Returns the full set of known configured Spanner managers, JVM-wide.
+
+
annotatedField(Descriptors.Descriptor, GeneratedMessage.GeneratedExtension<DescriptorProtos.FieldOptions, E>) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve a ModelMetadata.FieldPointer within the scope of the provided model descriptor, that holds values for the + specified metadata annotation ext.
+
+
annotatedField(Descriptors.Descriptor, GeneratedMessage.GeneratedExtension<DescriptorProtos.FieldOptions, E>, Boolean, Optional<Function<E, Boolean>>) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve a ModelMetadata.FieldPointer within the scope of the provided model descriptor, that holds values for the + specified metadata annotation ext.
+
+
annotatedField(Descriptors.Descriptor, GeneratedMessage.GeneratedExtension<DescriptorProtos.FieldOptions, E>, Optional<Function<E, Boolean>>) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve a ModelMetadata.FieldPointer within the scope of the provided model descriptor, that holds values for the + specified metadata annotation ext.
+
+
annotatedField(Message, GeneratedMessage.GeneratedExtension<DescriptorProtos.FieldOptions, E>) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve a ModelMetadata.FieldPointer within the scope of instance, that holds values for the specified metadata + annotation ext.
+
+
annotatedField(Message, GeneratedMessage.GeneratedExtension<DescriptorProtos.FieldOptions, E>, Boolean) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve a ModelMetadata.FieldPointer within the scope of instance, that holds values for the specified metadata + annotation ext.
+
+
annotatedField(Message, GeneratedMessage.GeneratedExtension<DescriptorProtos.FieldOptions, E>, Boolean, Optional<Function<E, Boolean>>) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve a ModelMetadata.FieldPointer within the scope of instance, that holds values for the specified metadata + annotation ext.
+
+
AppController - Class in gust.backend
+
+
Describes a framework application controller, which is pre-loaded with convenient access to tooling, and any injected + application logic.
+
+
AppController(PageContextManager) - Constructor for class gust.backend.AppController
+
+
Initialize a new application controller.
+
+
Application - Class in gust.backend
+
+
Main application class, which bootstraps a backend Gust app via Micronaut, including any configured controllers, + services, or assets.
+
+
ApplicationBoot - Class in gust.backend
+
+
Responsible for any testable functionality that occurs when bootstrapping a Java-based application, including + force-resolving critical configuration files, setting up logging, and so on.
+
+
applyFieldsRecursive(Message.Builder, Message, Set<String>, FetchOptions.MaskMode, String) - Method in interface gust.backend.model.PersistenceDriver
+
+
Apply the fields from source to target, considering any provided FieldMask.
+
+
applyMask(Model, FetchOptions) - Method in interface gust.backend.model.PersistenceDriver
+
+
Apply mask-related options to the provided instance.
+
+
applyOpenGraph(Context.Metadata.OpenGraph.Builder) - Method in class gust.backend.PageContextManager
+
+
Apply the provided OpenGraph metadata configuration to the current OpenGraph metadata configuration, if any.
+
+
applyToEither(CompletionStage<? extends T>, Function<? super T, U>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
applyToEitherAsync(CompletionStage<? extends T>, Function<? super T, U>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
applyToEitherAsync(CompletionStage<? extends T>, Function<? super T, U>, Executor) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
APPROXIMATE - gust.util.MessageDifferencer.FloatComparison
+
+
Floats and doubles are compared using an equivalent of C++ MathUtil::AlmostEqual.
+
+
approximatelyEquals(Message, Message) - Static method in class gust.util.MessageDifferencer
+
+
Determines whether the supplied messages are approximately equal.
+
+
approximatelyEquivalent(Message, Message) - Static method in class gust.util.MessageDifferencer
+
+
Determines whether the supplied messages are approximately equivalent.
+
+
AS_LIST - gust.util.MessageDifferencer.RepeatedFieldComparison
+
+
Repeated fields are compared in order.
+
+
AS_SET - gust.util.MessageDifferencer.RepeatedFieldComparison
+
+
Treat all the repeated fields as sets by default.
+
+
ASC - gust.backend.driver.spanner.SpannerGeneratedDDL.SortDirection
+
+
Sort values in the column in ascending order.
+
+
asset(String, String, HttpRequest) - Method in class gust.backend.AssetController
+
+
Main GET serving endpoint for dynamic managed assets.
+
+
AssetConfiguration - Interface in gust.backend
+
+
App configuration bindings for asset management and serving.
+
+
AssetConfiguration.AssetCachingConfiguration - Interface in gust.backend
+
+
Describes the structure of asset caching configuration.
+
+
AssetConfiguration.AssetCompressionConfiguration - Interface in gust.backend
+
+
Describes settings regarding compressed asset serving.
+
+
AssetConfiguration.AssetVarianceConfiguration - Interface in gust.backend
+
+
Describes settings that control the Vary header affixed to assets.
+
+
AssetConfiguration.ContentDistributionConfiguration - Interface in gust.backend
+
+
Describes the structure of CDN-related asset settings.
+
+
AssetConfiguration.CrossOriginResourceConfiguration - Interface in gust.backend
+
+
Describes settings regarding Cross-Origin-Resource-Policy headers for dynamic content.
+
+
AssetController - Class in gust.backend
+
+
Built-in backend controller for serving dynamic managed assets.
+
+
AssetController.AssetsAwareCspFilter - Class in gust.backend
+
+
Replacement for CspFilter which disables itself when assets (or non-HTML content) are served.
+
+
assetDataByToken(String) - Method in class gust.backend.runtime.AssetManager
+
+
Resolve raw asset content by its opaque token.
+
+
AssetManager - Class in gust.backend.runtime
+
+
Manager class, which mediates interactions with the binary asset bundle.
+
+
AssetManager.ManagedAsset<M extends com.google.protobuf.Message> - Class in gust.backend.runtime
+
+
Public API surface for interacting with raw asset metadata.
+
+
AssetManager.ManagedAssetContent - Class in gust.backend.runtime
+
+
Public API surface for interacting with raw asset content.
+
+
AssetManager.ModuleType - Enum in gust.backend.runtime
+
+
Enumerates types of asset modules.
+
+
assetMetadataByModule(String) - Method in class gust.backend.runtime.AssetManager
+
+
Resolve asset metadata by its module name.
+
+
async() - Method in annotation type gust.backend.annotations.Js
+
+
Whether to completely unlink this script's loading flow from the DOM (i.e.
+
+
+ + + +

B

+
+
BaseController - Class in gust.backend
+
+
Supplies shared logic to all framework-provided controller base classes.
+
+
BaseController(PageContextManager) - Constructor for class gust.backend.BaseController
+
+
Initialize a base Gust controller from scratch.
+
+
BINARY - gust.backend.model.EncodingMode
+
+
Use Protobuf binary serialization.
+
+
BLIND - gust.backend.model.ModelSerializer.WriteDisposition
+
+
Blind writes.
+
+
BLIND - gust.backend.model.WriteOptions.WriteDisposition
+
+
We don't care.
+
+
block() - Method in interface gust.backend.DynamicServingConfiguration.XSSProtectionConfiguration
+
+
Whether to add the block flag.
+
+
build() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.Builder
+
+
Collapse the builder into an immutable DDL statement container
+
+
build() - Method in class gust.backend.driver.spanner.SpannerManager.Builder
+
+
Build this builder into a configured and immutable SpannerManager.ConfiguredSpannerManager instance, capable of + producing managed SpannerAdapters specialized to Message instances.
+
+
build() - Method in class gust.util.MessageDifferencer.Builder
+
+
Creates a new immutable differencer instance from this builder.
+
+
bump() - Method in interface gust.backend.AssetConfiguration
+
+
Specifies a bump value to apply to all asset URLs.
+
+
bytesToHex(byte[]) - Static method in class gust.util.Hex
+
+
Convert a byte array to hex.
+
+
bytesToHex(byte[], int) - Static method in class gust.util.Hex
+
+
Convert a byte array to hex, optionally limiting the number of characters returned and cycles performed.
+
+
+ + + +

C

+
+
cache() - Method in class gust.backend.driver.firestore.FirestoreAdapter
+
+
Return the cache driver in use for this particular model adapter.
+
+
cache() - Method in class gust.backend.driver.inmemory.InMemoryAdapter
+
+
Return the cache driver in use for this particular model adapter.
+
+
cache() - Method in class gust.backend.driver.spanner.SpannerAdapter
+
+
Return the cache driver in use for this particular model adapter.
+
+
cache() - Method in interface gust.backend.model.ModelAdapter
+
+
Return the cache driver in use for this particular model adapter.
+
+
cacheDefaultTTL() - Method in interface gust.backend.model.CacheOptions
+
 
+
cacheDefaultTTLUnit() - Method in interface gust.backend.model.CacheOptions
+
 
+
CacheDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message> - Interface in gust.backend.model
+
+
Describes the surface of a cache driver, which is a partner object to a PersistenceDriver specifically + tailored to deal with caching engines.
+
+
cacheEvictionMode() - Method in interface gust.backend.model.CacheOptions
+
 
+
CacheOptions - Interface in gust.backend.model
+
+
Specifies operational options related to caching.
+
+
CacheOptions.EvictionMode - Enum in gust.backend.model
+
+
Describes operating modes with regard to cache eviction.
+
+
cacheTimeout() - Method in interface gust.backend.model.CacheOptions
+
 
+
cacheTimeoutUnit() - Method in interface gust.backend.model.CacheOptions
+
 
+
calculateDefaultFields(Descriptors.Descriptor, SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Calculate a default projection of Spanner columns, as configured on the provided default model instance.
+
+
calculateDefaultFieldStream(Descriptors.Descriptor, SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Calculate a default projection of Spanner columns, as configured on the provided default model instance.
+
+
cancel() - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription
+
+
Request the publisher to stop sending data and clean up resources.
+
+
cancel() - Method in class gust.backend.runtime.ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription
+
+
Request the publisher to stop sending data and clean up resources.
+
+
cancel(boolean) - Method in class gust.backend.runtime.ReactiveFuture
+
+
Attempts to cancel execution of this task.
+
+
cancel(boolean) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
cancel(boolean) - Method in class gust.backend.runtime.ReactiveFuture.PublisherListenableFuture
+
 
+
cancelled() - Static method in class gust.backend.runtime.ReactiveFuture
+
+
Create an already-cancelled future.
+
+
CANCELLED - gust.backend.model.PersistenceFailure
+
+
The operation was cancelled.
+
+
CANONICAL - Static variable in annotation type gust.backend.annotations.Page
+
+
Property name for the canonical URL.
+
+
canonicalUrl() - Method in annotation type gust.backend.annotations.Page
+
+
Specifies the canonical URL to use in sitemaps and robots.txt listings, etc, for this page, if applicable.
+
+
CASCADE - gust.backend.driver.spanner.SpannerGeneratedDDL.PropagatedAction
+
+
Cascade changes on delete or update.
+
+
cdn() - Method in interface gust.backend.AssetConfiguration
+
+
Specifies settings regarding CDN use.
+
+
cdnPrefix(Optional<String>) - Method in class gust.backend.PageContextManager
+
+
Set the specified prefix as the Content Distribution Network hostname prefix to use when rendering asset + links for this HTTP cycle.
+
+
CHANGE_FREQUENCY - Static variable in annotation type gust.backend.annotations.Page
+
+
Property name for the page's change-frequency value.
+
+
changeFrequency() - Method in annotation type gust.backend.annotations.Page
+
+
Retrieve the change frequency set for this page, if any.
+
+
charset() - Method in interface gust.backend.AssetConfiguration.AssetVarianceConfiguration
+
+
Whether to vary based on Accept-Charset.
+
+
charset() - Method in interface gust.backend.DynamicServingConfiguration.DynamicVarianceConfiguration
+
+
Whether to indicate response variance by Accept-Charset.
+
+
checkExpectedTypes() - Method in interface gust.backend.driver.spanner.SpannerDriverSettings
+
 
+
clear() - Method in class gust.backend.model.SerializedModel
+
 
+
clearDelegatePackage() - Method in class gust.backend.PageContextManager
+
+
Clear any current delegate package.
+
+
clearFeaturePolicy() - Method in class gust.backend.PageContextManager
+
+
Clear the current set of `Feature-Policy` entries.
+
+
clearGooglebot() - Method in class gust.backend.PageContextManager
+
+
Clear the current value, if any, set for
+
+
clearKeywords() - Method in class gust.backend.PageContextManager
+
+
Clear the current set of page keywords for the current render flow.
+
+
clearLinks() - Method in class gust.backend.PageContextManager
+
+
Clear the set of HTML metadata links assigned to the current render flow.
+
+
clearOpenGraph() - Method in class gust.backend.PageContextManager
+
+
Clear any OpenGraph metadata configuration attached to the current render flow.
+
+
clearRobots() - Method in class gust.backend.PageContextManager
+
+
Clear the current value, if any, set for
+
+
clientHints() - Method in interface gust.backend.DynamicServingConfiguration
+
+
Settings related to support for Client Hints.
+
+
clone() - Method in class gust.backend.model.EncodedModel
+
close() - Method in class gust.backend.driver.spanner.SpannerAdapter
+
 
+
close() - Method in class gust.backend.driver.spanner.SpannerManager
+
+
Close all active Spanner connections tracked or controlled by this manager.
+
+
close() - Method in class gust.backend.driver.spanner.SpannerManager.ConfiguredSpannerManager
+
+
Close all active Spanner connections tracked or controlled by this configured manager.
+
+
close() - Method in class gust.backend.PageContextManager
+
+
Closes this stream and releases any system resources associated with it.
+
+
cloudDateFromProto(Date) - Static method in class gust.backend.driver.spanner.SpannerTemporalConverter
+
+
Convert a regular/standard Protocol Buffers date into a Google Cloud date without loss of resolution.
+
+
cloudTimestampFromProto(Timestamp) - Static method in class gust.backend.driver.spanner.SpannerTemporalConverter
+
+
Convert a regular/standard Protocol Buffers timestamp into a Google Cloud timestamp without loss of resolution.
+
+
codec() - Method in class gust.backend.driver.firestore.FirestoreAdapter
+
+
Acquire an instance of the codec used by this adapter.
+
+
codec() - Method in class gust.backend.driver.firestore.FirestoreDriver
+
+
Acquire an instance of the codec used by this adapter.
+
+
codec() - Method in class gust.backend.driver.inmemory.InMemoryAdapter
+
+
Acquire an instance of the codec used by this adapter.
+
+
codec() - Method in class gust.backend.driver.inmemory.InMemoryDriver
+
+
Acquire an instance of the codec used by this adapter.
+
+
codec() - Method in class gust.backend.driver.spanner.SpannerAdapter
+
+
Acquire an instance of the codec used by this adapter.
+
+
codec() - Method in class gust.backend.driver.spanner.SpannerDriver
+
 
+
codec() - Method in interface gust.backend.model.PersistenceDriver
+
+
Acquire an instance of the codec used by this adapter.
+
+
CollapsedMessage - Class in gust.backend.model
+
+
Describes a generic, collapsed message (i.e.
+
+
CollapsedMessage.Operation - Interface in gust.backend.model
+
+
Describes a generic collapsed data store operation.
+
+
CollapsedMessageCodec<Model extends com.google.protobuf.Message,​ReadIntermediate> - Class in gust.backend.model
+
 
+
CollapsedMessageSerializer<Model extends com.google.protobuf.Message> - Interface in gust.backend.model
+
 
+
columnOpts(Descriptors.FieldDescriptor) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Given a resolved Protocol Buffer Descriptors.FieldDescriptor which is considered eligible for interaction + with Spanner, resolve any present column-generic options and annotations via TableFieldOptions.
+
+
columnOpts(ModelMetadata.FieldPointer) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Given a pre-resolved ModelMetadata.FieldPointer, resolve any present TableFieldOptions.
+
+
compare(Message, Message) - Method in class gust.util.MessageDifferencer
+
+
Compares the two specified messages, returning true if they are the same.
+
+
compare(Message, Message, Descriptors.FieldDescriptor, int, int, ImmutableList<MessageDifferencer.SpecificField>) - Method in class gust.util.MessageDifferencer.DefaultFieldComparator
+
 
+
compare(Message, Message, Descriptors.FieldDescriptor, int, int, ImmutableList<MessageDifferencer.SpecificField>) - Method in interface gust.util.MessageDifferencer.FieldComparator
+
+
Compares the values of a field in two protocol buffer messages.
+
+
compare(Message, Message, MessageDifferencer.Reporter) - Method in class gust.util.MessageDifferencer
+
+
Compares the two specified messages, returning true if they are the same.
+
+
compareTo(ModelMetadata.FieldContainer<V>) - Method in class gust.backend.model.ModelMetadata.FieldContainer
+
compareTo(ModelMetadata.FieldPointer) - Method in class gust.backend.model.ModelMetadata.FieldPointer
+
compareTo(AssetManager.ManagedAsset) - Method in class gust.backend.runtime.AssetManager.ManagedAsset
+
 
+
compareTo(AssetManager.ManagedAssetContent) - Method in class gust.backend.runtime.AssetManager.ManagedAssetContent
+
 
+
compareWithFields(Message, Message, Set<Descriptors.FieldDescriptor>, Set<Descriptors.FieldDescriptor>) - Method in class gust.util.MessageDifferencer
+
+
Same as above, except comparing only the given sets of field descriptors, using only the given + message fields.
+
+
compareWithFields(Message, Message, Set<Descriptors.FieldDescriptor>, Set<Descriptors.FieldDescriptor>, MessageDifferencer.Reporter) - Method in class gust.util.MessageDifferencer
+
+
Compares the two specified messages, returning true if they are the same, using only the given + message fields.
+
+
compression() - Method in interface gust.backend.AssetConfiguration
+
+
Specifies settings for HTTP compression.
+
+
compressionModes() - Method in interface gust.backend.AssetConfiguration.AssetCompressionConfiguration
+
+
Whether to enable serving of pre-compressed assets.
+
+
CONFIG_PREFIX - Static variable in class gust.backend.driver.firestore.FirestoreTransportConfig
+
+
Path prefix at which the Firestore transport layer may be configured.
+
+
CONFIG_PREFIX - Static variable in class gust.backend.transport.GoogleTransportManager
+
+
Prefix under which Google services may be configured.
+
+
configureForDatabase(DatabaseId) - Method in class gust.backend.driver.spanner.SpannerManager
+
+
Configure a vanilla Spanner manager instance for a given database.
+
+
containsKey(Object) - Method in class gust.backend.model.SerializedModel
+
 
+
containsValue(Object) - Method in class gust.backend.model.SerializedModel
+
 
+
contentType() - Method in class gust.backend.PageContextManager
+
+
Returns the currently-set content type for this response render flow.
+
+
contentType(String) - Method in class gust.backend.PageContextManager
+
+
Sets the content type to return for the current render response flow.
+
+
context - Variable in class gust.backend.BaseController
+
+
Holds request-bound page context as it is built.
+
+
Core - Class in gust
+
+
Provides core values, utility methods, etc, which can be used throughout the back- and front-end of a Gust-based + application.
+
+
create(Key, Model) - Method in interface gust.backend.model.PersistenceDriver
+
+
Create the record specified by model using the optional pre-fabricated key, in underlying storage.
+
+
create(Key, Model, WriteOptions) - Method in interface gust.backend.model.PersistenceDriver
+
+
Create the record specified by model using the optional pre-fabricated key, and making use of the + specified options, in underlying storage.
+
+
create(Model) - Method in interface gust.backend.model.PersistenceDriver
+
+
Create the record specified by model in underlying storage, provisioning a key or ID for the record if + needed.
+
+
create(Model, WriteOptions) - Method in interface gust.backend.model.PersistenceDriver
+
+
Create the record specified by model using the specified set of options, in underlying storage.
+
+
create(Reference, SerializedModel) - Method in interface gust.backend.model.WriteProxy
+
+
Create a collapsed `message` in underlying storage, referenced by `reference`.
+
+
CREATE - gust.backend.model.ModelSerializer.WriteDisposition
+
+
Create-style writes.
+
+
credentialsProvider() - Method in interface gust.backend.transport.GrpcTransportCredentials
+
 
+
credentialsProvider(Optional<List<String>>) - Method in interface gust.backend.transport.GoogleTransportConfig
+
+
Resolve a credentials provider bound to the specified auth requirements.
+
+
credentialsProvider(Optional<List<String>>) - Method in interface gust.backend.transport.GrpcTransportCredentials
+
 
+
crossOriginResources() - Method in interface gust.backend.AssetConfiguration
+
+
Cross-Origin-Resource-Policy configuration for dynamic content.
+
+
CSS - gust.backend.runtime.AssetManager.ModuleType
+
+
The bundle contains style declarations.
+
+
cssRenamingMap() - Method in class gust.backend.TemplateProvider
+
+
By default, return `null` for the Soy CSS renaming map.
+
+
+ + + +

D

+
+
DatabaseAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadRecord,​WriteRecord> - Interface in gust.backend.model
+
+
Extends the standard ModelAdapter interface with rich persistence features, including querying, indexing, and + other stuff one would expect when interacting with a full database.
+
+
DatabaseDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadRecord,​WriteRecord> - Interface in gust.backend.model
+
 
+
DatabaseManager<Adapter extends DatabaseAdapter,​Driver extends DatabaseDriver> - Interface in gust.backend.model
+
 
+
DEFAULT_CACHE_TIMEOUT - Static variable in interface gust.backend.model.PersistenceDriver
+
+
Default timeout to apply when fetching from the cache.
+
+
DEFAULT_CACHE_TIMEOUT_UNIT - Static variable in interface gust.backend.model.PersistenceDriver
+
+ +
+
DEFAULT_CAPITALIZED_NAMES - Static variable in class gust.backend.driver.spanner.SpannerDriverSettings.DefaultSettings
+
+
Default value: Whether to generate Spanner style names with initial capitals.
+
+
DEFAULT_CHECK_EXPECTED_TYPES - Static variable in class gust.backend.driver.spanner.SpannerDriverSettings.DefaultSettings
+
+
Default value: Whether to perform runtime deserialization checks (`true`, default) or not (`false`).
+
+
DEFAULT_ENABLE_KEEPALIVE - Static variable in class gust.backend.driver.firestore.FirestoreTransportConfig
+
+
Whether to enable keepalive features by default.
+
+
DEFAULT_ENUMS_AS_NUMBERS - Static variable in class gust.backend.driver.spanner.SpannerDriverSettings.DefaultSettings
+
+
Default value: Whether to treat enumeration instances as numbers (`true`) or strings (`false`, default).
+
+
DEFAULT_FIRESTORE_ENDPOINT - Static variable in class gust.backend.driver.firestore.FirestoreTransportConfig
+
+
Default Firestore endpoint.
+
+
DEFAULT_KEEPALIVE_NO_ACTIVITY - Static variable in class gust.backend.driver.firestore.FirestoreTransportConfig
+
+
Whether to keep the connection alive, even if there is no activity.
+
+
DEFAULT_KEEPALIVE_TIME - Static variable in class gust.backend.driver.firestore.FirestoreTransportConfig
+
+
Length of time in between keepalive pings.
+
+
DEFAULT_KEEPALIVE_TIMEOUT - Static variable in class gust.backend.driver.firestore.FirestoreTransportConfig
+
+
Amount of time to wait before ending the keepalive stream.
+
+
DEFAULT_MAX_INBOUND_MESSAGE_SIZE - Static variable in interface gust.backend.transport.GrpcTransportConfig
+
+
Default maximum inbound message size, if no other value is specified.
+
+
DEFAULT_MAX_INBOUND_METADATA_SIZE - Static variable in interface gust.backend.transport.GrpcTransportConfig
+
+
Default maximum inbound metadata size, if no other value is specified.
+
+
DEFAULT_POOL_SIZE - Static variable in class gust.backend.driver.firestore.FirestoreTransportConfig
+
+
Specifies the default number of connections to maintain to Firestore.
+
+
DEFAULT_PRESERVE_FIELD_NAMES - Static variable in class gust.backend.driver.spanner.SpannerDriverSettings.DefaultSettings
+
+
Default value: Whether to preserve proto field names (`true`) or use JSON names (`false`, default).
+
+
DEFAULT_PRIME_CONNECTIONS - Static variable in interface gust.backend.transport.GrpcTransportConfig
+
+
Whether to prime managed gRPC connections by default.
+
+
DEFAULT_TIMEOUT - Static variable in interface gust.backend.model.PersistenceDriver
+
+
Default timeout to apply when otherwise unspecified.
+
+
DEFAULT_TIMEOUT_UNIT - Static variable in interface gust.backend.model.PersistenceDriver
+
+ +
+
defaultCapitalizedNames() - Method in interface gust.backend.driver.spanner.SpannerDriverSettings
+
 
+
defaultColumnSize() - Method in interface gust.backend.driver.spanner.SpannerDriverSettings
+
 
+
defaultConfig - Static variable in class gust.backend.ApplicationBoot
+
+
Default configuration provided by Gust.
+
+
DefaultFieldComparator(MessageDifferencer.FloatComparison) - Constructor for class gust.util.MessageDifferencer.DefaultFieldComparator
+
 
+
DEFAULTS - Static variable in interface gust.backend.AssetConfiguration.AssetCachingConfiguration
+
+
Sensible defaults for asset caching over HTTP.
+
+
DEFAULTS - Static variable in interface gust.backend.AssetConfiguration.AssetCompressionConfiguration
+
+
Sensible defaults for asset compression.
+
+
DEFAULTS - Static variable in interface gust.backend.AssetConfiguration.AssetVarianceConfiguration
+
+
Sensible defaults for vary headers.
+
+
DEFAULTS - Static variable in interface gust.backend.AssetConfiguration.ContentDistributionConfiguration
+
+
Sensible defaults for asset CDN settings.
+
+
DEFAULTS - Static variable in interface gust.backend.AssetConfiguration.CrossOriginResourceConfiguration
+
+
Sensible defaults for cross-origin resource policy.
+
+
DEFAULTS - Static variable in interface gust.backend.AssetConfiguration
+
+
Sensible defaults for asset configuration.
+
+
DEFAULTS - Static variable in interface gust.backend.driver.spanner.SpannerDriverSettings
+
+
Default set of configured settings for the Spanner driver.
+
+
DEFAULTS - Static variable in interface gust.backend.DynamicServingConfiguration.ClientHintsConfiguration
+
+
Sensible defaults for client hints.
+
+
DEFAULTS - Static variable in interface gust.backend.DynamicServingConfiguration
+
+
Set of sensible defaults for Gust dynamic serving.
+
+
DEFAULTS - Static variable in interface gust.backend.DynamicServingConfiguration.DynamicETagsConfiguration
+
+
Sensible defaults for dynamic etags.
+
+
DEFAULTS - Static variable in interface gust.backend.DynamicServingConfiguration.DynamicVarianceConfiguration
+
+
Sensible defaults for dynamic page variance.
+
+
DEFAULTS - Static variable in interface gust.backend.DynamicServingConfiguration.FeaturePolicyConfiguration
+
+
Sensible defaults for Feature-Policy.
+
+
DEFAULTS - Static variable in interface gust.backend.DynamicServingConfiguration.XSSProtectionConfiguration
+
+
Sensible defaults for cross-site scripting protection.
+
+
DEFAULTS - Static variable in interface gust.backend.model.DeleteOptions
+
+
Default set of delete operation options.
+
+
DEFAULTS - Static variable in interface gust.backend.model.FetchOptions
+
+
Default set of fetch options.
+
+
DEFAULTS - Static variable in interface gust.backend.model.UpdateOptions
+
+
Default set of update operation options.
+
+
DEFAULTS - Static variable in interface gust.backend.model.WriteOptions
+
+
Default set of write operation options.
+
+
defer() - Method in annotation type gust.backend.annotations.Js
+
+
Whether to defer loading this script until the body DOM has initialized.
+
+
deflate(Model) - Method in class gust.backend.driver.spanner.SpannerMutationSerializer
+
 
+
deflate(Model) - Method in interface gust.backend.model.ModelSerializer
+
+
Serialize a model instance from the provided object type to the specified output type, throwing exceptions + verbosely if we are unable to correctly, verifiably, and properly export the record.
+
+
delegatePackage() - Method in class gust.backend.PageContext
+
+
Return the delegate package that should be active when rendering the desired Soy output, if applicable.
+
+
delegatePackage() - Method in class gust.backend.PageContextManager
+
+
Fetch the active delegate package, or return Optional.empty().
+
+
delegatePackage(String) - Method in class gust.backend.PageContextManager
+
+
Set the active delegate package name, wrapped in an implied predicate which filters explicitly against the provided + name value.
+
+
delegatePackage(Predicate<String>) - Method in class gust.backend.PageContextManager
+
+
Set the active delegate package predicate directly.
+
+
delete(Key) - Method in interface gust.backend.model.PersistenceDriver
+
+
Delete and fully erase the record referenced by key from underlying storage, permanently.
+
+
delete(Key, DeleteOptions) - Method in class gust.backend.driver.firestore.FirestoreDriver
+
+
Low-level record delete method.
+
+
delete(Key, DeleteOptions) - Method in class gust.backend.driver.inmemory.InMemoryDriver
+
+
Low-level record delete method.
+
+
delete(Key, DeleteOptions) - Method in class gust.backend.driver.spanner.SpannerDriver
+
 
+
delete(Key, DeleteOptions) - Method in interface gust.backend.model.ModelAdapter
+
+
Low-level record delete method.
+
+
delete(Key, DeleteOptions) - Method in interface gust.backend.model.PersistenceDriver
+
+
Low-level record delete method.
+
+
DELETED - gust.util.MessageDifferencer.ReportType
+
+
A field has been deleted in message2.
+
+
DeleteOptions - Interface in gust.backend.model
+
+
Describes options specifically involved with deleting existing model entities.
+
+
deleteRecord(Model) - Method in interface gust.backend.model.PersistenceDriver
+
+
Delete and fully erase the supplied model from underlying storage, permanently.
+
+
deleteRecord(Model, DeleteOptions) - Method in interface gust.backend.model.PersistenceDriver
+
+
Delete and fully erase the supplied model from underlying storage, permanently.
+
+
DESC - gust.backend.driver.spanner.SpannerGeneratedDDL.SortDirection
+
+
Sort values in the column in descending order.
+
+
description() - Method in class gust.backend.PageContextManager
+
+
Retrieve the current value for the page description, set in the builder.
+
+
description(String) - Method in class gust.backend.PageContextManager
+
+
Set the page description for the current render flow.
+
+
deserialize(ReadIntermediate) - Method in interface gust.backend.model.ModelCodec
+
+
Sugar shortcut to de-serialize a model through the current codec's installed ModelDeserializer.
+
+
deserializer() - Method in class gust.backend.driver.spanner.SpannerCodec
+
 
+
deserializer() - Method in class gust.backend.model.CollapsedMessageCodec
+
+
Acquire an instance of the ModelDeserializer attached to this adapter.
+
+
deserializer() - Method in interface gust.backend.model.ModelCodec
+
+
Acquire an instance of the ModelDeserializer attached to this adapter.
+
+
deserializer() - Method in class gust.backend.model.ProtoModelCodec
+
+
Acquire an instance of the ModelDeserializer attached to this adapter.
+
+
DIFFERENT - gust.util.MessageDifferencer.FieldComparator.ComparisonResult
+
+
Compared fields are different.
+
+
dnsPrefetch() - Method in interface gust.backend.DynamicServingConfiguration
+
+
Hostnames to pre-load into the browser's DNS.
+
+
dnsPrefetch(Iterable<String>) - Method in class gust.backend.PageContextManager
+
+
Inject the specified list of hosts as DNS records to prefetch from the browser.
+
+
dnsPrefetch(String...) - Method in class gust.backend.PageContextManager
+
+
Inject the specified DNS hostname(s) as records to prefetch from the browser.
+
+
doFilter(HttpRequest<?>, ServerFilterChain) - Method in class gust.backend.AssetController.AssetsAwareCspFilter
+
 
+
done(R) - Static method in class gust.backend.runtime.ReactiveFuture
+
+
Create an already-resolved future, wrapping the provided value.
+
+
DynamicServingConfiguration - Interface in gust.backend
+
+
Supplies configuration structure for dynamically-served app pages through Gust.
+
+
DynamicServingConfiguration.ClientHintsConfiguration - Interface in gust.backend
+
+
Describes settings related to Client Hints support.
+
+
DynamicServingConfiguration.DynamicETagsConfiguration - Interface in gust.backend
+
+
Describes settings regarding ETag headers for dynamic content.
+
+
DynamicServingConfiguration.DynamicVarianceConfiguration - Interface in gust.backend
+
+
Describes settings regarding Vary headers for dynamic content.
+
+
DynamicServingConfiguration.FeaturePolicyConfiguration - Interface in gust.backend
+
+
Describes settings regarding Feature-Policy headers for dynamic content.
+
+
DynamicServingConfiguration.XSSProtectionConfiguration - Interface in gust.backend
+
+
Describes settings regarding X-XSS-Protection headers for dynamic content.
+
+
+ + + +

E

+
+
empty() - Static method in class gust.backend.PageContext
+
+
Factory to create an empty page context.
+
+
enableCache() - Method in interface gust.backend.model.CacheOptions
+
 
+
enabled() - Method in interface gust.backend.AssetConfiguration.AssetCachingConfiguration
+
+
Whether to enable intelligent HTTP caching for assets served dynamically.
+
+
enabled() - Method in interface gust.backend.AssetConfiguration.AssetCompressionConfiguration
+
+
Whether to enable serving of pre-compressed assets.
+
+
enabled() - Method in interface gust.backend.AssetConfiguration.AssetVarianceConfiguration
+
+
Whether to enable Vary headers at all.
+
+
enabled() - Method in interface gust.backend.AssetConfiguration.ContentDistributionConfiguration
+
+
Whether to enable CDN features.
+
+
enabled() - Method in interface gust.backend.AssetConfiguration.CrossOriginResourceConfiguration
+
+
Whether to enable Cross-Origin-Resource-Policy headers for dynamically-served content.
+
+
enabled() - Method in interface gust.backend.DynamicServingConfiguration.ClientHintsConfiguration
+
+
Whether to enable support for client hints.
+
+
enabled() - Method in interface gust.backend.DynamicServingConfiguration.DynamicETagsConfiguration
+
+
Whether to enable ETag headers for dynamically-served content.
+
+
enabled() - Method in interface gust.backend.DynamicServingConfiguration.DynamicVarianceConfiguration
+
+
Whether to enable Vary headers for dynamically-served content.
+
+
enabled() - Method in interface gust.backend.DynamicServingConfiguration.FeaturePolicyConfiguration
+
+
Whether to enable Feature-Policy headers for dynamically-served content.
+
+
enabled() - Method in interface gust.backend.DynamicServingConfiguration.XSSProtectionConfiguration
+
+
Whether to enable old-style XSS protection.
+
+
enableETags() - Method in interface gust.backend.AssetConfiguration
+
+
Specifies whether to affix ETag values.
+
+
enableETags() - Method in class gust.backend.PageContextManager
+
enableETags(Boolean) - Method in class gust.backend.PageContextManager
+
+
Enable a dynamic ETag header value, which is computed from the rendered content produced by this page + context record.
+
+
enableIndexing() - Method in annotation type gust.backend.annotations.Page
+
+
Whether to allow robots on the selected page.
+
+
enableLastModified() - Method in interface gust.backend.AssetConfiguration
+
+
Specifies whether to affix Last-Modified values.
+
+
enableNoSniff() - Method in interface gust.backend.AssetConfiguration
+
+
Specifies whether to affix a X-Content-Type-Options policy for nosniff.
+
+
enablePrimer() - Method in interface gust.backend.transport.GrpcTransportConfig
+
 
+
enableShared() - Method in interface gust.backend.AssetConfiguration.AssetCachingConfiguration
+
+
Whether to enable a shared-cache directive in the HTTP cache settings.
+
+
enableVary() - Method in interface gust.backend.AssetConfiguration.AssetCompressionConfiguration
+
+
Whether to enable the `Vary` header with regard to compression.
+
+
EncodedModel - Class in gust.backend.model
+
+
Container class for an encoded Protocol Buffer model.
+
+
encoding() - Method in interface gust.backend.DynamicServingConfiguration.DynamicVarianceConfiguration
+
+
Whether to indicate response variance by Accept-Encoding.
+
+
EncodingMode - Enum in gust.backend.model
+
+
Wire format mode to apply when serializing or de-serializing.
+
+
endpoint() - Method in class gust.backend.driver.firestore.FirestoreTransportConfig
+
 
+
endpoint() - Method in interface gust.backend.transport.GrpcTransportConfig
+
 
+
enforceAnyRole(Descriptors.Descriptor, DatapointType...) - Static method in class gust.backend.model.ModelMetadata
+
+
Enforce that a particular schema descriptor matches any of the provided DatapointType + annotations.
+
+
enforceAnyRole(Message, DatapointType...) - Static method in class gust.backend.model.ModelMetadata
+
+
Enforce that a particular datamodel type matches any of the provided DatapointType + annotations.
+
+
enforceRole(Descriptors.Descriptor, DatapointType) - Static method in class gust.backend.model.ModelMetadata
+
+
Enforce that a particular datamodel schema descriptor matches the provided DatapointType + annotation.
+
+
enforceRole(Message, DatapointType) - Static method in class gust.backend.model.ModelMetadata
+
+
Enforce that a particular model instance matches the provided DatapointType annotation.
+
+
engine() - Method in class gust.backend.driver.firestore.FirestoreAdapter
+
+
Return the lower-level DatabaseDriver powering this adapter.
+
+
engine() - Method in class gust.backend.driver.inmemory.InMemoryAdapter
+
+
Return the lower-level PersistenceDriver powering this adapter.
+
+
engine() - Method in class gust.backend.driver.spanner.SpannerAdapter
+
+
Return the lower-level DatabaseDriver powering this adapter.
+
+
engine() - Method in interface gust.backend.model.DatabaseAdapter
+
+
Return the lower-level DatabaseDriver powering this adapter.
+
+
engine() - Method in interface gust.backend.model.ModelAdapter
+
+
Return the lower-level PersistenceDriver powering this adapter.
+
+
entrySet() - Method in class gust.backend.model.SerializedModel
+
 
+
enumsAsNumbers() - Method in interface gust.backend.driver.spanner.SpannerDriverSettings
+
 
+
EQUAL - gust.util.MessageDifferencer.MessageFieldComparison
+
+
Fields must be present in both messages for the messages to be considered the same.
+
+
equals(Message, Message) - Static method in class gust.util.MessageDifferencer
+
+
Determines whether the supplied messages are equal.
+
+
equals(Object) - Method in class gust.backend.model.EncodedModel
+
equals(Object) - Method in class gust.backend.model.ModelMetadata.FieldContainer
+
equals(Object) - Method in class gust.backend.model.ModelMetadata.FieldPointer
+
equals(Object) - Method in class gust.backend.runtime.AssetManager.ManagedAsset
+
 
+
equals(Object) - Method in class gust.backend.runtime.AssetManager.ManagedAssetContent
+
 
+
equivalent(Message, Message) - Static method in class gust.util.MessageDifferencer
+
+
Determines whether the supplied messages are equivalent.
+
+
EQUIVALENT - gust.util.MessageDifferencer.MessageFieldComparison
+
+
Fields with default values are considered set for comparison purposes even if not explicitly + set in the messages themselves.
+
+
etags() - Method in interface gust.backend.DynamicServingConfiguration
+
+
ETag configuration for dynamic content.
+
+
evict(Iterable<Key>, ListeningScheduledExecutorService) - Method in interface gust.backend.model.CacheDriver
+
+
Force-evict the set of cached records specified by keys, in the cache managed by this driver.
+
+
evict(Key, ListeningScheduledExecutorService) - Method in interface gust.backend.model.CacheDriver
+
+
Force-evict any cached record at the provided key in the cache managed by this driver.
+
+
evict(K, ListeningScheduledExecutorService) - Method in class gust.backend.driver.inmemory.InMemoryCache
+
+
Force-evict any cached record at the provided key in the cache managed by this driver.
+
+
EXACT - gust.util.MessageDifferencer.FloatComparison
+
+
Floats and doubles are compared exactly.
+
+
exceptionally(Function<Throwable, ? extends T>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
EXCLUDE - gust.backend.model.FetchOptions.MaskMode
+
+
Omit fields mentioned in the field mask.
+
+
execute(String, WriteProxy<Object>, Optional<CollapsedMessage.Parent>) - Method in interface gust.backend.model.CollapsedMessage.Operation
+
+
Execute the underlying operation against the provided write proxy.
+
+
executorService() - Method in class gust.backend.driver.firestore.FirestoreAdapter
+
+
Resolve an executor service for use with this persistence driver.
+
+
executorService() - Method in class gust.backend.driver.firestore.FirestoreDriver
+
+
Resolve an executor service for use with this persistence driver.
+
+
executorService() - Method in class gust.backend.driver.inmemory.InMemoryDriver
+
+
Resolve an executor service for use with this persistence driver.
+
+
executorService() - Method in class gust.backend.driver.spanner.SpannerAdapter
+
+
Resolve an executor service for use with this persistence driver.
+
+
executorService() - Method in class gust.backend.driver.spanner.SpannerDriver
+
 
+
executorService() - Method in interface gust.backend.model.ModelAdapter
+
+
Resolve an executor service for use with this persistence driver.
+
+
executorService() - Method in interface gust.backend.model.OperationOptions
+
 
+
executorService() - Method in interface gust.backend.model.PersistenceDriver
+
+
Resolve an executor service for use with this persistence driver.
+
+
+ + + +

F

+
+
factory() - Static method in class gust.backend.model.SerializedModel
+
+
Create an empty serialized model, for use as a container.
+
+
factory(SortedMap<String, Value>) - Static method in class gust.backend.model.SerializedModel
+
+
Create a serialized model, pre-filled with the provided backing data.
+
+
failed(Throwable) - Static method in class gust.backend.runtime.ReactiveFuture
+
+
Create an already-failed future, wrapping the provided exception instance.
+
+
featurePolicy() - Method in interface gust.backend.DynamicServingConfiguration
+
+
Feature-Policy configuration for dynamic content.
+
+
fetch(Key) - Method in interface gust.backend.model.PersistenceDriver
+
+
Synchronously retrieve a data model instance from underlying storage, addressed by its unique ID.
+
+
fetch(Key, FetchOptions) - Method in interface gust.backend.model.PersistenceDriver
+
+
Synchronously retrieve a data model instance from underlying storage, addressed by its unique ID.
+
+
fetch(Key, FetchOptions, ListeningScheduledExecutorService) - Method in interface gust.backend.model.CacheDriver
+
+
Attempt to resolve a known model, addressed by key, from the cache powered/backed by this driver, according + to options and making use of executor.
+
+
fetch(K, FetchOptions, ListeningScheduledExecutorService) - Method in class gust.backend.driver.inmemory.InMemoryCache
+
+
Attempt to resolve a known model, addressed by key, from the cache powered/backed by this driver, according + to options and making use of executor.
+
+
fetchAsync(Key) - Method in interface gust.backend.model.PersistenceDriver
+
+
Asynchronously retrieve a data model instance from storage, which will populate the provided Future value.
+
+
fetchAsync(Key, FetchOptions) - Method in interface gust.backend.model.PersistenceDriver
+
+
Asynchronously retrieve a data model instance from storage, which will populate the provided Future value.
+
+
FetchOptions - Interface in gust.backend.model
+
+
Specifies options which may be applied, generically, to model instance fetch operations implemented through the + PersistenceDriver interface.
+
+
FetchOptions.MaskMode - Enum in gust.backend.model
+
+
Enumerates ways the FieldMask may be applied.
+
+
fetchReactive(Key) - Method in interface gust.backend.model.PersistenceDriver
+
+
Reactively retrieve a data model instance from storage, emitting it over a Publisher wrapped in an + Optional.
+
+
fetchReactive(Key, FetchOptions) - Method in interface gust.backend.model.PersistenceDriver
+
+
Reactively retrieve a data model instance from storage, emitting it over a Publisher wrapped in an + Optional.
+
+
fetchSafe(Key) - Method in interface gust.backend.model.PersistenceDriver
+
+
Safely (and synchronously) retrieve a data model instance from storage, returning Optional.empty() if it + cannot be located, rather than null.
+
+
fetchSafe(Key, FetchOptions) - Method in interface gust.backend.model.PersistenceDriver
+
+
Safely (and synchronously) retrieve a data model instance from storage, returning Optional.empty() if it + cannot be located, rather than null.
+
+
fieldAnnotation(Descriptors.FieldDescriptor, GeneratedMessage.GeneratedExtension<DescriptorProtos.FieldOptions, E>) - Static method in class gust.backend.model.ModelMetadata
+
+
Retrieve a field-level annotation, from the provided field schema descriptor, structured by ext.
+
+
fieldAtName(Descriptors.Descriptor, String) - Static method in class gust.backend.model.ModelMetadata.FieldPointer
+
+
Wrap the field at the specified name on the provided model.
+
+
fieldMask() - Method in interface gust.backend.model.FetchOptions
+
 
+
fieldMaskMode() - Method in interface gust.backend.model.FetchOptions
+
 
+
fieldOpts(Descriptors.FieldDescriptor) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Given a resolved Protocol Buffer Descriptors.FieldDescriptor which is considered eligible for interaction + with Spanner, resolve any present field-generic options and annotations via FieldPersistenceOptions.
+
+
fieldOpts(ModelMetadata.FieldPointer) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Given a pre-resolved ModelMetadata.FieldPointer, resolve any present generic FieldPersistenceOptions.
+
+
filter() - Method in interface gust.backend.DynamicServingConfiguration.XSSProtectionConfiguration
+
+
Whether to specify XSS protection as active.
+
+
finalizeResponse(HttpRequest<?>, MutableHttpResponse<T>, T, MessageDigest) - Method in class gust.backend.PageContextManager
+
FIRESTORE - gust.backend.transport.GoogleService
+
+
Google Cloud Firestore (token:
+
+
FirestoreAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message> - Class in gust.backend.driver.firestore
+
+
Defines a built-in database adapter for interacting with Google Cloud Firestore, using business-data models defined + through Protobuf annotated with framework-provided metadata.
+
+
FirestoreDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message> - Class in gust.backend.driver.firestore
+
+
Defines a built-in framework DatabaseDriver for interacting seamlessly with Google Cloud Firestore.
+
+
FirestoreManager - Class in gust.backend.driver.firestore
+
 
+
FirestoreManager() - Constructor for class gust.backend.driver.firestore.FirestoreManager
+
 
+
FirestoreTransportConfig - Class in gust.backend.driver.firestore
+
+
Specifies configuration property bindings for a managed transport channel interacting with Cloud Firestore.
+
+
FirestoreTransportConfig() - Constructor for class gust.backend.driver.firestore.FirestoreTransportConfig
+
 
+
FIXED32 - gust.util.MessageDifferencer.UnknownFieldType
+
+
Fixed32.
+
+
FIXED64 - gust.util.MessageDifferencer.UnknownFieldType
+
+
Fixed64.
+
+
flush(ListeningScheduledExecutorService) - Method in class gust.backend.driver.inmemory.InMemoryCache
+
+
Flush the entire cache managed by this driver.
+
+
flush(ListeningScheduledExecutorService) - Method in interface gust.backend.model.CacheDriver
+
+
Flush the entire cache managed by this driver.
+
+
forEachField(Descriptors.Descriptor, Optional<Predicate<ModelMetadata.FieldPointer>>) - Static method in class gust.backend.model.ModelMetadata
+
+
Crawl all fields, recursively, on the provided descriptor for a model instance.
+
+
forEachField(Descriptors.Descriptor, Optional<Predicate<ModelMetadata.FieldPointer>>, boolean) - Static method in class gust.backend.model.ModelMetadata
+
+
Crawl all fields, recursively, on the provided descriptor for a model instance.
+
+
forEachField(Descriptors.Descriptor, Optional<Predicate<ModelMetadata.FieldPointer>>, Predicate<ModelMetadata.FieldPointer>) - Static method in class gust.backend.model.ModelMetadata
+
+
Crawl all fields, recursively, on the provided descriptor for a model instance.
+
+
forModel(Message.Builder, FirestoreDriver<K, M>) - Static method in class gust.backend.driver.firestore.FirestoreAdapter
+
+
Create or otherwise resolve a FirestoreAdapter for the provided model type and builder.
+
+
forModel(Message.Builder, FirestoreDriver<K, M>, Optional<CacheDriver<K, M>>) - Static method in class gust.backend.driver.firestore.FirestoreAdapter
+
+
Create or otherwise resolve a FirestoreAdapter for the provided model type and builder.
+
+
forModel(SpannerDriver<K, M>) - Static method in class gust.backend.driver.spanner.SpannerAdapter
+
+
Create or resolve a SpannerAdapter for the pre-fabricated SpannerDriver.
+
+
forModel(SpannerDriver<K, M>, Optional<CacheDriver<K, M>>) - Static method in class gust.backend.driver.spanner.SpannerAdapter
+
+
Create or resolve a SpannerAdapter for the pre-fabricated SpannerDriver, optionally using the + provided CacheDriver, if present.
+
+
forModel(M) - Static method in class gust.backend.driver.spanner.SpannerCodec
+
+
Create a Spanner message codec which adapts the provided builder to a Spanner Mutation and back with + default codecs and settings.
+
+
forModel(M) - Static method in class gust.backend.model.ProtoModelCodec
+
+
Acquire a Protobuf model codec for the provided model instance.
+
+
forModel(M, SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerCodec
+
+
Create a Spanner message codec which adapts the provided builder to a Spanner Mutation and back with + default codecs and custom settings.
+
+
forModel(M, SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerStructDeserializer
+
+
Construct a Struct deserializer for the provided instance.
+
+
forModel(M, EncodingMode) - Static method in class gust.backend.model.ProtoModelCodec
+
+
Acquire a Protobuf model codec for the provided model instance.
+
+
forModel(M, EncodingMode, Optional<TypeRegistry>) - Static method in class gust.backend.model.ProtoModelCodec
+
+
Acquire a Protobuf model codec for the provided model instance.
+
+
forModel(M, ModelDeserializer<RI, M>) - Static method in class gust.backend.model.CollapsedMessageCodec
+
+
Create a collapsed message codec which adapts the provided builder to CollapsedMessage and back.
+
+
forModel(M, ModelSerializer<M, Mutation>, ModelDeserializer<Struct, M>) - Static method in class gust.backend.driver.spanner.SpannerCodec
+
+
Create a Spanner message codec which adapts the provided builder to a Spanner Mutation and back.
+
+
forParent(String) - Static method in class gust.backend.driver.spanner.SpannerGeneratedDDL.InterleaveTarget
+
+
Generate an interleave target specification for the provided parent table name.
+
+
forParent(String, Optional<SpannerGeneratedDDL.PropagatedAction>) - Static method in class gust.backend.driver.spanner.SpannerGeneratedDDL.InterleaveTarget
+
+
Generate an interleave target specification for the provided parent table name, optionally applying the + provided propagation action.
+
+
framingPolicy() - Method in interface gust.backend.DynamicServingConfiguration
+
+
X-Frame-Options configuration for dynamic content.
+
+
from(Message) - Static method in class gust.backend.model.EncodedModel
+
+
Return an encoded representation of the provided message.
+
+
from(Message, Descriptors.Descriptor) - Static method in class gust.backend.model.EncodedModel
+
+
Return an encoded representation of the provided message.
+
+
fromMap(Map<String, Object>) - Static method in class gust.backend.PageContext
+
+
Factory to create a page context object from a regular Java map, of string context properties to values of any + object type.
+
+
fromMap(Map<String, Object>, Map<String, Object>) - Static method in class gust.backend.PageContext
+
+
Factory to create a page context object from a regular Java map, of string context properties to values of any + object type.
+
+
fromMap(Map<String, Object>, Map<String, Object>, SoyNamingMapProvider) - Static method in class gust.backend.PageContext
+
+
Factory to create a page context object from a regular Java map, of string context properties to values of any + object type.
+
+
fromMap(Map<String, Object>, Map<String, Object>, SoyNamingMapProvider, SoyContext.SoyI18NContext, Predicate<String>) - Static method in class gust.backend.PageContext
+
+
Factory to create a page context object from a regular Java map, of string context properties to values of any + object type.
+
+
fromProto(Context) - Static method in class gust.backend.PageContext
+
+
Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`.
+
+
fromProto(Context, Map<String, Object>) - Static method in class gust.backend.PageContext
+
+
Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`.
+
+
fromProto(Context, Map<String, Object>, Map<String, Object>) - Static method in class gust.backend.PageContext
+
+
Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`.
+
+
fromProto(Context, Map<String, Object>, Map<String, Object>, SoyNamingMapProvider) - Static method in class gust.backend.PageContext
+
+
Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`.
+
+
fromProto(Context, Map<String, Object>, Map<String, Object>, SoyNamingMapProvider, SoyContext.SoyI18NContext, Predicate<String>) - Static method in class gust.backend.PageContext
+
+
Factory to create a page context object from a proto message containing structured data, which is injected into the + render flow at `context`.
+
+
FULL - gust.util.MessageDifferencer.Scope
+
+
All fields of both messages are considered in the comparison.
+
+
fullyQualifiedName(Descriptors.Descriptor) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve the fully-qualified type path, or name, for the provided datamodel type descriptor.
+
+
fullyQualifiedName(Message) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve the fully-qualified type path, or name, for the provided datamodel instance.
+
+
+ + + +

G

+
+
generateId(Message) - Method in interface gust.backend.model.PersistenceDriver
+
+
Generate a semi-random opaque token, usable as an ID for a newly-created entity via the model layer.
+
+
generateKey(Message) - Method in class gust.backend.driver.firestore.FirestoreDriver
+
+
Generate a key for a new entity, which must be stored by this driver, but does not yet have a key.
+
+
generateKey(Message) - Method in interface gust.backend.model.DatabaseAdapter
+
+
Generate a key for a new entity, which must be stored by this driver, but does not yet have a key.
+
+
generateKey(Message) - Method in interface gust.backend.model.ModelAdapter
+
+
Generate a key for a new entity, which must be stored by this driver, but does not yet have a key.
+
+
generateKey(Message) - Method in interface gust.backend.model.PersistenceDriver
+
+
Generate a key for a new entity, which must be stored by this driver, but does not yet have a key.
+
+
generateStruct(Descriptors.Descriptor, SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Calculate a default projection of Spanner columns, as configured on the provided default model instance.
+
+
generateTableDDL(Descriptors.Descriptor, SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerGeneratedDDL
+
+
Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing + that model's properties.
+
+
generateTableDDL(Message) - Static method in class gust.backend.driver.spanner.SpannerGeneratedDDL
+
+
Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing + that model's properties.
+
+
generateTableDDL(Message, Optional<SpannerDriverSettings>) - Static method in class gust.backend.driver.spanner.SpannerGeneratedDDL
+
+
Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing + that model's properties.
+
+
generic() - Method in class gust.backend.driver.spanner.SpannerManager.ConfiguredSpannerManager
+
+
Acquire a generic adapter instance designed to work with all Message-inheriting model types.
+
+
get() - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
get() - Method in class gust.backend.runtime.ReactiveFuture
+
+
Waits if necessary for the computation to complete, and then retrieves its result.
+
+
get() - Method in class gust.backend.runtime.ReactiveFuture.PublisherListenableFuture
+
 
+
get(long, TimeUnit) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
get(long, TimeUnit) - Method in class gust.backend.runtime.ReactiveFuture
+
+
Waits if necessary for at most the given time for the computation to complete, and then retrieves its result, if + available.
+
+
get(long, TimeUnit) - Method in class gust.backend.runtime.ReactiveFuture.PublisherListenableFuture
+
 
+
get(Object) - Method in class gust.backend.model.SerializedModel
+
 
+
get(String) - Method in class gust.backend.PageContextManager
+
+
Safely retrieve a value from the current render context properties.
+
+
get(String, boolean) - Method in class gust.backend.PageContextManager
+
+
Safely retrieve a value from either the current render context properties, or the current injected values.
+
+
getAssets() - Method in class gust.backend.runtime.AssetManager.ManagedAsset
+
 
+
getBase() - Method in class gust.backend.model.ModelMetadata.FieldPointer
+
 
+
getBuilder() - Method in class gust.backend.model.CollapsedMessageCodec
+
 
+
getCache() - Method in class gust.backend.driver.spanner.SpannerManager.Builder
+
 
+
getCache() - Method in class gust.backend.driver.spanner.SpannerManager.ConfiguredSpannerManager
+
 
+
getCdnPrefix() - Method in class gust.backend.PageContextManager
+
+
Retrieve the currently-configured CDN prefix value, if one exists.
+
+
getClosed() - Method in class gust.backend.driver.spanner.SpannerManager.ConfiguredSpannerManager
+
 
+
getColumns() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.Builder
+
 
+
getColumns() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL
+
 
+
getCompressedSize() - Method in class gust.backend.runtime.AssetManager.ManagedAssetContent
+
 
+
getCompressionOptions() - Method in class gust.backend.runtime.AssetManager.ManagedAssetContent
+
 
+
getConfigType() - Method in enum gust.backend.transport.GoogleService
+
 
+
getContent() - Method in class gust.backend.runtime.AssetManager.ManagedAsset
+
 
+
getContent() - Method in class gust.backend.runtime.AssetManager.ManagedAssetContent
+
+
Retrieve the content backing this info record.
+
+
getContext() - Method in class gust.backend.PageContextManager
+
 
+
getData() - Method in class gust.backend.model.SerializedModel
+
 
+
getDatabase() - Method in class gust.backend.driver.spanner.SpannerManager.Builder
+
 
+
getDatabase() - Method in class gust.backend.driver.spanner.SpannerManager.ConfiguredSpannerManager
+
 
+
getDataMode() - Method in class gust.backend.model.EncodedModel
+
 
+
getDatapointTypes() - Method in exception gust.backend.model.InvalidModelType
+
 
+
getDynamicAssetPrefix() - Static method in class gust.Core
+
+
The dynamic asset prefix is a URL prefix under which frontend application assets are served, using a managed + file and token scheme, with a generated binary asset manifest living at the root of the JAR.
+
+
getEngine() - Static method in class gust.Core
+
+
Retrieve the current engine running this code.
+
+
getETag() - Method in class gust.backend.runtime.AssetManager.ManagedAssetContent
+
 
+
getExecutor() - Method in class gust.backend.driver.spanner.SpannerManager.Builder
+
 
+
getExtraInterceptors() - Method in interface gust.backend.transport.GrpcTransportConfig
+
 
+
getFailedExpectation() - Method in exception gust.backend.model.ModelWriteConflict
+
 
+
getFailure() - Method in exception gust.backend.model.PersistenceOperationFailed
+
 
+
getField() - Method in class gust.backend.model.ModelMetadata.FieldContainer
+
+
Pointer to the field holding the specified value.
+
+
getField() - Method in class gust.backend.model.ModelMetadata.FieldPointer
+
 
+
getField() - Method in class gust.util.MessageDifferencer.SpecificField
+
+
Returns the descriptor for known fields, or null for unknown fields.
+
+
getFieldNumber() - Method in class gust.util.MessageDifferencer.UnknownDescriptor
+
+
Returns the field number.
+
+
getFieldType() - Method in class gust.util.MessageDifferencer.UnknownDescriptor
+
+
Returns the field type.
+
+
getFilename() - Method in class gust.backend.runtime.AssetManager.ManagedAssetContent
+
 
+
getGeneratedStatement() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL
+
 
+
getGustVersion() - Static method in class gust.Core
+
+
Retrieve the application version setting, which is applied via the JVM system property
+
+
getHints() - Method in class gust.backend.PageContextManager
+
+
Return the set of interpreted Client Hints headers for the current request.
+
+
getIndex() - Method in class gust.util.MessageDifferencer.SpecificField
+
+
Returns the field index.
+
+
getInjectedProperties(Map<String, Object>) - Method in class gust.backend.PageContext
+
+
Retrieve properties and values that should be made available via `@inject`.
+
+
getInjectedProperties(Map<String, Object>) - Method in class gust.backend.PageContextManager
+
+
Retrieve properties and values that should be made available via `@inject`.
+
+
getInterleaveTarget() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.Builder
+
 
+
getJsonName() - Method in class gust.backend.model.ModelMetadata.FieldPointer
+
 
+
getKeepaliveEnabled() - Method in class gust.backend.driver.firestore.FirestoreTransportConfig
+
 
+
getKeepaliveEnabled() - Method in interface gust.backend.transport.TransportConfig
+
 
+
getKeepAliveNoActivity() - Method in class gust.backend.driver.firestore.FirestoreTransportConfig
+
 
+
getKeepAliveNoActivity() - Method in interface gust.backend.transport.GrpcTransportConfig
+
 
+
getKeepaliveTime() - Method in class gust.backend.driver.firestore.FirestoreTransportConfig
+
 
+
getKeepaliveTime() - Method in interface gust.backend.transport.TransportConfig
+
 
+
getKeepaliveTimeout() - Method in class gust.backend.driver.firestore.FirestoreTransportConfig
+
 
+
getKeepaliveTimeout() - Method in interface gust.backend.transport.TransportConfig
+
 
+
getKey() - Method in exception gust.backend.model.ModelWriteFailure
+
 
+
getKey() - Method in class gust.util.Pair
+
 
+
getKeySortDirection() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.Builder
+
 
+
getLabel() - Method in enum gust.backend.model.CacheOptions.EvictionMode
+
 
+
getLastModified() - Method in class gust.backend.runtime.AssetManager.ManagedAssetContent
+
 
+
getMaxPoolSize() - Method in class gust.backend.transport.GoogleTransportManager
+
 
+
getMessage() - Method in class gust.backend.model.SerializedModel
+
 
+
getModel() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.Builder
+
 
+
getModel() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL
+
 
+
getModel() - Method in exception gust.backend.model.ModelWriteFailure
+
 
+
getModule() - Method in class gust.backend.runtime.AssetManager.ManagedAssetContent
+
 
+
getName() - Method in class gust.backend.model.ModelMetadata.FieldPointer
+
 
+
getName() - Method in class gust.backend.runtime.AssetManager.ManagedAsset
+
 
+
getNewIndex() - Method in class gust.util.MessageDifferencer.SpecificField
+
+
Returns the new field index.
+
+
getOpenGraph() - Method in class gust.backend.PageContextManager
+
+
Retrieve OpenGraph settings specified in the current page context.
+
+
getOptimalCompression() - Method in class gust.backend.runtime.AssetManager.ManagedAssetContent
+
 
+
getOptimizerVersion() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.Builder
+
 
+
getOptions() - Method in class gust.backend.driver.spanner.SpannerManager.Builder
+
 
+
getPageContext() - Method in class gust.backend.PageContext
+
+
Retrieve serializable server-side-rendered page context, which should be assigned to the render flow bound to this + context mediator.
+
+
getPageContext() - Method in class gust.backend.PageContextManager
+
+
Retrieve serializable server-side-rendered page context, which should be assigned to the render flow bound to this + context mediator.
+
+
getPageContext() - Method in interface gust.backend.PageRender
+
+
Retrieve serializable server-side-rendered page context, which should be assigned to the render flow bound to this + context mediator.
+
+
getParent() - Method in interface gust.backend.model.CollapsedMessage.Operation
+
 
+
getParent() - Method in class gust.backend.model.ModelMetadata.FieldPointer
+
 
+
getPath() - Method in interface gust.backend.model.CollapsedMessage.Operation
+
 
+
getPath() - Method in class gust.backend.model.ModelMetadata.FieldPointer
+
 
+
getPoolSize() - Method in class gust.backend.driver.firestore.FirestoreTransportConfig
+
 
+
getPoolSize() - Method in interface gust.backend.transport.PooledTransportConfig
+
 
+
getPrimaryKey() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.Builder
+
 
+
getProperties() - Method in class gust.backend.PageContext
+
+
Retrieve properties which should be made available via regular, declared `@param` statements.
+
+
getProperties() - Method in class gust.backend.PageContextManager
+
+
Retrieve properties which should be made available via regular, declared `@param` statements.
+
+
getRawBytes() - Method in class gust.backend.model.EncodedModel
+
 
+
getRequest() - Method in class gust.backend.PageContextManager
+
+
Return the currently-active HTTP request object, to which the current render/controller flow is bound.
+
+
getRequiredField() - Method in exception gust.backend.model.MissingAnnotatedField
+
 
+
getSettings() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.Builder
+
 
+
getSettings() - Method in class gust.backend.driver.spanner.SpannerManager.Builder
+
 
+
getSettings() - Method in class gust.backend.driver.spanner.SpannerManager.ConfiguredSpannerManager
+
 
+
getSize() - Method in class gust.backend.runtime.AssetManager.ManagedAssetContent
+
 
+
getTableConstraints() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.Builder
+
 
+
getTableName() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.Builder
+
 
+
getTableName() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL
+
 
+
getToken() - Method in class gust.backend.runtime.AssetManager.ManagedAssetContent
+
 
+
getToken() - Method in enum gust.backend.transport.GoogleService
+
 
+
getType() - Method in class gust.backend.model.EncodedModel
+
 
+
getType() - Method in class gust.backend.runtime.AssetManager.ManagedAsset
+
 
+
getUnknown() - Method in class gust.util.MessageDifferencer.SpecificField
+
+
Returns the descriptor for unknown fields, or null for known fields.
+
+
getValue() - Method in class gust.backend.model.ModelMetadata.FieldContainer
+
+
Value associated with the specified field, or Optional.empty() if the field has no initialized value.
+
+
getValue() - Method in class gust.util.Pair
+
 
+
getValues(UnknownFieldSet.Field) - Method in enum gust.util.MessageDifferencer.UnknownFieldType
+
+
Returns the corresponding values from the given field.
+
+
getVariantCount() - Method in class gust.backend.runtime.AssetManager.ManagedAssetContent
+
 
+
getVersionRetentionPeriod() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.Builder
+
 
+
getViolatingSchema() - Method in exception gust.backend.model.InvalidModelType
+
 
+
getViolatingSchema() - Method in exception gust.backend.model.MissingAnnotatedField
+
 
+
getWireFormat() - Method in enum gust.util.MessageDifferencer.UnknownFieldType
+
+
Returns the wire format for this unknown field type.
+
+
GoogleAPIChannel - Annotation Type in gust.backend.transport
+
+
Specifies an injection qualifier for a managed transport supporting the service specified by this annotation.
+
+
GOOGLEBOT_SETTINGS - Static variable in annotation type gust.backend.annotations.Page
+
+
Property for Googlebot-specific settings.
+
+
googlebotSettings() - Method in annotation type gust.backend.annotations.Page
+
+
Settings for Googlebot on the selected page.
+
+
GoogleService - Enum in gust.backend.transport
+
+
Enumerates Google Cloud services with built-in managed transport support.
+
+
GoogleTransportConfig - Interface in gust.backend.transport
+
+
Provides sensible defaults and additional configuration when applying transport settings specifically to Google- + provided or hosted services.
+
+
GoogleTransportManager - Class in gust.backend.transport
+
+
Supplies a TransportManager implementation for dealing with Google Cloud APIs via gRPC and Protobuf.
+
+
GROUP - gust.util.MessageDifferencer.UnknownFieldType
+
+
Group.
+
+
GrpcTransportConfig - Interface in gust.backend.transport
+
+
Specifies transport configuration properties specific to gRPC ManagedChannel objects.
+
+
GrpcTransportCredentials - Interface in gust.backend.transport
+
+
Transport-layer credentials configuration for use with managed gRPC connections.
+
+
gust - package gust
+
+
Provides logic and framework implementation structure for Gust.
+
+
gust.backend - package gust.backend
+
+
Defines backend logic for Gust-based applications.
+
+
gust.backend.annotations - package gust.backend.annotations
+
+
Provides annotations for backend Java applications.
+
+
gust.backend.driver.firestore - package gust.backend.driver.firestore
+
+
Provides a DatabaseDriver implementation, using Google Cloud Firestore.
+
+
gust.backend.driver.inmemory - package gust.backend.driver.inmemory
+
+
Provides a reference implementation of a persistence driver, backed by a concurrent map.
+
+
gust.backend.driver.spanner - package gust.backend.driver.spanner
+
+
Provides a DatabaseDriver implementation for integration with Google Cloud Spanner.
+
+
gust.backend.model - package gust.backend.model
+
+
Provides definitions and structure for logic dealing with business data.
+
+
gust.backend.runtime - package gust.backend.runtime
+
+
Supplies core backend runtime logic, like execution/scheduling, logging, and so on.
+
+
gust.backend.transport - package gust.backend.transport
+
+
Provides tools for managing data in motion between computers.
+
+
gust.util - package gust.util
+
+
Provides generic pure-Java utility objects and classes.
+
+
+ + + +

H

+
+
handle(BiFunction<? super T, Throwable, ? extends U>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
handleAsync(BiFunction<? super T, Throwable, ? extends U>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
handleAsync(BiFunction<? super T, Throwable, ? extends U>, Executor) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
hashCode() - Method in class gust.backend.model.EncodedModel
+
hashCode() - Method in class gust.backend.model.ModelMetadata.FieldContainer
+
hashCode() - Method in class gust.backend.model.ModelMetadata.FieldPointer
+
hashCode() - Method in class gust.backend.runtime.AssetManager.ManagedAsset
+
 
+
hashCode() - Method in class gust.backend.runtime.AssetManager.ManagedAssetContent
+
 
+
hasParent() - Method in class gust.backend.model.ModelMetadata.FieldPointer
+
 
+
header(String, String) - Method in class gust.backend.PageContextManager
+
+
Affix an arbitrary HTTP header to the response eventually produced by this page context, assuming no errors occur.
+
+
header(String, String, Boolean) - Method in class gust.backend.PageContextManager
+
+
Affix an arbitrary HTTP header to the response eventually produced by this page context, assuming no errors occur.
+
+
Hex - Class in gust.util
+
+
Provides utilities for encoding values into hex, or decoding values from hex.
+
+
hint(Context.ClientHint) - Method in class gust.backend.PageContextManager
+
+
Attempt to retrieve an interpreted Client Hints client-indicated value from the current HTTP request.
+
+
hints() - Method in interface gust.backend.DynamicServingConfiguration.ClientHintsConfiguration
+
+
Return the set of hints supported by the server.
+
+
hostnames() - Method in interface gust.backend.AssetConfiguration.ContentDistributionConfiguration
+
+
CDN host names to use for assets.
+
+
HTML - Static variable in class gust.backend.AppController
+
+
Pre-ordained HTML object which ensures the character encoding is set to UTF-8.
+
+
httpCaching() - Method in interface gust.backend.AssetConfiguration
+
+
Specifies settings for HTTP caching.
+
+
+ + + +

I

+
+
id(Message) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve the provided model instance's assigned ID, by walking the property structure for the entity, and returning + either the first id-annotated field's value at the top-level, or the first id-annotated field value + on the first key-annotated message field at the top level of the provided message.
+
+
idField(Descriptors.Descriptor) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve a pointer to the provided schema type descriptor's ID field, whether or not it has a value.
+
+
idField(Message) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve a pointer to the provided model instance's ID field, whether or not it has a value.
+
+
idRenamingMap() - Method in class gust.backend.TemplateProvider
+
+
By default, return `null` for the Soy ID renaming map.
+
+
IGNORED - gust.util.MessageDifferencer.ReportType
+
 
+
ignoreField(Descriptors.FieldDescriptor) - Method in class gust.util.MessageDifferencer.Builder
+
+
Indicates that any field with the given descriptor should be ignored for the purposes of + comparing two messages.
+
+
INCLUDE - gust.backend.model.FetchOptions.MaskMode
+
+
Only include fields mentioned in the field mask.
+
+
inflate(Struct) - Method in class gust.backend.driver.spanner.SpannerStructDeserializer
+
 
+
inflate(Message) - Method in class gust.backend.model.EncodedModel
+
+
Re-inflate the encoded model data held by this object, into an instance of Model, via the provided + builder.
+
+
inflate(Input) - Method in interface gust.backend.model.ModelDeserializer
+
+
De-serialize a model instance from the provided input type, throwing exceptions verbosely if we are unable to + correctly, verifiably, and properly load the record.
+
+
inject(String, Object) - Method in class gust.backend.PageContextManager
+
+
Install an injected context value, at the named key provided.
+
+
InMemoryAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message> - Class in gust.backend.driver.inmemory
+
+
Reference implementation of a ModelAdapter.
+
+
InMemoryCache<K extends com.google.protobuf.Message,​M extends com.google.protobuf.Message> - Class in gust.backend.driver.inmemory
+
+
Defines a CacheDriver backed by a Guava in-memory cache, which statically holds onto cached full model + instances, potentially on behalf of some other persistence driver (via use with a ModelAdapter).
+
+
InMemoryCache() - Constructor for class gust.backend.driver.inmemory.InMemoryCache
+
 
+
InMemoryDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message> - Class in gust.backend.driver.inmemory
+
+
Proxies calls to a static concurrent map, held by a private singleton.
+
+
InMemoryManager - Class in gust.backend.driver.inmemory
+
 
+
InMemoryManager() - Constructor for class gust.backend.driver.inmemory.InMemoryManager
+
 
+
installRenamingMaps(SoyCssRenamingMap, SoyIdRenamingMap) - Method in class gust.backend.TemplateProvider
+
+
Install a Soy-compatible style renaming map for CSS classes, and optionally one for CSS IDs as well.
+
+
installTemplates(SoySauce) - Method in class gust.backend.TemplateProvider
+
+
Install a set of compiled templates manually into the local template provider context.
+
+
instance() - Method in class gust.backend.driver.spanner.SpannerCodec
+
 
+
instance() - Method in class gust.backend.model.CollapsedMessageCodec
+
 
+
instance() - Method in interface gust.backend.model.ModelCodec
+
+
Retrieve the default instance stored with this codec.
+
+
instance() - Method in class gust.backend.model.ProtoModelCodec
+
 
+
instant(Timestamp) - Static method in class gust.util.InstantFactory
+
+
Convert a Protocol Buffers Timestamp record to a Java Instant record.
+
+
InstantFactory - Class in gust.util
+
+
Utilities to convert between different temporal instant records.
+
+
InstantFactory() - Constructor for class gust.util.InstantFactory
+
 
+
intercept(MethodInvocationContext<Object, Channel>) - Method in class gust.backend.transport.GoogleTransportManager
+
+
Provide acquired connections via injection-annotated Channel method or constructor parameters.
+
+
INTERNAL - gust.backend.model.PersistenceFailure
+
+
An unknown internal error occurred.
+
+
INTERRUPTED - gust.backend.model.PersistenceFailure
+
+
The operation was interrupted.
+
+
InvalidModelType - Exception in gust.backend.model
+
+
Specifies an error, wherein a user has requested a data adapter or some other database object, for a model which is + not usable with data storage systems (via annotations).
+
+
isCancelled() - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
isCancelled() - Method in class gust.backend.runtime.ReactiveFuture
+
+
Returns true if this task was cancelled before it completed normally.
+
+
isCancelled() - Method in class gust.backend.runtime.ReactiveFuture.PublisherListenableFuture
+
 
+
isDebugMode() - Static method in class gust.Core
+
+
Returns the current debug-mode flag state, which indicates whether we are running in a debugging-compatible mode or + not.
+
+
isDevMode() - Static method in class gust.Core
+
+
Returns the current dev-mode flag state, which indicates whether we are running locally/in a development context.
+
+
isDone() - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
isDone() - Method in class gust.backend.runtime.ReactiveFuture
+
+
Returns true if this task completed.
+
+
isDone() - Method in class gust.backend.runtime.ReactiveFuture.PublisherListenableFuture
+
 
+
isEmpty() - Method in class gust.backend.model.SerializedModel
+
 
+
isIgnored(Message, Message, Descriptors.FieldDescriptor, List<MessageDifferencer.SpecificField>) - Method in interface gust.util.MessageDifferencer.IgnoreCriteria
+
+
Should this field be ignored during the comparison.
+
+
isLiveReload() - Method in class gust.backend.PageContextManager
+
+
Indicate whether live-reload mode is enabled or not, which is governed by the built toolchain (i.e.
+
+
isMatch(MessageDifferencer, Message, Message, List<MessageDifferencer.SpecificField>) - Method in interface gust.util.MessageDifferencer.MapKeyComparator
+
+
Decides whether the given messages match with respect to the keys of the map entries they + represent.
+
+
ISO8601 - gust.backend.model.ModelSerializer.InstantSerializeMode
+
+
Encode temporal instants as ISO8601-formatted strings.
+
+
isProductionMode() - Static method in class gust.Core
+
+
"Production mode" is so-called because both Core.isDebugMode() and Core.isDevMode() return `false`.
+
+
+ + + +

J

+
+
Js - Annotation Type in gust.backend.annotations
+
+
Links a script module to a given controller endpoint, such that it will automatically be included and loaded in the + head of the page with the specified settings.
+
+
JS - gust.backend.runtime.AssetManager.ModuleType
+
+
The bundle contains JavaScript code.
+
+
JSON - gust.backend.model.EncodingMode
+
+
Use Protobuf JSON serialization.
+
+
+ + + +

K

+
+
key(Message) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve the provided model instance's assigned KEY instance, by walking the property structure for the + entity, and returning the first key-annotated field's value at the top-level of the provided message.
+
+
keyField(Descriptors.Descriptor) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve a pointer to the provided schema type descriptor's KEY field, whether or not it has a value + assigned.
+
+
keyField(Message) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve a pointer to the provided schema type descriptor's KEY field, whether or not it has a value + assigned.
+
+
keySet() - Method in class gust.backend.model.SerializedModel
+
 
+
keywords() - Method in class gust.backend.PageContextManager
+
+
Retrieve the current value for the page keywords, set in the builder.
+
+
+ + + +

L

+
+
language() - Method in interface gust.backend.AssetConfiguration.AssetVarianceConfiguration
+
+
Whether to vary based on Accept-Language.
+
+
language() - Method in interface gust.backend.DynamicServingConfiguration.DynamicVarianceConfiguration
+
+
Whether to indicate response variance by Accept-Language.
+
+
language() - Method in interface gust.backend.DynamicServingConfiguration
+
+
Value to set for Content-Language.
+
+
language() - Method in class gust.backend.PageContextManager
+
+
Return the language value set for the current render routine - i.e.
+
+
language(Optional<String>) - Method in class gust.backend.PageContextManager
+
+
Set the value to send back in this response's Content-Language header.
+
+
LAST_MODIFIED - Static variable in annotation type gust.backend.annotations.Page
+
+
Property name for the page's last-modified date.
+
+
lastModified() - Method in annotation type gust.backend.annotations.Page
+
+
Retrieve the last-modified string for this page.
+
+
LENGTH_DELIMITED - gust.util.MessageDifferencer.UnknownFieldType
+
+
Length delimited.
+
+
LFU - gust.backend.model.CacheOptions.EvictionMode
+
+
Least-Frequently-Used mode for cache eviction.
+
+
links() - Method in class gust.backend.PageContextManager
+
+
Retrieve the full set of regular HTML metadata links attached to the current render flow.
+
+
load() - Static method in class gust.backend.ApplicationBoot
+
+
Load main application configs, including the `app` config (usually `application.yml`), containing configuration for + Micronaut, and `logback.xml` which contains configuration for logging.
+
+
load() - Static method in class gust.backend.runtime.AssetManager
+
+
Attempt to force-load the asset manifest, in a static context, preparing our indexed read-only data regarding the + data it contains.
+
+
loadConfig(String, String, String) - Static method in class gust.backend.ApplicationBoot
+
+
Attempt to load a given global config file, failing if we can't find it in the expected spot, or the backup spot, + optionally provided as the second param.
+
+
logger(Class) - Static method in class gust.backend.runtime.Logging
+
+
Retrieve a logger for a particular Java class, named after the fully-qualified path to the class.
+
+
Logging - Class in gust.backend.runtime
+
+
Sugar bridge to SLF4J.
+
+
LRU - gust.backend.model.CacheOptions.EvictionMode
+
+
Least-Recently-Used mode for cache eviction.
+
+
+ + + +

M

+
+
main(String[]) - Static method in class gust.backend.Application
+
+
Main entrypoint into a Gust backend application, powered by Micronaut.
+
+
matchAnyRole(Descriptors.Descriptor, DatapointType...) - Static method in class gust.backend.model.ModelMetadata
+
+
Check that a particular schema descriptor matches any of the provided DatapointType + annotations.
+
+
matchAnyRole(Message, DatapointType...) - Static method in class gust.backend.model.ModelMetadata
+
+
Check that a particular datamodel type matches any of the provided DatapointType annotations.
+
+
matchCollectionAnnotation(Descriptors.FieldDescriptor, CollectionMode) - Static method in class gust.backend.model.ModelMetadata
+
+
Match a collection annotation.
+
+
MATCHED - gust.util.MessageDifferencer.ReportType
+
+
Reports that two fields match.
+
+
matchFieldAnnotation(Descriptors.FieldDescriptor, FieldType) - Static method in class gust.backend.model.ModelMetadata
+
+
Match an annotation to a field.
+
+
matchRole(Descriptors.Descriptor, DatapointType) - Static method in class gust.backend.model.ModelMetadata
+
+
Enforce that a particular datamodel schema descriptor matches any of the provided + DatapointType annotations.
+
+
matchRole(Message, DatapointType) - Static method in class gust.backend.model.ModelMetadata
+
+
Enforce that a particular datamodel type matches any of the provided DatapointType annotations.
+
+
maxInboundMessageSize() - Method in interface gust.backend.transport.GrpcTransportConfig
+
 
+
maxInboundMetadataSize() - Method in interface gust.backend.transport.GrpcTransportConfig
+
 
+
maybeWrapType(ModelMetadata.FieldPointer, Type) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
If a concrete type is marked as repeated, wrap it in an array type.
+
+
media() - Method in annotation type gust.backend.annotations.Style
+
+
Media type to apply to the injected spreadsheet, if applicable.
+
+
messageBundle() - Method in class gust.backend.PageContext
+
+
Return the pre-fabricated Soy message bundle, or the interpreted Soy message bundle based on the installed messages + file or resource URL.
+
+
messageBundle() - Method in class gust.backend.PageContextManager
+
+
Return the current pre-fabricated Soy message bundle that will be applied during the next render routine, if + available.
+
+
messageBundle(Optional<SoyMsgBundle>) - Method in class gust.backend.PageContextManager
+
+
Mount a pre-fabricated Soy message bundle for translation use during render.
+
+
MessageDifferencer - Class in gust.util
+
+
Static methods and classes for comparing Protocol Messages.
+
+
MessageDifferencer.Builder - Class in gust.util
+
+
Builder object for MessageDifferencer.
+
+
MessageDifferencer.DefaultFieldComparator - Class in gust.util
+
+
Basic implementation of FieldComparator.
+
+
MessageDifferencer.FieldComparator - Interface in gust.util
+
+
Interface for comparing protocol buffer fields.
+
+
MessageDifferencer.FieldComparator.ComparisonResult - Enum in gust.util
+
+ +
+
MessageDifferencer.FloatComparison - Enum in gust.util
+
+
How float and double fields in messages are compared.
+
+
MessageDifferencer.IgnoreCriteria - Interface in gust.util
+
+
IgnoreCriteria are registered with addIgnoreCriteria.
+
+
MessageDifferencer.MapKeyComparator - Interface in gust.util
+
+
MapKeyComparator is used to determine if two elements have the same key when comparing elements + of a repeated field as a map.
+
+
MessageDifferencer.MessageFieldComparison - Enum in gust.util
+
+
The type of comparison that is used by the differencer when determining how to compare fields + in messages.
+
+
MessageDifferencer.RepeatedFieldComparison - Enum in gust.util
+
+
How to compare repeated fields.
+
+
MessageDifferencer.Reporter - Interface in gust.util
+
+
Interface by which callers can receive information about each difference.
+
+
MessageDifferencer.ReportType - Enum in gust.util
+
+
The type of the reported difference.
+
+
MessageDifferencer.Scope - Enum in gust.util
+
+
Which fields to consider when comparing messages.
+
+
MessageDifferencer.SpecificField - Class in gust.util
+
+
Identifies an individual field in a message instance.
+
+
MessageDifferencer.StreamReporter - Class in gust.util
+
+
A message difference reporter that writes a textual description of the differences to a + character stream.
+
+
MessageDifferencer.StreamReporter.StreamException - Exception in gust.util
+
+
I/O exceptions that occur during reporting are wrapped by this type.
+
+
MessageDifferencer.UnknownDescriptor - Class in gust.util
+
+
Unknown field information.
+
+
MessageDifferencer.UnknownFieldType - Enum in gust.util
+
+
The wire type of unknown fields.
+
+
messagesFile() - Method in class gust.backend.PageContext
+
+
Return the file that should be loaded and interpreted to perform translation when rendering this page.
+
+
messagesFile() - Method in class gust.backend.PageContextManager
+
+
Return the current translation file that will be applied during the next render routine, if available.
+
+
messagesFile(Optional<File>) - Method in class gust.backend.PageContextManager
+
+
Mount a loaded XLIFF file for use during render.
+
+
messagesResource() - Method in class gust.backend.PageContext
+
+
Return the URL to the resource that should be loaded and interpreted to perform translation when rendering this + page.
+
+
messagesResource() - Method in class gust.backend.PageContextManager
+
+
Return the current translation resource that will be applied during the next render routine, if available.
+
+
messagesResource(Optional<URL>) - Method in class gust.backend.PageContextManager
+
+
Load and mount a referenced XLIFF resource for use during render.
+
+
MissingAnnotatedField - Exception in gust.backend.model
+
+
Specifies that a model was missing a required annotated-field for a given operation.
+
+
mode() - Method in interface gust.backend.AssetConfiguration.AssetCachingConfiguration
+
+
Main mode to apply with regard to HTTP caching for assets served dynamically.
+
+
ModelAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadIntermediate,​WriteIntermediate> - Interface in gust.backend.model
+
+
Specifies an adapter for data models.
+
+
modelAnnotation(Descriptors.Descriptor, GeneratedMessage.GeneratedExtension<DescriptorProtos.MessageOptions, E>, Boolean) - Static method in class gust.backend.model.ModelMetadata
+
+
Retrieve a model-level annotation, from the provided model schema descriptor, structured by ext.
+
+
modelAnnotation(Message, GeneratedMessage.GeneratedExtension<DescriptorProtos.MessageOptions, E>, Boolean) - Static method in class gust.backend.model.ModelMetadata
+
+
Retrieve a model-level annotation, from instance, structured by ext.
+
+
ModelCodec<Model extends com.google.protobuf.Message,​WriteIntermediate,​ReadIntermediate> - Interface in gust.backend.model
+
+
Specifies the requisite interface for a data codec implementation.
+
+
ModelDeflateException - Exception in gust.backend.model
+
+
Describes an error that occurred while serializing a model.
+
+
ModelDeserializer<Input,​Model extends com.google.protobuf.Message> - Interface in gust.backend.model
+
+
Describes the interface for a de-serializer, which is responsible for transitioning (adapting) objects from a given + type (or tree of types) to Message instances, corresponding with the data record type managed by this object.
+
+
ModelDeserializer.DeserializationError - Exception in gust.backend.model
+
+
Describes errors that occur during model deserialization or inflation activities.
+
+
ModelInflateException - Exception in gust.backend.model
+
+
Describes an error that occurred while de-serializing a model.
+
+
ModelMetadata - Class in gust.backend.model
+
+
Utility helper class, which is responsible for resolving metadata (based on the core framework annotations) from + arbitrary model definitions.
+
+
ModelMetadata.FieldContainer<V> - Class in gust.backend.model
+
+
Utility class that holds a ModelMetadata.FieldPointer and matching field value.
+
+
ModelMetadata.FieldPointer - Class in gust.backend.model
+
+
Utility class that points to a specific field, in a specific context.
+
+
ModelSerializer<Model extends com.google.protobuf.Message,​Output> - Interface in gust.backend.model
+
+
Describes the surface interface of an object responsible for serializing business data objects (hereinafter, + "models").
+
+
ModelSerializer.EnumSerializeMode - Enum in gust.backend.model
+
+
Enumerates modes for encoding enums.
+
+
ModelSerializer.InstantSerializeMode - Enum in gust.backend.model
+
+
Enumerates modes for encoding timestamps.
+
+
ModelSerializer.SerializationError - Exception in gust.backend.model
+
+
Describes errors that occur during model serialization activities.
+
+
ModelSerializer.WriteDisposition - Enum in gust.backend.model
+
+
Describes available write dispositions, each of which presents a strategy that governs how an individual + write operation is handled with regard to underlying storage.
+
+
ModelWriteConflict - Exception in gust.backend.model
+
+
Thrown when a write operation fails, because of some conflict situation.
+
+
ModelWriteConflict(Object, Message, WriteOptions.WriteDisposition) - Constructor for exception gust.backend.model.ModelWriteConflict
+
+
Create a model write exception with a throwable as a cause.
+
+
ModelWriteFailure - Exception in gust.backend.model
+
+
Thrown when a model fails to write.
+
+
MODIFIED - gust.util.MessageDifferencer.ReportType
+
+
A field has been modified.
+
+
module() - Method in annotation type gust.backend.annotations.Js
+
+
Whether to treat this script entry as a module.
+
+
MOVED - gust.util.MessageDifferencer.ReportType
+
+
A repeated field has been moved to another location.
+
+
MUST_EXIST - gust.backend.model.WriteOptions.WriteDisposition
+
+
The record must exist for the write to proceed (an update operation).
+
+
MUST_NOT_EXIST - gust.backend.model.WriteOptions.WriteDisposition
+
+
The record must not exist for the write to proceed (a create operation).
+
+
+ + + +

N

+
+
NAME - gust.backend.model.ModelSerializer.EnumSerializeMode
+
+
Encode enum values as their string name.
+
+
named(String, String) - Static method in class gust.backend.driver.spanner.SpannerGeneratedDDL.TableConstraint
+
+
Spawn a table constraint at the provided name, enforcing the provided expression.
+
+
newBuilder() - Static method in class gust.util.MessageDifferencer
+
+
Creates a new builder.
+
+
NO_ACTION - gust.backend.driver.spanner.SpannerGeneratedDDL.PropagatedAction
+
+
Take no action.
+
+
NO_VALUE - Static variable in annotation type gust.backend.annotations.Page
+
+
Sentinel for no-value strings.
+
+
noModule() - Method in annotation type gust.backend.annotations.Js
+
+
Whether to treat this script entry as a module fallback.
+
+
noSniff() - Method in interface gust.backend.DynamicServingConfiguration
+
+
Whether to apply nosniff to X-Content-Type-Options for dynamic content.
+
+
NUMERIC - gust.backend.model.ModelSerializer.EnumSerializeMode
+
+
Encode enum values as their numeric ID.
+
+
+ + + +

O

+
+
of(boolean) - Static method in enum gust.util.MessageDifferencer.FieldComparator.ComparisonResult
+
+ +
+
of(List<CollapsedMessage.Operation>) - Static method in class gust.backend.model.CollapsedMessage
+
+
Wrap a set of pre-built operations in a collapsed message record.
+
+
of(K, V) - Static method in class gust.util.Pair
+
+
Spawn a new K-V pair.
+
+
onlySpannerEligibleFields(SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Return a Predicate implementation which operates on ModelMetadata.FieldPointer objects to determine eligibility + for interaction with Spanner.
+
+
onlySpannerEligibleFields(SortedSet<String>, SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Return a Predicate implementation which determines field eligibility with regard to interaction with + Cloud Spanner, optionally considering the provided set of circumstantial higher-order eligible fields (for + instance, in the case of a known property projection).
+
+
OperationOptions - Interface in gust.backend.model
+
+
Operational options that can be applied to individual calls into the ModelAdapter framework.
+
+
origin() - Method in interface gust.backend.AssetConfiguration.AssetVarianceConfiguration
+
+
Whether to vary based on the value of Origin.
+
+
origin() - Method in interface gust.backend.DynamicServingConfiguration.DynamicVarianceConfiguration
+
+
Whether to indicate response variance by Origin.
+
+
overrideNamingMap() - Method in class gust.backend.PageContext
+
+
Specify a Soy renaming map which overrides the globally-installed map, if any.
+
+
overrideNamingMap() - Method in class gust.backend.PageContextManager
+
+
Specify a Soy renaming map which overrides the globally-installed map, if any.
+
+
+ + + +

P

+
+
Page - Annotation Type in gust.backend.annotations
+
+
Specifies settings for a given page method, that may later be applied via outputs like the application's site-map, or + other metadata assets.
+
+
PAGE_CONTEXT_IJ_NAME - Static variable in interface gust.backend.PageRender
+
+
Name at which proto-context is injected.
+
+
PageContext - Class in gust.backend
+
+
Supplies page context to a Micronaut/Soy render routine, based on the context proto provided/filled out by a given + server-side controller.
+
+
PageContextManager - Class in gust.backend
+
+
Manages the process of filling out PageContext objects before they are sealed, and delivered to Closure/Soy + to be reduced and rendered into content.
+
+
PageRender - Interface in gust.backend
+
+
Interface by which protobuf-driven Soy render context can be managed and orchestrated by a custom PageContext + object.
+
+
Pair<K,​V> - Class in gust.util
+
+
Simple container for a pair of values, one assigned to the name "key," and one assigned to the name "value."
+
+
PARTIAL - gust.util.MessageDifferencer.Scope
+
+
Only fields present in the first message are considered; fields set only in the second + message will be skipped during comparison.
+
+
persist(String, WriteProxy<?>) - Method in class gust.backend.model.CollapsedMessage
+
+
Execute the collapsed message against the provided WriteProxy, which creates it in underlying storage (once + any wrapping transaction finishes).
+
+
persist(Key, Model, WriteOptions) - Method in class gust.backend.driver.firestore.FirestoreDriver
+
+
Low-level record persistence method.
+
+
persist(Key, Model, WriteOptions) - Method in class gust.backend.driver.inmemory.InMemoryDriver
+
+
Low-level record persistence method.
+
+
persist(Key, Model, WriteOptions) - Method in class gust.backend.driver.spanner.SpannerDriver
+
 
+
persist(Key, Model, WriteOptions) - Method in interface gust.backend.model.ModelAdapter
+
+
Low-level record persistence method.
+
+
persist(Key, Model, WriteOptions) - Method in interface gust.backend.model.PersistenceDriver
+
+
Low-level record persistence method.
+
+
PersistenceDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message,​ReadIntermediate,​WriteIntermediate> - Interface in gust.backend.model
+
+
Describes the surface of a generic persistence driver, which is capable of accepting arbitrary structured and typed + business data (also called "data models"), and managing them with regard to persistent storage, which includes + storing them when asked, and recalling them when subsequently asked to do so.
+
+
PersistenceDriver.Internals - Class in gust.backend.model
+
+
Default model adapter internals.
+
+
PersistenceException - Exception in gust.backend.model
+
+
Defines a class of exceptions which can be encountered when interacting with persistence tools, including internal + (built-in) data adapters.
+
+
PersistenceFailure - Enum in gust.backend.model
+
+
Enumerates common kinds of persistence failures.
+
+
PersistenceManager<Driver extends PersistenceDriver> - Interface in gust.backend.model
+
 
+
PersistenceOperationFailed - Exception in gust.backend.model
+
+
Describes a generic operational failure that occurred within the persistence engine.
+
+
pluck(Message, ModelMetadata.FieldPointer) - Static method in class gust.backend.model.ModelMetadata
+
+
Pluck a field value, addressed by a ModelMetadata.FieldPointer, from the provided instance.
+
+
pluck(Message, String) - Static method in class gust.backend.model.ModelMetadata
+
+
Return a single field value container, plucked from the specified deep path, in dot form, using the regular + protobuf-definition names for each field.
+
+
pluckAll(Message, FieldMask) - Static method in class gust.backend.model.ModelMetadata
+
+
Return an iterable containing plucked value containers for each field mentioned in mask, that is present on + instance with an initialized value.
+
+
pluckAll(Message, FieldMask, Boolean) - Static method in class gust.backend.model.ModelMetadata
+
+
Return an iterable containing plucked value containers for each field mentioned in mask, that is present on + instance with an initialized value.
+
+
pluckStream(Message, FieldMask) - Static method in class gust.backend.model.ModelMetadata
+
+
Return a stream which emits plucked value containers for each field mentioned in mask, that is present on + instance with an initialized value.
+
+
pluckStream(Message, FieldMask, Boolean) - Static method in class gust.backend.model.ModelMetadata
+
+
Return a stream which emits plucked value containers for each field mentioned in mask, that is present on + instance with an initialized value.
+
+
policy() - Method in interface gust.backend.AssetConfiguration.CrossOriginResourceConfiguration
+
+
Specifies the default policy to employ for Cross-Origin-Resource-Policy for dynamic content.
+
+
policy() - Method in interface gust.backend.DynamicServingConfiguration.FeaturePolicyConfiguration
+
+
Specifies the default Feature-Policy to apply to dynamically-served content.
+
+
PooledTransportConfig - Interface in gust.backend.transport
+
+
Specifies configuration properties related to pooling of managed transport connections.
+
+
preconnect() - Method in interface gust.backend.DynamicServingConfiguration
+
+
Hostnames to pre-connect to from the browser.
+
+
preconnect(Iterable<String>) - Method in class gust.backend.PageContextManager
+
+
Inject the specified list of hosts as pre-connect hints for the browser.
+
+
preconnect(String...) - Method in class gust.backend.PageContextManager
+
+
Inject the specified list of hosts as pre-connect hints for the browser.
+
+
prefixes() - Method in class gust.backend.PageContextManager
+
+
Return the set of RDFa prefixes affixed to the current render flow, if any.
+
+
preserveFieldNames() - Method in interface gust.backend.driver.spanner.SpannerDriverSettings
+
 
+
PRINT - gust.backend.annotations.Style.MediaType
+
+
For print display.
+
+
priority() - Method in annotation type gust.backend.annotations.Page
+
+
Priority value for the page.
+
+
PRIORITY - Static variable in annotation type gust.backend.annotations.Page
+
+
Property name for the page's priority value.
+
+
PROJECTION - gust.backend.model.FetchOptions.MaskMode
+
+
Treat the field mask as a projection, for query purposes only.
+
+
protoDateFromCloud(Date) - Static method in class gust.backend.driver.spanner.SpannerTemporalConverter
+
+
Convert a Google Cloud date into a standard Protocol Buffers date without loss of resolution.
+
+
ProtoModelCodec<Model extends com.google.protobuf.Message> - Class in gust.backend.model
+
+
Defines a ModelCodec which uses Protobuf serialization to export and import protos to and from from raw + byte-strings.
+
+
protoTimestampFromCloud(Timestamp) - Static method in class gust.backend.driver.spanner.SpannerTemporalConverter
+
+
Convert a Google Cloud timestamp into a standard Protocol Buffers timestamp without loss of resolution.
+
+
provideCompiledTemplates() - Method in class gust.backend.TemplateProvider
+
+
Provide the compiled Soy file set for embedded templates.
+
+
provideSoyFileSet() - Method in class gust.backend.TemplateProvider
+
+
Deprecated. +
Soy file sets are slow due to runtime template interpretation. Please use compiled templates via the + SoySauce class instead. See the see-also listings for this method for more information.
+
+
+
PUBSUB - gust.backend.transport.GoogleService
+
+
Google Cloud Pub-Sub (token:
+
+
put(Message, Message, ListeningScheduledExecutorService) - Method in class gust.backend.driver.inmemory.InMemoryCache
+
+
Write a record (model) at key into the cache, overwriting any model currently stored at the same + key, if applicable.
+
+
put(String, Value) - Method in class gust.backend.model.SerializedModel
+
 
+
put(String, Object) - Method in class gust.backend.PageContextManager
+
+
Install a regular context value, at the named key provided.
+
+
put(Key, Model, ListeningScheduledExecutorService) - Method in interface gust.backend.model.CacheDriver
+
+
Write a record (model) at key into the cache, overwriting any model currently stored at the same + key, if applicable.
+
+
put(Reference, SerializedModel) - Method in interface gust.backend.model.WriteProxy
+
+
Save a collapsed `message` in underlying storage, referenced by `reference`.
+
+
putAll(Map<? extends String, ? extends Value>) - Method in class gust.backend.model.SerializedModel
+
 
+
+ + + +

R

+
+
ReactiveFuture<R> - Class in gust.backend.runtime
+
+
Adapts future/async value containers from different frameworks (namely, Reactive Java, Guava, and the JDK).
+
+
ReactiveFuture.CompletableFuturePublisher<T> - Class in gust.backend.runtime
+
+
Structure that adapts Java's CompletableFuture to a Reactive Java Publisher, which publishes one + item - either the result of the computation, or an error.
+
+
ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription - Class in gust.backend.runtime
+
+
Models a Reactive Java Subscription, which is responsible for propagating events from a + Concurrent Java CompletableFuture to a Subscriber.
+
+
ReactiveFuture.ListenableFuturePublisher<T> - Class in gust.backend.runtime
+
+
Structure that adapts Guava's ListenableFuture to a Reactive Java Publisher, which publishes one + item - either the result of the computation, or an error.
+
+
ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription - Class in gust.backend.runtime
+
+
Models a Reactive Java Subscription, which is responsible for propagating events from a + ListenableFuture to a Subscriber.
+
+
ReactiveFuture.PublisherListenableFuture<T> - Class in gust.backend.runtime
+
+
Structure that adapts a Publisher to a ListenableFuture interface.
+
+
RECURSE - gust.util.MessageDifferencer.FieldComparator.ComparisonResult
+
+
Compared submessages need to be compared recursively.
+
+
ref(String) - Method in interface gust.backend.model.WriteProxy
+
+
Prepare a database reference, based on the provided `path`.
+
+
ref(String, String) - Method in interface gust.backend.model.WriteProxy
+
+
Prepare a database reference, based on the provided `path`, and prepend the provided transaction-wide `prefix`.
+
+
referrerPolicy() - Method in interface gust.backend.DynamicServingConfiguration
+
+
Referrer-Policy configuration for dynamic content.
+
+
remove(Object) - Method in class gust.backend.model.SerializedModel
+
 
+
render() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.InterleaveTarget
+
 
+
render() - Method in interface gust.backend.driver.spanner.SpannerGeneratedDDL.RenderableStatement
+
+
Render this statement into a String buffer.
+
+
render() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.TableConstraint
+
 
+
render() - Method in class gust.backend.PageContextManager
+
 
+
report(MessageDifferencer.ReportType, Message, Message, ImmutableList<MessageDifferencer.SpecificField>) - Method in interface gust.util.MessageDifferencer.Reporter
+
+
Reports information about a specific field.
+
+
report(MessageDifferencer.ReportType, Message, Message, ImmutableList<MessageDifferencer.SpecificField>) - Method in class gust.util.MessageDifferencer.StreamReporter
+
 
+
reportStartupError(Throwable) - Static method in class gust.backend.ApplicationBoot
+
+
Report an error that occurred during server startup, which prevented the server from starting.
+
+
request(long) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription
+
+
Request the specified number of items from the underlying Subscription.
+
+
request(long) - Method in class gust.backend.runtime.ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription
+
+
Request the specified number of items from the underlying Subscription.
+
+
requiresCredentials() - Method in interface gust.backend.transport.GoogleTransportConfig
+
 
+
requiresCredentials() - Method in interface gust.backend.transport.TransportCredentials
+
 
+
resolveColumnIndex(Struct, ModelMetadata.FieldPointer, String) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Given a Spanner row result expressed as a Struct and a ModelMetadata.FieldPointer which is expected to be + present, with a pre-resolved column name, return the numeric column index.
+
+
resolveColumnName(Descriptors.FieldDescriptor) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Given a resolved field pointer resolve the expected/configured column name in Spanner for a given typed model + field.
+
+
resolveColumnName(Descriptors.FieldDescriptor, SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Given a resolved field pointer resolve the expected/configured column name in Spanner for a given typed model + field.
+
+
resolveColumnName(Descriptors.FieldDescriptor, Optional<SpannerFieldOptions>, Optional<TableFieldOptions>, SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Given a resolved Protocol Buffer field descriptor and set of annotations, resolve the expected/configured column + name in Spanner for a given typed model field.
+
+
resolveColumnName(ModelMetadata.FieldPointer, Optional<SpannerFieldOptions>, Optional<TableFieldOptions>, SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Given a resolved field pointer and set of annotations, resolve the expected/configured column name in Spanner for + a given typed model field.
+
+
resolveColumnSize(Descriptors.FieldDescriptor, Optional<SpannerFieldOptions>, Optional<TableFieldOptions>, SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Given a model field pointer which translates to a `STRING` or `BYTES` column in Spanner, determine the size that + should be used when declaring the string column.
+
+
resolveColumnType(ModelMetadata.FieldPointer, Optional<SpannerFieldOptions>, Optional<TableFieldOptions>, SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Given a resolved and eligible ModelMetadata.FieldPointer for a model field which should interact with Spanner, resolve + an expected Spanner Type, including any nested structure or complex objects, as mediated and regulated by + annotations on the model field.
+
+
resolveColumnValue(Struct, ModelMetadata.FieldPointer, Optional<SpannerFieldOptions>, Optional<TableFieldOptions>, SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Given a Spanner row result expressed as a Struct and a ModelMetadata.FieldPointer which is expected to be + present, resolve any present Value.
+
+
resolveDefaultColumns(Descriptors.Descriptor, SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerGeneratedDDL
+
+
Resolve the default calculated set of Spanner columns for a given model structure.
+
+
resolveDefaultType(ModelMetadata.FieldPointer, Descriptors.FieldDescriptor.Type, SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Resolve a default Spanner type for the provided field `pointer`.
+
+
resolveDefaultType(ModelMetadata.FieldPointer, SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Resolve a default Spanner type for the provided field `pointer`.
+
+
resolveField(Descriptors.Descriptor, String) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve an arbitrary field pointer from the provided model type escriptor, specified by the given + path to the property.
+
+
resolveField(Message, String) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve an arbitrary field pointer from the provided model instance, specified by the given path to + the property.
+
+
resolveKeyColumn(ModelMetadata.FieldPointer, SpannerDriverSettings) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
For a given key field pointer, resolve the column name which should be used for the primary key in Spanner, + according to the annotation structure present on the key.
+
+
resolveKeyType(ModelMetadata.FieldPointer) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
For a given key field pointer, resolve the column type which should be used for the primary key in Spanner, + according to the annotation structure present on the key.
+
+
resolveTableName(Descriptors.Descriptor) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Given a schema-driven model or key object, determine the table name that should be used in Spanner.
+
+
resolveTableName(Message) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Given a schema-driven model or key object, determine the table name that should be used in Spanner.
+
+
resolveType(ModelMetadata.FieldPointer, SpannerOptions.SpannerType) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Resolve a normalized Spanner type for the provided field `pointer`, with the explicit provided `spannerType`.
+
+
retries() - Method in interface gust.backend.model.OperationOptions
+
 
+
retrieve(Key, FetchOptions) - Method in class gust.backend.driver.firestore.FirestoreDriver
+
+
Low-level record retrieval method.
+
+
retrieve(Key, FetchOptions) - Method in class gust.backend.driver.inmemory.InMemoryDriver
+
+
Low-level record retrieval method.
+
+
retrieve(Key, FetchOptions) - Method in class gust.backend.driver.spanner.SpannerDriver
+
 
+
retrieve(Key, FetchOptions) - Method in interface gust.backend.model.ModelAdapter
+
+
Low-level record retrieval method.
+
+
retrieve(Key, FetchOptions) - Method in interface gust.backend.model.PersistenceDriver
+
+
Low-level record retrieval method.
+
+
rewrite(Optional<SoyNamingMapProvider>) - Method in class gust.backend.PageContextManager
+
+
Install, or uninstall, the request-scoped renaming map provider.
+
+
ROBOTS_ENABLE - Static variable in annotation type gust.backend.annotations.Page
+
+
Property name for robots enable/disable.
+
+
ROBOTS_SETTINGS - Static variable in annotation type gust.backend.annotations.Page
+
+
Property name for indexing robot settings.
+
+
robotsSettings() - Method in annotation type gust.backend.annotations.Page
+
+
Generic settings for indexing robots.
+
+
role(Descriptors.Descriptor) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve the general type for a given datamodel type descriptor.
+
+
role(Message) - Static method in class gust.backend.model.ModelMetadata
+
+
Resolve the general type for a given datamodel.
+
+
ROOT_CONFIG_PREFIX - Static variable in interface gust.backend.transport.TransportManager
+
+
Config prefix under which transport settings are specified.
+
+
rootConfig - Static variable in class gust.backend.ApplicationBoot
+
+
Root configuration for a Micronaut app.
+
+
runAfterBoth(CompletionStage<?>, Runnable) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
runAfterBothAsync(CompletionStage<?>, Runnable) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
runAfterBothAsync(CompletionStage<?>, Runnable, Executor) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
runAfterEither(CompletionStage<?>, Runnable) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
runAfterEitherAsync(CompletionStage<?>, Runnable) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
runAfterEitherAsync(CompletionStage<?>, Runnable, Executor) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
+ + + +

S

+
+
SAME - gust.util.MessageDifferencer.FieldComparator.ComparisonResult
+
+
Compared fields are equal.
+
+
SCREEN - gust.backend.annotations.Style.MediaType
+
+
For screen display.
+
+
script(String) - Method in class gust.backend.PageContextManager
+
+
Include the specified JavaScript resource in the rendered page, according to the specified settings.
+
+
script(String, Boolean, Boolean) - Method in class gust.backend.PageContextManager
+
+
Include the specified JavaScript resource in the rendered page, according to the specified settings.
+
+
script(String, String, Boolean, Boolean, Boolean, Boolean, Boolean, Boolean) - Method in class gust.backend.PageContextManager
+
+
Include the specified JavaScript resource in the rendered page, according to the specified settings.
+
+
script(Context.Scripts.JavaScript.Builder) - Method in class gust.backend.PageContextManager
+
+
Include the specified JavaScript resource in the rendered page, according to enclosed settings (i.e.
+
+
selectCdnPrefixes(PageContextManager, MutableHttpResponse, AssetConfiguration) - Method in class gust.backend.AppController
+
+
Select the set of CDN prefixes to use in this HTTP cycle, from the configured set.
+
+
serialize(Mutation.WriteBuilder, Model) - Method in class gust.backend.driver.spanner.SpannerCodec
+
+
Specialized entrypoint for converting model instances into Mutation instances so they may be written to + Spanner.
+
+
serialize(Model) - Method in interface gust.backend.model.ModelCodec
+
+
Sugar shortcut to serialize a model through the current codec's installed ModelSerializer.
+
+
SerializedModel - Class in gust.backend.model
+
+
Describes a model which has been serialized into a backing map of keys and properties.
+
+
serializer() - Method in class gust.backend.driver.spanner.SpannerCodec
+
 
+
serializer() - Method in class gust.backend.model.CollapsedMessageCodec
+
+
Acquire an instance of the ModelSerializer attached to this adapter.
+
+
serializer() - Method in interface gust.backend.model.ModelCodec
+
+
Acquire an instance of the ModelSerializer attached to this adapter.
+
+
serializer() - Method in class gust.backend.model.ProtoModelCodec
+
+
Acquire an instance of the ModelSerializer attached to this adapter.
+
+
serve(PageContextManager) - Method in class gust.backend.AppController
+
+
Serve the provided rendered-page response, while applying any app configuration related to dynamic page headers.
+
+
service() - Method in annotation type gust.backend.transport.GoogleAPIChannel
+
+
Service for which this annotation is requesting a ManagedChannel instance.
+
+
setCache(Optional<CacheDriver<Message, Message>>) - Method in class gust.backend.driver.spanner.SpannerManager.Builder
+
+
Set the cache for the target configured Spanner manager.
+
+
setExecutor(Optional<ListeningScheduledExecutorService>) - Method in class gust.backend.driver.spanner.SpannerManager.Builder
+
+
Set the executor used by adapters and drivers spawned by this manager.
+
+
setFeaturePolicy(Collection<String>) - Method in class gust.backend.PageContextManager
+
+
Overwrite the set of `Feature-Policy` entries for the current render flow.
+
+
setFieldComparator(MessageDifferencer.FieldComparator) - Method in class gust.util.MessageDifferencer.Builder
+
+
Sets the MessageDifferencer.FieldComparator used to determine differences between protocol buffer + fields.
+
+
setFloatComparison(MessageDifferencer.FloatComparison) - Method in class gust.util.MessageDifferencer.Builder
+
+
Sets the type of comparison that is used by the differencer when comparing float (and double) + fields in messages.
+
+
setGooglebot(String) - Method in class gust.backend.PageContextManager
+
+
Overwrite the value specified for the "googlebot" metadata key in the current render flow.
+
+
setInterleaveTarget(Optional<SpannerGeneratedDDL.InterleaveTarget>) - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.Builder
+
+
Set, or clear, the parent interleave target for this table.
+
+
setKeySortDirection(SpannerGeneratedDDL.SortDirection) - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.Builder
+
+
Set the sort direction for the primary key column in this table.
+
+
setKeywords(Collection<String>) - Method in class gust.backend.PageContextManager
+
+
Overwrite the current set of page keywords for the current render flow.
+
+
setLinks(Collection<Context.PageLink.Builder>) - Method in class gust.backend.PageContextManager
+
+
Overwrite the set of page metadata links for the current render flow.
+
+
setMessageFieldComparison(MessageDifferencer.MessageFieldComparison) - Method in class gust.util.MessageDifferencer.Builder
+
+
Sets the type of comparison that is used by the differencer when determining how to compare + fields in messages.
+
+
setOpenGraph(Context.Metadata.OpenGraph.Builder) - Method in class gust.backend.PageContextManager
+
+
Overwrite the OpenGraph metadata configuration for the current render flow, with the provided OpenGraph metadata + configuration.
+
+
setOptimizerVersion(Optional<Integer>) - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.Builder
+
+
Set, or clear, the optimizer version to apply when creating this table.
+
+
setOptions(Optional<SpannerOptions.Builder>) - Method in class gust.backend.driver.spanner.SpannerManager.Builder
+
+
Set the base package of Spanner client options to use when spawning new clients via the target configured + Spanner manager.
+
+
setPrefixes(Optional<List<Context.RDFPrefix>>) - Method in class gust.backend.PageContextManager
+
+
Overwrite the full set of RDFa prefixes for the current render flow.
+
+
setRepeatedFieldComparison(MessageDifferencer.RepeatedFieldComparison) - Method in class gust.util.MessageDifferencer.Builder
+
+
Sets the type of comparison for repeated field that is used by this differencer when compare + repeated fields in messages.
+
+
setReportMatches(boolean) - Method in class gust.util.MessageDifferencer.Builder
+
+
Tells the differencer whether or not to report matches.
+
+
setRobots(String) - Method in class gust.backend.PageContextManager
+
+
Overwrite the value specified for the "robots" metadata key in the current render flow.
+
+
setScope(MessageDifferencer.Scope) - Method in class gust.util.MessageDifferencer.Builder
+
+
Sets the scope of the comparison that is used by the differencer when determining which + fields to compare between the messages.
+
+
setSettings(Optional<SpannerDriverSettings>) - Method in class gust.backend.driver.spanner.SpannerManager.Builder
+
+
Set the main driver settings to use when spawning adapters in the target configured Spanner manager.
+
+
setTableConstraints(Optional<List<SpannerGeneratedDDL.TableConstraint>>) - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.Builder
+
+
Set, or clear, the set of table constraints added to this table.
+
+
setVersionRetentionPeriod(Optional<String>) - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL.Builder
+
+
Set, or clear, the data versioning retention period for this table.
+
+
sharedTtl() - Method in interface gust.backend.AssetConfiguration.AssetCachingConfiguration
+
+
When a shared-cache directive is enabled, this sets the TTL for shared caches.
+
+
sharedTtlUnit() - Method in interface gust.backend.AssetConfiguration.AssetCachingConfiguration
+
+
Time unit to apply to the value specified by AssetConfiguration.AssetCachingConfiguration.sharedTtl().
+
+
sitemap() - Method in annotation type gust.backend.annotations.Page
+
+
Whether to list this page in the site map.
+
+
SITEMAP - Static variable in annotation type gust.backend.annotations.Page
+
+
Property name for sitemap enable/disable.
+
+
size() - Method in class gust.backend.model.SerializedModel
+
 
+
snapshot() - Method in interface gust.backend.model.FetchOptions
+
 
+
spanner() - Method in class gust.backend.driver.spanner.SpannerAdapter
+
+
Acquire the Spanner for Java client powering this adapter.
+
+
SPANNER - gust.backend.transport.GoogleService
+
+
Google Cloud Firestore (token:
+
+
SpannerAdapter<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message> - Class in gust.backend.driver.spanner
+
+
Implementation of a DatabaseAdapter backed by Google Cloud Spanner, capable of marshalling arbitrary + schema-generated Message objects back and forth from structured columnar storage.
+
+
SpannerCodec<Model extends com.google.protobuf.Message> - Class in gust.backend.driver.spanner
+
+
Implements a ModelCodec for Spanner types used as intermediaries during database operations.
+
+
SpannerDriver<Key extends com.google.protobuf.Message,​Model extends com.google.protobuf.Message> - Class in gust.backend.driver.spanner
+
+
Provides a DatabaseDriver implementation which enables seamless Protocol Buffer persistence with Google Cloud + Spanner, for any Message-derived (schema-driven) business model in a given Gust app's ecosystem.
+
+
SpannerDriverSettings - Interface in gust.backend.driver.spanner
+
+
Specifies settings for the Spanner driver and data storage implementation.
+
+
SpannerDriverSettings.DefaultSettings - Class in gust.backend.driver.spanner
+
+
Concrete hard-coded driver defaults.
+
+
SpannerGeneratedDDL - Class in gust.backend.driver.spanner
+
+
Container for generated schema-driven Spanner DDL.
+
+
SpannerGeneratedDDL.Builder - Class in gust.backend.driver.spanner
+
+
Build properties for a generated Spanner table DDL statement, based on a given model instance as a base for + configuring the table name (via annotations / calculated defaults) and set of typed Spanner value columns.
+
+
SpannerGeneratedDDL.InterleaveTarget - Class in gust.backend.driver.spanner
+
+
Specify a parent table against which this table is interleaved.
+
+
SpannerGeneratedDDL.PropagatedAction - Enum in gust.backend.driver.spanner
+
+
Specifies options for reference action propagation (i.e.
+
+
SpannerGeneratedDDL.RenderableStatement - Interface in gust.backend.driver.spanner
+
+
Represents a DDL statement structure in code that can be rendered down to a string.
+
+
SpannerGeneratedDDL.SortDirection - Enum in gust.backend.driver.spanner
+
+
Sort direction settings which can apply to columns.
+
+
SpannerGeneratedDDL.TableConstraint - Class in gust.backend.driver.spanner
+
+
Specifies a generic table constraint to include in a DDL statement.
+
+
SpannerManager - Class in gust.backend.driver.spanner
+
+
Main adapter manager interface for interaction between Gust/Elide apps and Google Cloud Spanner, enabling seamless + persistence for generated Message-driven models.
+
+
SpannerManager.Builder - Class in gust.backend.driver.spanner
+
+
Intermediate builder which can gather settings for an eventually-immutable SpannerManager.ConfiguredSpannerManager.
+
+
SpannerManager.ConfiguredSpannerManager - Class in gust.backend.driver.spanner
+
+
Represents a configured version of a central SpannerManager, which has been sealed for immutable use at + runtime.
+
+
SpannerMutationSerializer<Model extends com.google.protobuf.Message> - Class in gust.backend.driver.spanner
+
+
Implements a specialized serializer, capable of converting generated Message-derived objects into Spanner + Mutation records during write operations.
+
+
spannerOpts(Descriptors.FieldDescriptor) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Given a resolved Protocol Buffer Descriptors.FieldDescriptor which is considered eligible for interaction + with Spanner, resolve any present Spanner-specific options and annotations via SpannerFieldOptions.
+
+
spannerOpts(ModelMetadata.FieldPointer) - Static method in class gust.backend.driver.spanner.SpannerUtil
+
+
Given a pre-resolved ModelMetadata.FieldPointer, resolve any present SpannerFieldOptions.
+
+
SpannerStructDeserializer<Model extends com.google.protobuf.Message> - Class in gust.backend.driver.spanner
+
+
Implements a specialized de-serializer, capable of converting runtime-inhabited Spanner Struct-records into + typed Message-derived objects.
+
+
SpannerTemporalConverter - Class in gust.backend.driver.spanner
+
+
Serialize back and forth between Google Cloud / Protocol Buffer standard TIMESTAMP and DATE records.
+
+
SpannerTransportConfig - Class in gust.backend.driver.spanner
+
+
Configures gRPC transport channels for use with Google Cloud Spanner, either in production or emulated circumstances + for unit and integration testing.
+
+
SpannerUtil - Class in gust.backend.driver.spanner
+
+
Provides utilities related to operations with Spanner, including tools for resolving column names and types from + models and annotations, and producing default sets of columns for DDL statements.
+
+
SpecificField() - Constructor for class gust.util.MessageDifferencer.SpecificField
+
 
+
splice(Message, ModelMetadata.FieldPointer, Optional<Value>) - Static method in class gust.backend.model.ModelMetadata
+
+
Splice the provided optional value (or clear any existing value) at the specified field pointer, in the + provided model instance.
+
+
splice(Message, String, Optional<Value>) - Static method in class gust.backend.model.ModelMetadata
+
+
Splice the provided optional value (or clear any existing value) at the field path in the provided model + instance.
+
+
spliceBuilder(Message.Builder, ModelMetadata.FieldPointer, Optional<Value>) - Static method in class gust.backend.model.ModelMetadata
+
+
Splice the provided optional value (or clear any existing value) at the specified field pointer, in the + provided model instance.
+
+
spliceId(Message, Optional<Value>) - Static method in class gust.backend.model.ModelMetadata
+
+
Splice the provided value at val, into the ID field value for instance.
+
+
spliceIdBuilder(Message.Builder, Optional<Value>) - Static method in class gust.backend.model.ModelMetadata
+
+
Splice the provided value at val, into the ID field value for the provided model builder.
+
+
spliceKey(Message, Optional<Key>) - Static method in class gust.backend.model.ModelMetadata
+
+
Splice the provided value at val, into the key message value for instance.
+
+
spliceKeyBuilder(Message.Builder, Optional<Key>) - Static method in class gust.backend.model.ModelMetadata
+
+
Splice the provided value at val, into the key message value for the supplied builder.
+
+
STORAGE - gust.backend.transport.GoogleService
+
+
Google Cloud Storage (token:
+
+
streamFields(Descriptors.Descriptor) - Static method in class gust.backend.model.ModelMetadata
+
+
Crawl all fields, recursively, on the descriptor associated with the provided model instance, and return them in + a stream.
+
+
streamFields(Descriptors.Descriptor, Predicate<ModelMetadata.FieldPointer>) - Static method in class gust.backend.model.ModelMetadata
+
+
Crawl all fields, recursively, on the descriptor associated with the provided model instance.
+
+
streamFields(Descriptors.Descriptor, Optional<Predicate<ModelMetadata.FieldPointer>>) - Static method in class gust.backend.model.ModelMetadata
+
+
Crawl all fields, recursively, on the descriptor associated with the provided model instance.
+
+
streamFields(Descriptors.Descriptor, Optional<Predicate<ModelMetadata.FieldPointer>>, Boolean) - Static method in class gust.backend.model.ModelMetadata
+
+
Crawl all fields, recursively, on the provided descriptor for a model instance.
+
+
streamFields(Descriptors.Descriptor, Optional<Predicate<ModelMetadata.FieldPointer>>, Predicate<ModelMetadata.FieldPointer>) - Static method in class gust.backend.model.ModelMetadata
+
+
Crawl all fields, recursively, on the provided descriptor for a model instance.
+
+
StreamReporter(Appendable) - Constructor for class gust.util.MessageDifferencer.StreamReporter
+
+
Equivalent to new StreamReporter(output, false).
+
+
StreamReporter(Appendable, boolean) - Constructor for class gust.util.MessageDifferencer.StreamReporter
+
+
Creates a new reporter.
+
+
strongETags() - Method in class gust.backend.PageContextManager
+
Style - Annotation Type in gust.backend.annotations
+
+
Links a style module to a given controller endpoint, such that it will automatically be included and loaded in the + head of the page, applying any applicable rewrite settings (if enabled).
+
+
Style.MediaType - Enum in gust.backend.annotations
+
+
Applicable media types.
+
+
stylesheet(String) - Method in class gust.backend.PageContextManager
+
+
Include the specified CSS stylesheet in the rendered page with default settings.
+
+
stylesheet(String, String) - Method in class gust.backend.PageContextManager
+
+
Include the specified CSS stylesheet in the rendered page, along with the specified media setting.
+
+
stylesheet(String, String, String, Boolean, Boolean) - Method in class gust.backend.PageContextManager
+
+
Include the specified CSS stylesheet in the rendered page, according to the specified settings.
+
+
stylesheet(Context.Styles.Stylesheet.Builder) - Method in class gust.backend.PageContextManager
+
+
Include the specified CSS stylesheet resource in the rendered page, according to the enclosed settings (i.e.
+
+
subscribe(Subscriber<? super R>) - Method in class gust.backend.runtime.ReactiveFuture
+
+
Request Publisher to start streaming data.
+
+
subscribe(Subscriber<? super T>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
subscribe(Subscriber<? super T>) - Method in class gust.backend.runtime.ReactiveFuture.ListenableFuturePublisher
+
 
+
subscribe(Subscriber<? super T>) - Method in class gust.backend.runtime.ReactiveFuture.PublisherListenableFuture
+
 
+
supportedClientHints(Optional<Iterable<Context.ClientHint>>, Optional<Long>) - Method in class gust.backend.PageContextManager
+
+
Enable the provided set of server-indicated client hint types.
+
+
supportedClientHints(Context.ClientHint...) - Method in class gust.backend.PageContextManager
+
+
Enable the provided set of server-indicated client hint types.
+
+
+ + + +

T

+
+
TemplateProvider - Class in gust.backend
+
+
Default Soy template provider.
+
+
TemplateProvider() - Constructor for class gust.backend.TemplateProvider
+
 
+
thenAccept(Consumer<? super T>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
thenAcceptAsync(Consumer<? super T>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
thenAcceptAsync(Consumer<? super T>, Executor) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
thenAcceptBoth(CompletionStage<? extends U>, BiConsumer<? super T, ? super U>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
thenAcceptBothAsync(CompletionStage<? extends U>, BiConsumer<? super T, ? super U>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
thenAcceptBothAsync(CompletionStage<? extends U>, BiConsumer<? super T, ? super U>, Executor) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
thenApply(Function<? super T, ? extends U>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
thenApplyAsync(Function<? super T, ? extends U>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
thenApplyAsync(Function<? super T, ? extends U>, Executor) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
thenCombine(CompletionStage<? extends U>, BiFunction<? super T, ? super U, ? extends V>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
thenCombineAsync(CompletionStage<? extends U>, BiFunction<? super T, ? super U, ? extends V>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
thenCombineAsync(CompletionStage<? extends U>, BiFunction<? super T, ? super U, ? extends V>, Executor) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
thenCompose(Function<? super T, ? extends CompletionStage<U>>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
thenComposeAsync(Function<? super T, ? extends CompletionStage<U>>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
thenComposeAsync(Function<? super T, ? extends CompletionStage<U>>, Executor) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
thenRun(Runnable) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
thenRunAsync(Runnable) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
thenRunAsync(Runnable, Executor) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
TIMEOUT - gust.backend.model.PersistenceFailure
+
+
The operation timed out.
+
+
timeoutUnit() - Method in interface gust.backend.model.OperationOptions
+
 
+
timeoutValue() - Method in interface gust.backend.model.OperationOptions
+
 
+
TIMESTAMP - gust.backend.model.ModelSerializer.InstantSerializeMode
+
+
Encode temporal instants as millisecond-precision Unix timestamps.
+
+
title() - Method in class gust.backend.PageContextManager
+
+
Retrieve the current value for the page title, set in the builder.
+
+
title(String) - Method in class gust.backend.PageContextManager
+
+
Set the page title for the current render flow.
+
+
toCompletableFuture() - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
toString() - Method in class gust.backend.driver.spanner.SpannerGeneratedDDL
+
 
+
toString() - Method in enum gust.backend.model.CacheOptions.EvictionMode
+
 
+
toString() - Method in class gust.backend.model.EncodedModel
+
toString() - Method in class gust.backend.model.ModelMetadata.FieldContainer
+
toString() - Method in class gust.backend.model.ModelMetadata.FieldPointer
+
Transaction - Class in gust.backend.model
+
 
+
Transaction() - Constructor for class gust.backend.model.Transaction
+
 
+
transactional() - Method in interface gust.backend.model.OperationOptions
+
 
+
translate() - Method in class gust.backend.PageContext
+
+
Specify whether to enable translation via Soy message bundles.
+
+
translate() - Method in class gust.backend.PageContextManager
+
+
Indicate whether message translation is enabled for the rendering layer.
+
+
TransportConfig - Interface in gust.backend.transport
+
+
Specifies base configuration properties generally supported by all transport configurations.
+
+
TransportCredentials - Interface in gust.backend.transport
+
+
Specifies the generic notion of "transport credentials," as configuration or logic.
+
+
TransportException - Exception in gust.backend.transport
+
+
Defines an error case that was encountered while dealing with managed transport logic.
+
+
TransportManager<A extends java.lang.annotation.Annotation,​E extends java.lang.Enum<E>,​C> - Interface in gust.backend.transport
+
+
Defines the interface by which "transport manager" objects must comply.
+
+
treatAsMap(Descriptors.FieldDescriptor, Descriptors.FieldDescriptor) - Method in class gust.util.MessageDifferencer.Builder
+
+
The elements of the given repeated field will be treated as a map for diffing purposes, with + key being the map key.
+
+
treatAsMapUsingKeyComparator(Descriptors.FieldDescriptor, MessageDifferencer.MapKeyComparator) - Method in class gust.util.MessageDifferencer.Builder
+
 
+
treatAsMapWithMultipleFieldsAsKey(Descriptors.FieldDescriptor, List<Descriptors.FieldDescriptor>) - Method in class gust.util.MessageDifferencer.Builder
+
 
+
treatAsSet(Descriptors.FieldDescriptor) - Method in class gust.util.MessageDifferencer.Builder
+
+
The elements of the given repeated field will be treated as a set for diffing purposes, so + different orderings of the same elements will be considered equal.
+
+
trustedResource(URI) - Method in class gust.backend.AppController
+
+
Generate a trusted resource URL for the provided Java URI.
+
+
trustedResource(URI) - Method in class gust.backend.PageContextManager
+
+
Generate a trusted resource URL for the provided Java URI.
+
+
trustedResource(URL) - Method in class gust.backend.AppController
+
+
Generate a trusted resource URL for the provided Java URL.
+
+
trustedResource(URL) - Method in class gust.backend.PageContextManager
+
+
Generate a trusted resource URL for the provided Java URL.
+
+
ttl() - Method in interface gust.backend.AssetConfiguration.AssetCachingConfiguration
+
+
Time-to-live value to apply to the main HTTP cache directive.
+
+
ttl() - Method in interface gust.backend.DynamicServingConfiguration.ClientHintsConfiguration
+
+
Client Hints configuration time-to-live value.
+
+
TTL - gust.backend.model.CacheOptions.EvictionMode
+
+
Flag to enable TTL enforcement.
+
+
ttlUnit() - Method in interface gust.backend.AssetConfiguration.AssetCachingConfiguration
+
+
Time unit to apply to the value specified by AssetConfiguration.AssetCachingConfiguration.ttl().
+
+
ttlUnit() - Method in interface gust.backend.DynamicServingConfiguration.ClientHintsConfiguration
+
+
Client Hints configuration time-to-live unit.
+
+
+ + + +

U

+
+
UnknownDescriptor() - Constructor for class gust.util.MessageDifferencer.UnknownDescriptor
+
 
+
update(Key, Model) - Method in interface gust.backend.model.PersistenceDriver
+
+
Update the record specified by model, and addressed by key, in underlying storage.
+
+
update(Key, Model, UpdateOptions) - Method in interface gust.backend.model.PersistenceDriver
+
+
Update the record specified by model, and addressed by key, in underlying storage.
+
+
update(Model) - Method in interface gust.backend.model.PersistenceDriver
+
+
Update the record specified by model in underlying storage, using the existing key or ID value affixed to + the model.
+
+
update(Model, UpdateOptions) - Method in interface gust.backend.model.PersistenceDriver
+
+
Update the record specified by model in underlying storage, making use of the specified options, + using the existing key or ID value affixed to the model.
+
+
update(Reference, SerializedModel) - Method in interface gust.backend.model.WriteProxy
+
+
Update a collapsed `message` in underlying storage, referenced by `reference`.
+
+
UPDATE - gust.backend.model.ModelSerializer.WriteDisposition
+
+
Update-style writes.
+
+
updatedAtMicros() - Method in interface gust.backend.model.OperationOptions
+
 
+
updatedAtSeconds() - Method in interface gust.backend.model.OperationOptions
+
 
+
UpdateOptions - Interface in gust.backend.model
+
+
Describes options specifically involved with updating existing model entities.
+
+
+ + + +

V

+
+
value() - Method in annotation type gust.backend.annotations.Js
+
+
Module name for the script module.
+
+
value() - Method in annotation type gust.backend.annotations.Page
+
+
Simple name / tag for the page.
+
+
value() - Method in annotation type gust.backend.annotations.Style
+
+
Module name for the style module.
+
+
valueOf(String) - Static method in enum gust.backend.annotations.Style.MediaType
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.backend.driver.spanner.SpannerGeneratedDDL.PropagatedAction
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.backend.driver.spanner.SpannerGeneratedDDL.SortDirection
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.backend.model.CacheOptions.EvictionMode
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.backend.model.EncodingMode
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.backend.model.FetchOptions.MaskMode
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.backend.model.ModelSerializer.EnumSerializeMode
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.backend.model.ModelSerializer.InstantSerializeMode
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.backend.model.ModelSerializer.WriteDisposition
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.backend.model.PersistenceFailure
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.backend.model.WriteOptions.WriteDisposition
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.backend.runtime.AssetManager.ModuleType
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.backend.transport.GoogleService
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.util.MessageDifferencer.FieldComparator.ComparisonResult
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.util.MessageDifferencer.FloatComparison
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.util.MessageDifferencer.MessageFieldComparison
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.util.MessageDifferencer.RepeatedFieldComparison
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.util.MessageDifferencer.ReportType
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.util.MessageDifferencer.Scope
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum gust.util.MessageDifferencer.UnknownFieldType
+
+
Returns the enum constant of this type with the specified name.
+
+
values() - Static method in enum gust.backend.annotations.Style.MediaType
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.backend.driver.spanner.SpannerGeneratedDDL.PropagatedAction
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.backend.driver.spanner.SpannerGeneratedDDL.SortDirection
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.backend.model.CacheOptions.EvictionMode
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.backend.model.EncodingMode
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.backend.model.FetchOptions.MaskMode
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.backend.model.ModelSerializer.EnumSerializeMode
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.backend.model.ModelSerializer.InstantSerializeMode
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.backend.model.ModelSerializer.WriteDisposition
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.backend.model.PersistenceFailure
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Method in class gust.backend.model.SerializedModel
+
 
+
values() - Static method in enum gust.backend.model.WriteOptions.WriteDisposition
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.backend.runtime.AssetManager.ModuleType
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.backend.transport.GoogleService
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.util.MessageDifferencer.FieldComparator.ComparisonResult
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.util.MessageDifferencer.FloatComparison
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.util.MessageDifferencer.MessageFieldComparison
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.util.MessageDifferencer.RepeatedFieldComparison
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.util.MessageDifferencer.ReportType
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.util.MessageDifferencer.Scope
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum gust.util.MessageDifferencer.UnknownFieldType
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
variance() - Method in interface gust.backend.AssetConfiguration
+
+
Specifies settings for Vary headers.
+
+
variance() - Method in interface gust.backend.DynamicServingConfiguration
+
+
Vary configuration for dynamic content.
+
+
VARINT - gust.util.MessageDifferencer.UnknownFieldType
+
+
Varint.
+
+
vary(Iterable<String>) - Method in class gust.backend.PageContextManager
+
+
Append an HTTP request header considered as part of the Vary header in the response.
+
+
vary(String) - Method in class gust.backend.PageContextManager
+
+
Append an HTTP request header considered as part of the Vary header in the response.
+
+
vary(String...) - Method in class gust.backend.PageContextManager
+
+
Append an HTTP request header considered as part of the Vary header in the response.
+
+
+ + + +

W

+
+
whenComplete(BiConsumer<? super T, ? super Throwable>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
whenCompleteAsync(BiConsumer<? super T, ? super Throwable>) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
whenCompleteAsync(BiConsumer<? super T, ? super Throwable>, Executor) - Method in class gust.backend.runtime.ReactiveFuture.CompletableFuturePublisher
+
 
+
wrap(ApiFuture<R>) - Static method in class gust.backend.runtime.ReactiveFuture
+
+
Wrap a Google APIs ApiFuture in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value.
+
+
wrap(ApiFuture<R>, Executor) - Static method in class gust.backend.runtime.ReactiveFuture
+
+
Wrap a Google APIs ApiFuture in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value.
+
+
wrap(ListenableFuture<R>) - Static method in class gust.backend.runtime.ReactiveFuture
+
+
Wrap a Guava ListenableFuture in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value.
+
+
wrap(ListenableFuture<R>, Executor) - Static method in class gust.backend.runtime.ReactiveFuture
+
+
Wrap a Guava ListenableFuture in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value.
+
+
wrap(String, EncodingMode, byte[]) - Static method in class gust.backend.model.EncodedModel
+
+
Wrap a blob of opaque data, asserting that it is actually an encoded model record.
+
+
wrap(CompletableFuture<R>) - Static method in class gust.backend.runtime.ReactiveFuture
+
+
Wrap a regular Java CompletableFuture in a universal ReactiveFuture, such that it may be used with + any interface requiring support for that class.
+
+
wrap(CompletableFuture<R>, Executor) - Static method in class gust.backend.runtime.ReactiveFuture
+
+
Wrap a regular Java CompletableFuture in a universal ReactiveFuture, such that it may be used with + any interface requiring support for that class.
+
+
wrap(SortedMap<String, Value>, Message) - Static method in class gust.backend.model.SerializedModel
+
+
Create a serialized model, pre-filled with the provided backing data.
+
+
wrap(Publisher<R>) - Static method in class gust.backend.runtime.ReactiveFuture
+
+
Wrap a Reactive Java Publisher in a universal ReactiveFuture, such that it may be used with any + interface requiring a supported async or future value.
+
+
writeMode() - Method in interface gust.backend.model.WriteOptions
+
 
+
WriteOptions - Interface in gust.backend.model
+
+
Describes options involved with operations to persist model entities.
+
+
WriteOptions.WriteDisposition - Enum in gust.backend.model
+
+
Enumerates write attitudes with regard to existing record collisions.
+
+
WriteProxy<Reference> - Interface in gust.backend.model
+
+
Provides an interface for virtualized object writes during transactions or hierarchical serialization.
+
+
+ + + +

X

+
+
xssProtection() - Method in interface gust.backend.DynamicServingConfiguration
+
+
X-XSS-Protection configuration for dynamic content.
+
+
+A B C D E F G H I J K L M N O P R S T U V W X 
All Classes All Packages
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/index.html b/docs/java/index.html new file mode 100644 index 000000000..f7b70dc96 --- /dev/null +++ b/docs/java/index.html @@ -0,0 +1,219 @@ + + + + + +Overview + + + + + + + + + + + + + + +
+ +
+
+

GUST Framework

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Packages 
PackageDescription
gust +
Provides logic and framework implementation structure for Gust.
+
gust.backend +
Defines backend logic for Gust-based applications.
+
gust.backend.annotations +
Provides annotations for backend Java applications.
+
gust.backend.driver.firestore +
Provides a DatabaseDriver implementation, using Google Cloud Firestore.
+
gust.backend.driver.inmemory +
Provides a reference implementation of a persistence driver, backed by a concurrent map.
+
gust.backend.driver.spanner +
Provides a DatabaseDriver implementation for integration with Google Cloud Spanner.
+
gust.backend.model +
Provides definitions and structure for logic dealing with business data.
+
gust.backend.runtime +
Supplies core backend runtime logic, like execution/scheduling, logging, and so on.
+
gust.backend.transport +
Provides tools for managing data in motion between computers.
+
gust.util +
Provides generic pure-Java utility objects and classes.
+
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/jquery/external/jquery/jquery.js b/docs/java/jquery/external/jquery/jquery.js new file mode 100644 index 000000000..50937333b --- /dev/null +++ b/docs/java/jquery/external/jquery/jquery.js @@ -0,0 +1,10872 @@ +/*! + * jQuery JavaScript Library v3.5.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2020-05-04T22:49Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.5.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.5 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2020-03-14 + */ +( function( window ) { +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + +
+ +

index.html

+
+ + diff --git a/docs/java/overview-tree.html b/docs/java/overview-tree.html new file mode 100644 index 000000000..fc035f457 --- /dev/null +++ b/docs/java/overview-tree.html @@ -0,0 +1,423 @@ + + + + + +Class Hierarchy + + + + + + + + + + + + + +
+ +
+
+ +
+
+

Class Hierarchy

+ +
+
+

Interface Hierarchy

+ +
+
+

Annotation Type Hierarchy

+
    +
  • gust.backend.transport.GoogleAPIChannel (implements java.lang.annotation.Annotation)
  • +
  • gust.backend.annotations.Js (implements java.lang.annotation.Annotation)
  • +
  • gust.backend.annotations.Page (implements java.lang.annotation.Annotation)
  • +
  • gust.backend.annotations.Style (implements java.lang.annotation.Annotation)
  • +
+
+
+

Enum Hierarchy

+ +
+
+
+
+ +

Copyright (©) 2020, The Gust Framework Authors

+
+ + diff --git a/docs/java/package-search-index.js b/docs/java/package-search-index.js new file mode 100644 index 000000000..51f7d154f --- /dev/null +++ b/docs/java/package-search-index.js @@ -0,0 +1 @@ +packageSearchIndex = [{"l":"All Packages","url":"allpackages-index.html"},{"l":"gust"},{"l":"gust.backend"},{"l":"gust.backend.annotations"},{"l":"gust.backend.driver.firestore"},{"l":"gust.backend.driver.inmemory"},{"l":"gust.backend.driver.spanner"},{"l":"gust.backend.model"},{"l":"gust.backend.runtime"},{"l":"gust.backend.transport"},{"l":"gust.util"}] \ No newline at end of file diff --git a/docs/java/package-search-index.zip b/docs/java/package-search-index.zip new file mode 100644 index 0000000000000000000000000000000000000000..73c21fb81d820450109781f1e038a592028e65f2 GIT binary patch literal 299 zcmWIWW@Zs#;Nak3*v`lj%zy+Wf$W0BR3-ORj{)C#?<;{3eYeqTN& zLk_p~NfG_F4c7S&?(JA1=B~6JoRsvExf%rEN>jUL}qZ_~k#FbE+Q;{`;0FZwVNX2n-^JoI; zP;4#$8DIy*Yk-P>VN(DUKmPse7mx+ExD4O|;?E5D0Z5($mjO3`*anwQU^s{ZDK#Lz zj>~{qyaIx5K!t%=G&2IJNzg!ChRpyLkO7}Ry!QaotAHAMpbB3AF(}|_f!G-oI|uK6 z`id_dumai5K%C3Y$;tKS_iqMPHg<*|-@e`liWLAggVM!zAP#@l;=c>S03;{#04Z~5 zN_+ss=Yg6*hTr59mzMwZ@+l~q!+?ft!fF66AXT#wWavHt30bZWFCK%!BNk}LN?0Hg z1VF_nfs`Lm^DjYZ1(1uD0u4CSIr)XAaqW6IT{!St5~1{i=i}zAy76p%_|w8rh@@c0Axr!ns=D-X+|*sY6!@wacG9%)Qn*O zl0sa739kT-&_?#oVxXF6tOnqTD)cZ}2vi$`ZU8RLAlo8=_z#*P3xI~i!lEh+Pdu-L zx{d*wgjtXbnGX_Yf@Tc7Q3YhLhPvc8noGJs2DA~1DySiA&6V{5JzFt ojAY1KXm~va;tU{v7C?Xj0BHw!K;2aXV*mgE07*qoM6N<$f;4TDA^-pY literal 0 HcmV?d00001 diff --git a/docs/java/script.js b/docs/java/script.js new file mode 100644 index 000000000..7dc93c48e --- /dev/null +++ b/docs/java/script.js @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +var moduleSearchIndex; +var packageSearchIndex; +var typeSearchIndex; +var memberSearchIndex; +var tagSearchIndex; +function loadScripts(doc, tag) { + createElem(doc, tag, 'jquery/jszip/dist/jszip.js'); + createElem(doc, tag, 'jquery/jszip-utils/dist/jszip-utils.js'); + if (window.navigator.userAgent.indexOf('MSIE ') > 0 || window.navigator.userAgent.indexOf('Trident/') > 0 || + window.navigator.userAgent.indexOf('Edge/') > 0) { + createElem(doc, tag, 'jquery/jszip-utils/dist/jszip-utils-ie.js'); + } + createElem(doc, tag, 'search.js'); + + $.get(pathtoroot + "module-search-index.zip") + .done(function() { + JSZipUtils.getBinaryContent(pathtoroot + "module-search-index.zip", function(e, data) { + JSZip.loadAsync(data).then(function(zip){ + zip.file("module-search-index.json").async("text").then(function(content){ + moduleSearchIndex = JSON.parse(content); + }); + }); + }); + }); + $.get(pathtoroot + "package-search-index.zip") + .done(function() { + JSZipUtils.getBinaryContent(pathtoroot + "package-search-index.zip", function(e, data) { + JSZip.loadAsync(data).then(function(zip){ + zip.file("package-search-index.json").async("text").then(function(content){ + packageSearchIndex = JSON.parse(content); + }); + }); + }); + }); + $.get(pathtoroot + "type-search-index.zip") + .done(function() { + JSZipUtils.getBinaryContent(pathtoroot + "type-search-index.zip", function(e, data) { + JSZip.loadAsync(data).then(function(zip){ + zip.file("type-search-index.json").async("text").then(function(content){ + typeSearchIndex = JSON.parse(content); + }); + }); + }); + }); + $.get(pathtoroot + "member-search-index.zip") + .done(function() { + JSZipUtils.getBinaryContent(pathtoroot + "member-search-index.zip", function(e, data) { + JSZip.loadAsync(data).then(function(zip){ + zip.file("member-search-index.json").async("text").then(function(content){ + memberSearchIndex = JSON.parse(content); + }); + }); + }); + }); + $.get(pathtoroot + "tag-search-index.zip") + .done(function() { + JSZipUtils.getBinaryContent(pathtoroot + "tag-search-index.zip", function(e, data) { + JSZip.loadAsync(data).then(function(zip){ + zip.file("tag-search-index.json").async("text").then(function(content){ + tagSearchIndex = JSON.parse(content); + }); + }); + }); + }); + if (!moduleSearchIndex) { + createElem(doc, tag, 'module-search-index.js'); + } + if (!packageSearchIndex) { + createElem(doc, tag, 'package-search-index.js'); + } + if (!typeSearchIndex) { + createElem(doc, tag, 'type-search-index.js'); + } + if (!memberSearchIndex) { + createElem(doc, tag, 'member-search-index.js'); + } + if (!tagSearchIndex) { + createElem(doc, tag, 'tag-search-index.js'); + } + $(window).resize(function() { + $('.navPadding').css('padding-top', $('.fixedNav').css("height")); + }); +} + +function createElem(doc, tag, path) { + var script = doc.createElement(tag); + var scriptElement = doc.getElementsByTagName(tag)[0]; + script.src = pathtoroot + path; + scriptElement.parentNode.insertBefore(script, scriptElement); +} + +function show(type) { + count = 0; + for (var key in data) { + var row = document.getElementById(key); + if ((data[key] & type) !== 0) { + row.style.display = ''; + row.className = (count++ % 2) ? rowColor : altColor; + } + else + row.style.display = 'none'; + } + updateTabs(type); +} + +function updateTabs(type) { + for (var value in tabs) { + var sNode = document.getElementById(tabs[value][0]); + var spanNode = sNode.firstChild; + if (value == type) { + sNode.className = activeTableTab; + spanNode.innerHTML = tabs[value][1]; + } + else { + sNode.className = tableTab; + spanNode.innerHTML = "" + tabs[value][1] + ""; + } + } +} + +function updateModuleFrame(pFrame, cFrame) { + top.packageFrame.location = pFrame; + top.classFrame.location = cFrame; +} diff --git a/docs/java/search.js b/docs/java/search.js new file mode 100644 index 000000000..b773531bd --- /dev/null +++ b/docs/java/search.js @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +var noResult = {l: "No results found"}; +var catModules = "Modules"; +var catPackages = "Packages"; +var catTypes = "Types"; +var catMembers = "Members"; +var catSearchTags = "SearchTags"; +var highlight = "$&"; +var camelCaseRegexp = ""; +var secondaryMatcher = ""; +function getHighlightedText(item) { + var ccMatcher = new RegExp(camelCaseRegexp); + var label = item.replace(ccMatcher, highlight); + if (label === item) { + label = item.replace(secondaryMatcher, highlight); + } + return label; +} +function getURLPrefix(ui) { + var urlPrefix=""; + if (useModuleDirectories) { + var slash = "/"; + if (ui.item.category === catModules) { + return ui.item.l + slash; + } else if (ui.item.category === catPackages && ui.item.m) { + return ui.item.m + slash; + } else if ((ui.item.category === catTypes && ui.item.p) || ui.item.category === catMembers) { + $.each(packageSearchIndex, function(index, item) { + if (ui.item.p == item.l) { + urlPrefix = item.m + slash; + } + }); + return urlPrefix; + } else { + return urlPrefix; + } + } + return urlPrefix; +} +var watermark = 'Search'; +$(function() { + $("#search").val(''); + $("#search").prop("disabled", false); + $("#reset").prop("disabled", false); + $("#search").val(watermark).addClass('watermark'); + $("#search").blur(function() { + if ($(this).val().length == 0) { + $(this).val(watermark).addClass('watermark'); + } + }); + $("#search").on('click keydown', function() { + if ($(this).val() == watermark) { + $(this).val('').removeClass('watermark'); + } + }); + $("#reset").click(function() { + $("#search").val(''); + $("#search").focus(); + }); + $("#search").focus(); + $("#search")[0].setSelectionRange(0, 0); +}); +$.widget("custom.catcomplete", $.ui.autocomplete, { + _create: function() { + this._super(); + this.widget().menu("option", "items", "> :not(.ui-autocomplete-category)"); + }, + _renderMenu: function(ul, items) { + var rMenu = this, + currentCategory = ""; + rMenu.menu.bindings = $(); + $.each(items, function(index, item) { + var li; + if (item.l !== noResult.l && item.category !== currentCategory) { + ul.append("
  • " + item.category + "
  • "); + currentCategory = item.category; + } + li = rMenu._renderItemData(ul, item); + if (item.category) { + li.attr("aria-label", item.category + " : " + item.l); + li.attr("class", "resultItem"); + } else { + li.attr("aria-label", item.l); + li.attr("class", "resultItem"); + } + }); + }, + _renderItem: function(ul, item) { + var label = ""; + if (item.category === catModules) { + label = getHighlightedText(item.l); + } else if (item.category === catPackages) { + label = (item.m) + ? getHighlightedText(item.m + "/" + item.l) + : getHighlightedText(item.l); + } else if (item.category === catTypes) { + label = (item.p) + ? getHighlightedText(item.p + "." + item.l) + : getHighlightedText(item.l); + } else if (item.category === catMembers) { + label = getHighlightedText(item.p + "." + (item.c + "." + item.l)); + } else if (item.category === catSearchTags) { + label = getHighlightedText(item.l); + } else { + label = item.l; + } + var li = $("
  • ").appendTo(ul); + var div = $("
    ").appendTo(li); + if (item.category === catSearchTags) { + if (item.d) { + div.html(label + " (" + item.h + ")
    " + + item.d + "
    "); + } else { + div.html(label + " (" + item.h + ")"); + } + } else { + div.html(label); + } + return li; + } +}); +$(function() { + $("#search").catcomplete({ + minLength: 1, + delay: 100, + source: function(request, response) { + var result = new Array(); + var presult = new Array(); + var tresult = new Array(); + var mresult = new Array(); + var tgresult = new Array(); + var secondaryresult = new Array(); + var displayCount = 0; + var exactMatcher = new RegExp("^" + $.ui.autocomplete.escapeRegex(request.term) + "$", "i"); + camelCaseRegexp = ($.ui.autocomplete.escapeRegex(request.term)).split(/(?=[A-Z])/).join("([a-z0-9_$]*?)"); + var camelCaseMatcher = new RegExp("^" + camelCaseRegexp); + secondaryMatcher = new RegExp($.ui.autocomplete.escapeRegex(request.term), "i"); + + // Return the nested innermost name from the specified object + function nestedName(e) { + return e.l.substring(e.l.lastIndexOf(".") + 1); + } + + function concatResults(a1, a2) { + a1 = a1.concat(a2); + a2.length = 0; + return a1; + } + + if (moduleSearchIndex) { + var mdleCount = 0; + $.each(moduleSearchIndex, function(index, item) { + item.category = catModules; + if (exactMatcher.test(item.l)) { + result.push(item); + mdleCount++; + } else if (camelCaseMatcher.test(item.l)) { + result.push(item); + } else if (secondaryMatcher.test(item.l)) { + secondaryresult.push(item); + } + }); + displayCount = mdleCount; + result = concatResults(result, secondaryresult); + } + if (packageSearchIndex) { + var pCount = 0; + var pkg = ""; + $.each(packageSearchIndex, function(index, item) { + item.category = catPackages; + pkg = (item.m) + ? (item.m + "/" + item.l) + : item.l; + if (exactMatcher.test(item.l)) { + presult.push(item); + pCount++; + } else if (camelCaseMatcher.test(pkg)) { + presult.push(item); + } else if (secondaryMatcher.test(pkg)) { + secondaryresult.push(item); + } + }); + result = result.concat(concatResults(presult, secondaryresult)); + displayCount = (pCount > displayCount) ? pCount : displayCount; + } + if (typeSearchIndex) { + var tCount = 0; + $.each(typeSearchIndex, function(index, item) { + item.category = catTypes; + var s = nestedName(item); + if (exactMatcher.test(s)) { + tresult.push(item); + tCount++; + } else if (camelCaseMatcher.test(s)) { + tresult.push(item); + } else if (secondaryMatcher.test(item.p + "." + item.l)) { + secondaryresult.push(item); + } + }); + result = result.concat(concatResults(tresult, secondaryresult)); + displayCount = (tCount > displayCount) ? tCount : displayCount; + } + if (memberSearchIndex) { + var mCount = 0; + $.each(memberSearchIndex, function(index, item) { + item.category = catMembers; + var s = nestedName(item); + if (exactMatcher.test(s)) { + mresult.push(item); + mCount++; + } else if (camelCaseMatcher.test(s)) { + mresult.push(item); + } else if (secondaryMatcher.test(item.c + "." + item.l)) { + secondaryresult.push(item); + } + }); + result = result.concat(concatResults(mresult, secondaryresult)); + displayCount = (mCount > displayCount) ? mCount : displayCount; + } + if (tagSearchIndex) { + var tgCount = 0; + $.each(tagSearchIndex, function(index, item) { + item.category = catSearchTags; + if (exactMatcher.test(item.l)) { + tgresult.push(item); + tgCount++; + } else if (secondaryMatcher.test(item.l)) { + secondaryresult.push(item); + } + }); + result = result.concat(concatResults(tgresult, secondaryresult)); + displayCount = (tgCount > displayCount) ? tgCount : displayCount; + } + displayCount = (displayCount > 500) ? displayCount : 500; + var counter = function() { + var count = {Modules: 0, Packages: 0, Types: 0, Members: 0, SearchTags: 0}; + var f = function(item) { + count[item.category] += 1; + return (count[item.category] <= displayCount); + }; + return f; + }(); + response(result.filter(counter)); + }, + response: function(event, ui) { + if (!ui.content.length) { + ui.content.push(noResult); + } else { + $("#search").empty(); + } + }, + autoFocus: true, + position: { + collision: "flip" + }, + select: function(event, ui) { + if (ui.item.l !== noResult.l) { + var url = getURLPrefix(ui); + if (ui.item.category === catModules) { + if (useModuleDirectories) { + url += "module-summary.html"; + } else { + url = ui.item.l + "-summary.html"; + } + } else if (ui.item.category === catPackages) { + if (ui.item.url) { + url = ui.item.url; + } else { + url += ui.item.l.replace(/\./g, '/') + "/package-summary.html"; + } + } else if (ui.item.category === catTypes) { + if (ui.item.url) { + url = ui.item.url; + } else if (ui.item.p === "") { + url += ui.item.l + ".html"; + } else { + url += ui.item.p.replace(/\./g, '/') + "/" + ui.item.l + ".html"; + } + } else if (ui.item.category === catMembers) { + if (ui.item.p === "") { + url += ui.item.c + ".html" + "#"; + } else { + url += ui.item.p.replace(/\./g, '/') + "/" + ui.item.c + ".html" + "#"; + } + if (ui.item.url) { + url += ui.item.url; + } else { + url += ui.item.l; + } + } else if (ui.item.category === catSearchTags) { + url += ui.item.u; + } + if (top !== window) { + parent.classFrame.location = pathtoroot + url; + } else { + window.location.href = pathtoroot + url; + } + $("#search").focus(); + } + } + }); +}); diff --git a/docs/java/serialized-form.html b/docs/java/serialized-form.html new file mode 100644 index 000000000..c29224ec1 --- /dev/null +++ b/docs/java/serialized-form.html @@ -0,0 +1,432 @@ + + + + + +Serialized Form + + + + + + + + + + + + + +
    + +
    +
    +
    +

    Serialized Form

    +
    +
    + +
    +
    +
    + +

    Copyright (©) 2020, The Gust Framework Authors

    +
    + + diff --git a/docs/java/src-html/gust/Core.html b/docs/java/src-html/gust/Core.html new file mode 100644 index 000000000..dc8e73a8d --- /dev/null +++ b/docs/java/src-html/gust/Core.html @@ -0,0 +1,187 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust;
    +014
    +015import jsinterop.annotations.JsType;
    +016
    +017
    +018/**
    +019 * Provides core values, utility methods, etc, which can be used throughout the back- and front-end of a Gust-based
    +020 * application. Some of these methods or properties will return different values based on where the application is
    +021 * executed. So, accessing, say, {@link #getEngine()} will return {@code browser} when invoked from JavaScript on the
    +022 * front-end, and one of {@code jvm} or {@code native} when running on the backend.
    +023 */
    +024@JsType
    +025@SuppressWarnings("WeakerAccess")
    +026public final class Core {
    +027  /** Holds the current runtime engine. */
    +028  static final String currentEngine = System.getProperty("gust.engine", "unknown");
    +029
    +030  /** Version of the framework. Injected at build time. */
    +031  static final String frameworkVersion = System.getProperty("gust.version", "alpha");
    +032
    +033  /** Holds the current `dev` flag status. */
    +034  static final Boolean devMode = Boolean.parseBoolean(System.getProperty("gust.dev", "true"));
    +035
    +036  /** Holds the current `debug` flag status. */
    +037  static final Boolean debugMode = Boolean.parseBoolean(System.getProperty("gust.debug", "true"));
    +038
    +039  /** Holds the static prefix for dynamically routed assets. */
    +040  static final String dynamicAssetPrefix = "/_/assets";
    +041
    +042  private Core() { /* Disallow instantiation. */ }
    +043
    +044  /**
    +045   * Retrieve the application version setting, which is applied via the JVM system property <pre>gust.version</pre>.
    +046   * This value also shows up in frontend libraries as <pre>gust.version</pre>. The default value for this property, if
    +047   * left unspecified by the runtime, is `alpha`.
    +048   *
    +049   * @return Version assigned for the currently-running application.
    +050   **/
    +051  public static String getGustVersion() {
    +052    return frameworkVersion;
    +053  }
    +054
    +055  /**
    +056   * Retrieve the current engine running this code. By default, this is the string `unknown`. When running in frontend
    +057   * contexts, this is generally overridden to be `browser`. On the backend, this value is generally either `jvm` or
    +058   * `native`, depending on how the code is executing.
    +059   *
    +060   * @return Current execution engine.
    +061   */
    +062  public static String getEngine() {
    +063    return currentEngine;
    +064  }
    +065
    +066  /**
    +067   * Returns the current debug-mode flag state, which indicates whether we are running in a debugging-compatible mode or
    +068   * not. If not, we can generally expect a stripped binary (on the front and back-end), and perhaps expect that symbol
    +069   * renaming is active.
    +070   *
    +071   * @return Whether we are running in debug mode, or production mode (in which case this is `false`).
    +072   */
    +073  public static Boolean isDebugMode() {
    +074    return debugMode;
    +075  }
    +076
    +077  /**
    +078   * Returns the current dev-mode flag state, which indicates whether we are running locally/in a development context.
    +079   * This is intentionally separate from {@link #isDebugMode()} to facilitate "production" binary testing in a local
    +080   * setting. Flip this flag to `true` to enable local RPC calls, logging, and so on, but with a production-optimized
    +081   * set of artifacts.
    +082   *
    +083   * @return Whether we are running in dev mode, or production mode (in which case this is `false`).
    +084   */
    +085  public static Boolean isDevMode() {
    +086    return devMode;
    +087  }
    +088
    +089  /**
    +090   * "Production mode" is so-called because both {@link #isDebugMode()} and {@link #isDevMode()} return `false`. In this
    +091   * circumstance, we are running entirely in a mode for production use, in a production context. RPCs should be sent to
    +092   * endpoints that write and read to/from production databases.
    +093   *
    +094   * @return Whether we are running in production mode or not (`true` when `dev` and `debug` are both `false`).
    +095   */
    +096  public static Boolean isProductionMode() {
    +097    return !debugMode && !devMode;
    +098  }
    +099
    +100  /**
    +101   * The <i>dynamic asset prefix</i> is a URL prefix under which frontend application assets are served, using a managed
    +102   * file and token scheme, with a generated binary asset manifest living at the root of the JAR. Using this feature,
    +103   * the server auto-detects managed assets and includes them in the page when directed.
    +104   *
    +105   * <p>All assets, stylesheets and scripts included, are served under this URL if they are "managed assets," meaning
    +106   * they are defined in the asset manifest and enclosed in the JAR.</p>
    +107   *
    +108   * @return Dynamic frontend asset serving prefix.
    +109   */
    +110  public static String getDynamicAssetPrefix() {
    +111    return dynamicAssetPrefix;
    +112  }
    +113}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/AppController.html b/docs/java/src-html/gust/backend/AppController.html new file mode 100644 index 000000000..ac99dc24c --- /dev/null +++ b/docs/java/src-html/gust/backend/AppController.html @@ -0,0 +1,421 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015import com.google.common.base.Joiner;
    +016import com.google.common.html.types.TrustedResourceUrlProto;
    +017import gust.backend.runtime.Logging;
    +018import io.micronaut.http.HttpHeaders;
    +019import io.micronaut.http.HttpResponse;
    +020import io.micronaut.http.MediaType;
    +021import io.micronaut.http.MutableHttpResponse;
    +022import org.slf4j.Logger;
    +023import tools.elide.page.Context.ClientHint;
    +024import tools.elide.page.Context.FramingPolicy;
    +025import tools.elide.page.Context.ReferrerPolicy;
    +026
    +027import javax.annotation.Nonnull;
    +028import javax.inject.Inject;
    +029import java.net.URI;
    +030import java.net.URL;
    +031import java.nio.charset.StandardCharsets;
    +032import java.util.*;
    +033import java.util.function.BiConsumer;
    +034
    +035import static java.lang.String.format;
    +036
    +037
    +038/**
    +039 * Describes a framework application controller, which is pre-loaded with convenient access to tooling, and any injected
    +040 * application logic. Alternatively, if the invoking developer wishes to use their own base class, they can acquire most
    +041 * or all of the enclosed functionality via injection.
    +042 *
    +043 * <p>Various properties are made available to sub-classes which allow easy access to stuff like:</p>
    +044 * <ul>
    +045 *   <li><b>{@code context}:</b> This property exposes a builder for the current flow's page context. This can be
    +046 *       manipulated to provide/inject properties, and then subsequently used in Soy.</li>
    +047 * </ul>
    +048 *
    +049 * <p>To make use of this controller, simply inherit from it in your own {@code @Controller}-annotated class. When a
    +050 * request method is invoked, the logic provided by this object will have been initialized and will be ready to use.</p>
    +051 */
    +052@SuppressWarnings("unused")
    +053public abstract class AppController extends BaseController {
    +054  /** Private logging pipe for {@code AppController}. */
    +055  private final Logger logging = Logging.logger(AppController.class);
    +056
    +057  /** Pre-ordained HTML object which ensures the character encoding is set to UTF-8. */
    +058  protected final static @Nonnull MediaType HTML;
    +059
    +060  /** Configuration for dynamic serving. */
    +061  private @Inject DynamicServingConfiguration servingConfiguration;
    +062
    +063  /** Configuration for dynamic serving. */
    +064  private @Inject AssetConfiguration assetConfiguration;
    +065
    +066  static {
    +067    // initialize HTML page type
    +068    HTML = new MediaType(MediaType.TEXT_HTML_TYPE.getName(), MediaType.TEXT_HTML_TYPE.getExtension(),
    +069      Collections.singletonMap("charset", StandardCharsets.UTF_8.displayName()));
    +070  }
    +071
    +072  /**
    +073   * Initialize a new application controller.
    +074   *
    +075   * @param context Injected page context manager.
    +076   */
    +077  protected AppController(@Nonnull PageContextManager context) {
    +078    super(context);
    +079  }
    +080
    +081  // -- API: Trusted Resources (Delegated to Context) -- //
    +082
    +083  /**
    +084   * Generate a trusted resource URL for the provided Java URL.
    +085   *
    +086   * @param url Pre-ordained trusted resource URL.
    +087   * @return Trusted resource URL specification proto.
    +088   */
    +089  public @Nonnull TrustedResourceUrlProto trustedResource(@Nonnull URL url) {
    +090    return context.trustedResource(url);
    +091  }
    +092
    +093  /**
    +094   * Generate a trusted resource URL for the provided Java URI.
    +095   *
    +096   * @param uri Pre-ordained trusted resource URI.
    +097   * @return Trusted resource URL specification proto.
    +098   */
    +099  public @Nonnull TrustedResourceUrlProto trustedResource(@Nonnull URI uri) {
    +100    return context.trustedResource(uri);
    +101  }
    +102
    +103  // -- API: Configurable Serving -- //
    +104
    +105  /**
    +106   * Affix headers to the provided response, according to the provided app {@code config} for dynamic serving. The
    +107   * resulting response is kept mutable for further changes.
    +108   *
    +109   * @param response HTTP response to affix headers to.
    +110   * @return HTTP response, with headers affixed.
    +111   */
    +112  @SuppressWarnings({"WeakerAccess", "SameParameterValue"})
    +113  protected @Nonnull <T> MutableHttpResponse<T> affixHeaders(@Nonnull MutableHttpResponse<T> response,
    +114                                                             @Nonnull DynamicServingConfiguration config) {
    +115    // first up: content language
    +116    if (this.context.language().isPresent()) {
    +117      var lang = this.context.language().get();
    +118      logging.debug(format("Affixing `Content-Language` header from context: '%s'.", lang));
    +119      response.setAttribute("language", lang);
    +120    } else if (config.language().isPresent()) {
    +121      logging.debug(format("Affixing `Content-Language` header from config: '%s'.", config.language().get()));
    +122      response.setAttribute("language", config.language().get());
    +123      this.context.language(config.language());
    +124    } else if (logging.isTraceEnabled()) {
    +125      logging.trace("No `Content-Language` header value to set.");
    +126    }
    +127
    +128    // next up: etags
    +129    if (config.etags().enabled()) {
    +130      if (logging.isTraceEnabled())
    +131        logging.trace("Dynamic `ETag` values are enabled.");
    +132      this.context.enableETags(true);
    +133    } else if (logging.isTraceEnabled()) {
    +134      logging.trace("Dynamic `ETag` values are disabled.");
    +135    }
    +136
    +137    // next up: client hints
    +138    if (config.clientHints().enabled()) {
    +139      Set<ClientHint> hints = config.clientHints().hints();
    +140      if (!hints.isEmpty()) {
    +141        if (logging.isDebugEnabled())
    +142          logging.debug(format("Client hints are ENABLED. Applying hints: '%s'.", Joiner.on(", ").join(hints)));
    +143        this.context.supportedClientHints(
    +144          Optional.of(hints),
    +145          config.clientHints().ttl().isPresent() ?
    +146            Optional.of(config.clientHints().ttlUnit().toSeconds(config.clientHints().ttl().get())) :
    +147            Optional.empty());
    +148
    +149      } else if (logging.isTraceEnabled()) {
    +150        logging.trace("No client hints are enabled.");
    +151      }
    +152    } else if (logging.isTraceEnabled()) {
    +153      logging.trace("Client hints are DISABLED.");
    +154    }
    +155
    +156    // next up: vary
    +157    if (config.variance().enabled()) {
    +158      Set<String> varySet = new TreeSet<>();
    +159      response.setAttribute("vary", varySet);
    +160      DynamicServingConfiguration.DynamicVarianceConfiguration varyConfig = config.variance();
    +161
    +162      BiConsumer<Boolean, String> affixVarySegment = (condition, header) -> {
    +163        if (condition) {
    +164          varySet.add(header);
    +165          if (logging.isDebugEnabled()) {
    +166            logging.debug(format("Indicating dynamic response variance by `%s` (due to config).", header));
    +167          }
    +168        } else if (logging.isTraceEnabled()) {
    +169          logging.trace(format("Dynamic response variance by `%s` is DISABLED (due to config).", header));
    +170        }
    +171      };
    +172
    +173      affixVarySegment.accept(varyConfig.accept(), HttpHeaders.ACCEPT);  // `Accept`
    +174      affixVarySegment.accept(varyConfig.charset(), HttpHeaders.ACCEPT_CHARSET);  // `Accept-Charset`
    +175      affixVarySegment.accept(varyConfig.encoding(), HttpHeaders.ACCEPT_ENCODING);  // `Accept-Encoding`
    +176      affixVarySegment.accept(varyConfig.language(), HttpHeaders.ACCEPT_LANGUAGE);  // `Accept-Language`
    +177      affixVarySegment.accept(varyConfig.origin(), HttpHeaders.ORIGIN);  // `Origin`
    +178      if (!varySet.isEmpty()) {
    +179        if (logging.isDebugEnabled())
    +180          logging.debug(format("Indicating configured variance for response: '%s'.", Joiner.on(", ").join(varySet)));
    +181        this.context.vary(varySet);
    +182      } else if (logging.isTraceEnabled()) {
    +183        logging.trace("No variance configured for response.");
    +184      }
    +185    }
    +186
    +187    // next up: feature policy
    +188    if (config.featurePolicy().enabled()) {
    +189      SortedSet<String> featurePolicies = config.featurePolicy().policy();
    +190      if (logging.isDebugEnabled())
    +191        logging.debug(format("Indicating `Feature-Policy` for response: '%s'.", Joiner.on(", ").join(featurePolicies)));
    +192      featurePolicies.forEach((policy) ->
    +193        this.context.getContext().addFeaturePolicy(policy));
    +194    } else if (logging.isDebugEnabled()) {
    +195      logging.debug("`Feature-Policy` disabled via config.");
    +196    }
    +197
    +198    // next up: referrer policy
    +199    if (config.referrerPolicy() != ReferrerPolicy.DEFAULT_REFERRER_POLICY) {
    +200      if (logging.isDebugEnabled())
    +201        logging.debug(format("Applying `Referrer-Policy`: '%s'.", config.referrerPolicy().name()));
    +202      this.context.getContext().setReferrerPolicy(config.referrerPolicy());
    +203    } else if (logging.isDebugEnabled()) {
    +204      logging.debug("`Referrer-Policy` disabled via config.");
    +205    }
    +206
    +207    // next up: framing policy
    +208    if (config.framingPolicy() != FramingPolicy.DEFAULT_FRAMING_POLICY) {
    +209      if (logging.isDebugEnabled())
    +210        logging.debug(format("Applying `X-Frame-Options` policy: '%s'.", config.framingPolicy().name()));
    +211      this.context.getContext().setFramingPolicy(config.framingPolicy());
    +212    } else if (logging.isDebugEnabled()) {
    +213      logging.debug("`X-Frame-Options` disabled via config.");
    +214    }
    +215
    +216    // next up: DNS domain prefetch
    +217    if (!config.dnsPrefetch().isEmpty()) {
    +218      if (logging.isDebugEnabled())
    +219        logging.debug(format("Pre-fetching %s domains via browser DNS: '%s'.",
    +220          config.dnsPrefetch().size(),
    +221          Joiner.on(", ").join(config.dnsPrefetch())));
    +222
    +223      this.context.dnsPrefetch(config.dnsPrefetch());
    +224
    +225    } else if (logging.isDebugEnabled()) {
    +226      logging.debug("No domains to prefetch via DNS.");
    +227    }
    +228
    +229    // next up: domain pre-connect
    +230    if (!config.preconnect().isEmpty()) {
    +231      if (logging.isDebugEnabled())
    +232        logging.debug(format("Pre-connecting to %s domains via browser: '%s'.",
    +233          config.dnsPrefetch().size(),
    +234          Joiner.on(", ").join(config.preconnect())));
    +235
    +236      this.context.preconnect(config.preconnect());
    +237
    +238    } else if (logging.isDebugEnabled()) {
    +239      logging.debug("No domains to pre-connect to.");
    +240    }
    +241
    +242    // next up: `nosniff`
    +243    if (config.noSniff()) {
    +244      if (logging.isDebugEnabled())
    +245        logging.debug("Indicating `nosniff` for `X-Content-Type-Options`.");
    +246      this.context.getContext().setContentTypeNosniff(true);
    +247    } else if (logging.isDebugEnabled())
    +248      logging.debug("`X-Content-Type-Options` disabled via config.");
    +249
    +250    // next up: `X-XSS-Protection`
    +251    if (config.xssProtection().enabled()) {
    +252      String xssProtectToken = config.xssProtection().filter() ? "1" : "0";
    +253      String modeToken = config.xssProtection().block() ? "; mode=block" : "";
    +254      String composedHeader = xssProtectToken + modeToken;
    +255
    +256      if (logging.isDebugEnabled())
    +257        logging.debug(format("Old-style `X-XSS-Protection` is enabled. Affixing header: '%s'.", composedHeader));
    +258      this.context.getContext().setXssProtection(composedHeader);
    +259    } else if (logging.isDebugEnabled())
    +260      logging.debug("Old-style `X-XSS-Protection` is disabled by configuration.");
    +261
    +262    // finally: arbitrary headers
    +263    if (!config.additionalHeaders().isEmpty()) {
    +264      config.additionalHeaders().forEach(this.context::header);
    +265    }
    +266    return response;
    +267  }
    +268
    +269  /**
    +270   * Affix headers to the provided response, according to current app configuration for dynamic serving. The resulting
    +271   * response is kept mutable for further changes.
    +272   *
    +273   * @param response HTTP response to affix headers to.
    +274   * @return HTTP response, with headers affixed.
    +275   */
    +276  protected @Nonnull <T> MutableHttpResponse<T> affixHeaders(@Nonnull MutableHttpResponse<T> response) {
    +277    return affixHeaders(response, DynamicServingConfiguration.DEFAULTS);
    +278  }
    +279
    +280  /**
    +281   * Select the set of CDN prefixes to use in this HTTP cycle, from the configured set. Once this action completes, the
    +282   * set of CDN prefixes is considered "frozen" for this cycle.
    +283   *
    +284   * @param render Page context manager, after the handler has completed.
    +285   * @param response Response object, on which we should set the CDN prefix property.
    +286   * @param servingConfig Serving configuration from which to calculate the active set of CDN prefixes.
    +287   */
    +288  @SuppressWarnings("WeakerAccess")
    +289  protected void selectCdnPrefixes(@Nonnull PageContextManager render,
    +290                                   @Nonnull MutableHttpResponse response,
    +291                                   @Nonnull AssetConfiguration servingConfig) {
    +292    if (!render.getCdnPrefix().isPresent()) {
    +293      // resolve CDN prefix to use for this run
    +294      if (servingConfig.cdn().enabled()) {
    +295        List<String> hostnames = servingConfig.cdn().hostnames();
    +296        if (hostnames.isEmpty() && logging.isDebugEnabled()) {
    +297          logging.debug("No CDN prefixes available.");
    +298        } else if (!hostnames.isEmpty()) {
    +299          final String hostname;
    +300          if (hostnames.size() == 1) {
    +301            hostname = hostnames.get(0);
    +302          } else {
    +303            // select one
    +304            hostname = hostnames.get(new Random().nextInt(hostnames.size()));
    +305          }
    +306          if (logging.isDebugEnabled())
    +307            logging.debug(format("Selected CDN prefix for HTTP cycle: '%s'.", hostname));
    +308          response.setAttribute("cdn_prefix", hostname);
    +309          render.cdnPrefix(Optional.of(hostname));
    +310
    +311          // add to pre-connect list and DNS prefetch list
    +312          render.dnsPrefetch(hostname);
    +313          render.preconnect(hostname);
    +314        }
    +315
    +316      } else if (logging.isDebugEnabled()) {
    +317        logging.debug("CDN prefix skipped (DISABLED via config).");
    +318      }
    +319    } else if (logging.isDebugEnabled()) {
    +320      logging.debug("Deferring to developer-specified CDN prefix.");
    +321    }
    +322  }
    +323
    +324  /**
    +325   * Serve the provided rendered-page response, while applying any app configuration related to dynamic page headers.
    +326   * This may include headers like {@code Vary}, {@code ETag}, and so on, which may be calculated based on the response
    +327   * intended to be provided to the client.
    +328   *
    +329   * @param render Page render to perform before responding.
    +330   * @return Prepped and rendered HTTP response.
    +331   */
    +332  protected @Nonnull MutableHttpResponse<PageRender> serve(@Nonnull PageContextManager render) {
    +333    DynamicServingConfiguration servingConfig = (
    +334      this.servingConfiguration != null ? this.servingConfiguration : DynamicServingConfiguration.DEFAULTS);
    +335    AssetConfiguration assetConfig = (
    +336      this.assetConfiguration != null ? this.assetConfiguration : AssetConfiguration.DEFAULTS);
    +337
    +338    // order matters here. `selectCdnPrefix` must be called first, to load any CDN prefix before link pre-loads go out.
    +339    // next, `affixHeaders` must be called before `render`, which produces the `ctx` that is set on the response (so it
    +340    // may be picked up in `PageContextManager#finalizeResponse`).
    +341    MutableHttpResponse<PageRender> response = HttpResponse.ok(render);
    +342    selectCdnPrefixes(render, response, assetConfig);
    +343    this.affixHeaders(response, servingConfig);
    +344    response.setAttribute("context", render.render().getPageContext());
    +345    return response;
    +346  }
    +347}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/Application.html b/docs/java/src-html/gust/backend/Application.html new file mode 100644 index 000000000..f439b577d --- /dev/null +++ b/docs/java/src-html/gust/backend/Application.html @@ -0,0 +1,117 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015import gust.backend.runtime.AssetManager;
    +016import io.micronaut.runtime.Micronaut;
    +017
    +018
    +019/**
    +020 * Main application class, which bootstraps a backend Gust app via Micronaut, including any configured controllers,
    +021 * services, or assets. This is where execution starts when running on a JVM. Micronaut uses build-time annotation
    +022 * processing, and a number of other techniques, to pre-initialize and wire up the app before it ever gets to the
    +023 * server to be executed at runtime.
    +024 */
    +025public final class Application {
    +026  /**
    +027   * Main entrypoint into a Gust backend application, powered by Micronaut. This function will pre-load any static stuff
    +028   * that needs to be bootstrapped, and then it initializes the app via Micronaut.
    +029   *
    +030   * @param args Arguments passed on the command line.
    +031   */
    +032  public static void main(String[] args) {
    +033    try {
    +034      ApplicationBoot.load();
    +035      AssetManager.load();
    +036      Micronaut.run(Application.class);
    +037    } catch (Throwable thr) {
    +038      ApplicationBoot.reportStartupError(thr);
    +039    }
    +040  }
    +041
    +042  private Application() { /* Disallow instantiation. */ }
    +043}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/ApplicationBoot.html b/docs/java/src-html/gust/backend/ApplicationBoot.html new file mode 100644 index 000000000..b1c0c3c0a --- /dev/null +++ b/docs/java/src-html/gust/backend/ApplicationBoot.html @@ -0,0 +1,166 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015import javax.annotation.Nonnull;
    +016import javax.annotation.Nullable;
    +017import java.io.IOException;
    +018import java.io.InputStream;
    +019
    +020
    +021/**
    +022 * Responsible for any testable functionality that occurs when bootstrapping a Java-based application, including
    +023 * force-resolving critical configuration files, setting up logging, and so on.
    +024 */
    +025@SuppressWarnings("WeakerAccess")
    +026public final class ApplicationBoot {
    +027  /** Root configuration for a Micronaut app. */
    +028  public static final String rootConfig = "/application.yml";
    +029
    +030  /** Default configuration provided by Gust. */
    +031  public static final String defaultConfig = "/gust" + rootConfig;
    +032
    +033  /** Root logging configuration for a Micronaut app. */
    +034  private static final String loggingConfig = "/logback.xml";
    +035
    +036  /** Default configuration provided by Gust. */
    +037  private static final String defaultLoggingConfig = "/gust" + loggingConfig;
    +038
    +039  private ApplicationBoot() { /* Disallow instantiation. */ }
    +040
    +041  /**
    +042   * Report an error that occurred during server startup, which prevented the server from starting. Errors encountered
    +043   * and reported in this manner are fatal.
    +044   *
    +045   * @param err Fatal error that occurred and prevented server startup.
    +046   */
    +047  public static void reportStartupError(@Nonnull Throwable err) {
    +048    System.err.println("Uncaught exception: " + err.getMessage());
    +049    err.printStackTrace(System.err);
    +050    throw new IllegalStateException(
    +051      String.format("Catastrophic startup error thrown: %s.", err.getMessage()));
    +052  }
    +053
    +054  /**
    +055   * Attempt to load a given global config file, failing if we can't find it in the expected spot, or the backup spot,
    +056   * optionally provided as the second param.
    +057   *
    +058   * @param role Role description for this file.
    +059   * @param name Configuration file name.
    +060   * @param alt Alternate configuration file name.
    +061   * @throws RuntimeException Wrapping an {@link IOException}, If the configuration can't be loaded.
    +062   */
    +063  public static void loadConfig(@Nonnull String role, @Nonnull String name, @Nullable String alt) {
    +064    try (final InputStream configStream = ApplicationBoot.class.getResourceAsStream(name)) {
    +065      if (configStream == null) {
    +066        if (alt != null) {
    +067          try (final InputStream defaultConfigStream = ApplicationBoot.class.getResourceAsStream(alt)) {
    +068            if (defaultConfigStream == null)
    +069              throw new IOException("Loaded config was `null` (for configuration '" + role + "').");
    +070            return;  // we loaded it at the alternate location: good to go
    +071          }
    +072        }
    +073        throw new IOException("Config stream was `null` when loaded (for configuration '" + role + "').");
    +074      }
    +075
    +076    } catch (IOException ioe) {
    +077      System.out.println("Failed to load server configuration '" + role + "'. Failing.");
    +078      throw new RuntimeException(ioe);
    +079    }
    +080  }
    +081
    +082  /**
    +083   * Load main application configs, including the `app` config (usually `application.yml`), containing configuration for
    +084   * Micronaut, and `logback.xml` which contains configuration for logging. If either config file cannot be loaded, then
    +085   * an error is thrown which prevents server startup.
    +086   */
    +087  public static void load() {
    +088    // validate config & start the server
    +089    loadConfig("app", rootConfig, defaultConfig);
    +090    loadConfig("logging", loggingConfig, defaultLoggingConfig);
    +091  }
    +092}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/AssetConfiguration.AssetCachingConfiguration.html b/docs/java/src-html/gust/backend/AssetConfiguration.AssetCachingConfiguration.html new file mode 100644 index 000000000..4631ea95e --- /dev/null +++ b/docs/java/src-html/gust/backend/AssetConfiguration.AssetCachingConfiguration.html @@ -0,0 +1,285 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015
    +016import com.google.common.collect.ImmutableSortedSet;
    +017import io.micronaut.context.annotation.ConfigurationProperties;
    +018import io.micronaut.core.bind.annotation.Bindable;
    +019import tools.elide.core.data.CompressionMode;
    +020import tools.elide.page.Context.CrossOriginResourcePolicy;
    +021
    +022import java.util.*;
    +023import java.util.concurrent.TimeUnit;
    +024
    +025
    +026/** App configuration bindings for asset management and serving. */
    +027@ConfigurationProperties("gust.assets")
    +028public interface AssetConfiguration {
    +029  /** Sensible defaults for asset configuration. */
    +030  AssetConfiguration DEFAULTS = new AssetConfiguration() {};
    +031
    +032  /** Specifies a bump value to apply to all asset URLs. */
    +033  @Bindable(value = "bump") default Optional<Integer> bump() {
    +034    return Optional.empty();
    +035  }
    +036
    +037  /** Specifies whether to affix {@code ETag} values. */
    +038  @Bindable(value = "etags") default Boolean enableETags() {
    +039    return true;
    +040  }
    +041
    +042  /** Specifies whether to affix {@code Last-Modified} values. */
    +043  @Bindable(value = "lastModified") default Boolean enableLastModified() {
    +044    return true;
    +045  }
    +046
    +047  /** Specifies whether to affix a {@code X-Content-Type-Options} policy for {@code nosniff}. */
    +048  @Bindable(value = "noSniff") default Boolean enableNoSniff() {
    +049    return true;
    +050  }
    +051
    +052  /** Specifies settings regarding CDN use. */
    +053  @Bindable(value = "cdn") default ContentDistributionConfiguration cdn() {
    +054    return ContentDistributionConfiguration.DEFAULTS;
    +055  }
    +056
    +057  /** Specifies settings for {@code Vary} headers. */
    +058  @Bindable(value = "vary") default AssetVarianceConfiguration variance() {
    +059    return AssetVarianceConfiguration.DEFAULTS;
    +060  }
    +061
    +062  /** Specifies settings for HTTP compression. */
    +063  @Bindable(value = "compression") default AssetCompressionConfiguration compression() {
    +064    return AssetCompressionConfiguration.DEFAULTS;
    +065  }
    +066
    +067  /** Specifies settings for HTTP caching. */
    +068  @Bindable(value = "httpCaching") default AssetCachingConfiguration httpCaching() {
    +069    return AssetCachingConfiguration.DEFAULTS;
    +070  }
    +071
    +072  /** {@code Cross-Origin-Resource-Policy} configuration for dynamic content. */
    +073  @Bindable(value = "resourcePolicy") default CrossOriginResourceConfiguration crossOriginResources() {
    +074    return CrossOriginResourceConfiguration.DEFAULTS;
    +075  }
    +076
    +077  /** Describes settings regarding {@code Cross-Origin-Resource-Policy} headers for dynamic content. */
    +078  @ConfigurationProperties("gust.serving.resourcePolicy")
    +079  interface CrossOriginResourceConfiguration {
    +080    /** Sensible defaults for cross-origin resource policy. */
    +081    CrossOriginResourceConfiguration DEFAULTS = new CrossOriginResourceConfiguration() {};
    +082
    +083    /** Whether to enable {@code Cross-Origin-Resource-Policy} headers for dynamically-served content. */
    +084    @Bindable(value = "enabled") default Boolean enabled() {
    +085      return true;
    +086    }
    +087
    +088    /** Specifies the default policy to employ for {@code Cross-Origin-Resource-Policy} for dynamic content. */
    +089    @Bindable(value = "policy") default CrossOriginResourcePolicy policy() {
    +090      return CrossOriginResourcePolicy.SAME_SITE;
    +091    }
    +092  }
    +093
    +094  /** Describes settings regarding compressed asset serving. */
    +095  @ConfigurationProperties("gust.assets.compression")
    +096  interface AssetCompressionConfiguration {
    +097    /** Sensible defaults for asset compression. */
    +098    AssetCompressionConfiguration DEFAULTS = new AssetCompressionConfiguration() {};
    +099
    +100    /** Whether to enable serving of pre-compressed assets. */
    +101    @Bindable(value = "enabled") default Boolean enabled() {
    +102      return true;
    +103    }
    +104
    +105    /** Whether to enable serving of pre-compressed assets. */
    +106    @Bindable(value = "modes") default SortedSet<CompressionMode> compressionModes() {
    +107      return ImmutableSortedSet.of(CompressionMode.GZIP, CompressionMode.BROTLI);
    +108    }
    +109
    +110    /** Whether to enable the `Vary` header with regard to compression. */
    +111    @Bindable(value = "vary") default Boolean enableVary() {
    +112      return true;
    +113    }
    +114  }
    +115
    +116  /** Describes settings that control the {@code Vary} header affixed to assets. */
    +117  @ConfigurationProperties("gust.assets.vary")
    +118  interface AssetVarianceConfiguration {
    +119    /** Sensible defaults for vary headers. */
    +120    AssetVarianceConfiguration DEFAULTS = new AssetVarianceConfiguration() {};
    +121
    +122    /** Whether to enable {@code Vary} headers at all. */
    +123    @Bindable(value = "enabled") default Boolean enabled() {
    +124      return true;
    +125    }
    +126
    +127    /** Whether to vary based on {@code Accept}. */
    +128    @Bindable(value = "accept") default Boolean accept() {
    +129      return true;
    +130    }
    +131
    +132    /** Whether to vary based on {@code Accept-Language}. */
    +133    @Bindable(value = "language") default Boolean language() {
    +134      return false;
    +135    }
    +136
    +137    /** Whether to vary based on {@code Accept-Charset}. */
    +138    @Bindable(value = "charset") default Boolean charset() {
    +139      return false;
    +140    }
    +141
    +142    /** Whether to vary based on the value of {@code Origin}. */
    +143    @Bindable(value = "origin") default Boolean origin() {
    +144      return false;
    +145    }
    +146  }
    +147
    +148  /** Describes the structure of asset caching configuration. */
    +149  @ConfigurationProperties("gust.assets.httpCaching")
    +150  interface AssetCachingConfiguration {
    +151    /** Sensible defaults for asset caching over HTTP. */
    +152    AssetCachingConfiguration DEFAULTS = new AssetCachingConfiguration() {};
    +153
    +154    /** Whether to enable intelligent HTTP caching for assets served dynamically. */
    +155    @Bindable(value = "enabled") default Boolean enabled() {
    +156      return false;
    +157    }
    +158
    +159    /** Main mode to apply with regard to HTTP caching for assets served dynamically. */
    +160    @Bindable(value = "mode") default String mode() {
    +161      return "private";
    +162    }
    +163
    +164    /** Additional directives to inject into the HTTP caching header. */
    +165    @Bindable(value = "additionalDirectives") default Optional<List<String>> additionalDirectives() {
    +166      return Optional.empty();
    +167    }
    +168
    +169    /** Time-to-live value to apply to the main HTTP cache directive. Units tunable with {@link #ttlUnit()}. */
    +170    @Bindable(value = "ttl") default Long ttl() {
    +171      return 300L;
    +172    }
    +173
    +174    /** Whether to enable a shared-cache directive in the HTTP cache settings. */
    +175    @Bindable(value = "shared") default Boolean enableShared() {
    +176      return false;
    +177    }
    +178
    +179    /** When a shared-cache directive is enabled, this sets the TTL for shared caches. */
    +180    @Bindable(value = "sharedTtl") default Long sharedTtl() {
    +181      return 86400L;
    +182    }
    +183
    +184    /** Time unit to apply to the value specified by {@link #ttl()}. Defaults to {@code SECONDS}. */
    +185    default TimeUnit ttlUnit() {
    +186      return TimeUnit.SECONDS;
    +187    }
    +188
    +189    /** Time unit to apply to the value specified by {@link #sharedTtl()}. Defaults to {@code SECONDS}. */
    +190    default TimeUnit sharedTtlUnit() {
    +191      return TimeUnit.SECONDS;
    +192    }
    +193  }
    +194
    +195  /** Describes the structure of CDN-related asset settings. */
    +196  @ConfigurationProperties("gust.assets.cdn")
    +197  interface ContentDistributionConfiguration {
    +198    /** Sensible defaults for asset CDN settings. */
    +199    ContentDistributionConfiguration DEFAULTS = new ContentDistributionConfiguration() {};
    +200
    +201    /** Whether to enable CDN features. */
    +202    @Bindable(value = "enabled") default Boolean enabled() {
    +203      return true;
    +204    }
    +205
    +206    /** CDN host names to use for assets. A random selection is made from this list for each page render. */
    +207    @Bindable(value = "hostnames") default List<String> hostnames() {
    +208      return Collections.emptyList();
    +209    }
    +210  }
    +211}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/AssetConfiguration.AssetCompressionConfiguration.html b/docs/java/src-html/gust/backend/AssetConfiguration.AssetCompressionConfiguration.html new file mode 100644 index 000000000..4631ea95e --- /dev/null +++ b/docs/java/src-html/gust/backend/AssetConfiguration.AssetCompressionConfiguration.html @@ -0,0 +1,285 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015
    +016import com.google.common.collect.ImmutableSortedSet;
    +017import io.micronaut.context.annotation.ConfigurationProperties;
    +018import io.micronaut.core.bind.annotation.Bindable;
    +019import tools.elide.core.data.CompressionMode;
    +020import tools.elide.page.Context.CrossOriginResourcePolicy;
    +021
    +022import java.util.*;
    +023import java.util.concurrent.TimeUnit;
    +024
    +025
    +026/** App configuration bindings for asset management and serving. */
    +027@ConfigurationProperties("gust.assets")
    +028public interface AssetConfiguration {
    +029  /** Sensible defaults for asset configuration. */
    +030  AssetConfiguration DEFAULTS = new AssetConfiguration() {};
    +031
    +032  /** Specifies a bump value to apply to all asset URLs. */
    +033  @Bindable(value = "bump") default Optional<Integer> bump() {
    +034    return Optional.empty();
    +035  }
    +036
    +037  /** Specifies whether to affix {@code ETag} values. */
    +038  @Bindable(value = "etags") default Boolean enableETags() {
    +039    return true;
    +040  }
    +041
    +042  /** Specifies whether to affix {@code Last-Modified} values. */
    +043  @Bindable(value = "lastModified") default Boolean enableLastModified() {
    +044    return true;
    +045  }
    +046
    +047  /** Specifies whether to affix a {@code X-Content-Type-Options} policy for {@code nosniff}. */
    +048  @Bindable(value = "noSniff") default Boolean enableNoSniff() {
    +049    return true;
    +050  }
    +051
    +052  /** Specifies settings regarding CDN use. */
    +053  @Bindable(value = "cdn") default ContentDistributionConfiguration cdn() {
    +054    return ContentDistributionConfiguration.DEFAULTS;
    +055  }
    +056
    +057  /** Specifies settings for {@code Vary} headers. */
    +058  @Bindable(value = "vary") default AssetVarianceConfiguration variance() {
    +059    return AssetVarianceConfiguration.DEFAULTS;
    +060  }
    +061
    +062  /** Specifies settings for HTTP compression. */
    +063  @Bindable(value = "compression") default AssetCompressionConfiguration compression() {
    +064    return AssetCompressionConfiguration.DEFAULTS;
    +065  }
    +066
    +067  /** Specifies settings for HTTP caching. */
    +068  @Bindable(value = "httpCaching") default AssetCachingConfiguration httpCaching() {
    +069    return AssetCachingConfiguration.DEFAULTS;
    +070  }
    +071
    +072  /** {@code Cross-Origin-Resource-Policy} configuration for dynamic content. */
    +073  @Bindable(value = "resourcePolicy") default CrossOriginResourceConfiguration crossOriginResources() {
    +074    return CrossOriginResourceConfiguration.DEFAULTS;
    +075  }
    +076
    +077  /** Describes settings regarding {@code Cross-Origin-Resource-Policy} headers for dynamic content. */
    +078  @ConfigurationProperties("gust.serving.resourcePolicy")
    +079  interface CrossOriginResourceConfiguration {
    +080    /** Sensible defaults for cross-origin resource policy. */
    +081    CrossOriginResourceConfiguration DEFAULTS = new CrossOriginResourceConfiguration() {};
    +082
    +083    /** Whether to enable {@code Cross-Origin-Resource-Policy} headers for dynamically-served content. */
    +084    @Bindable(value = "enabled") default Boolean enabled() {
    +085      return true;
    +086    }
    +087
    +088    /** Specifies the default policy to employ for {@code Cross-Origin-Resource-Policy} for dynamic content. */
    +089    @Bindable(value = "policy") default CrossOriginResourcePolicy policy() {
    +090      return CrossOriginResourcePolicy.SAME_SITE;
    +091    }
    +092  }
    +093
    +094  /** Describes settings regarding compressed asset serving. */
    +095  @ConfigurationProperties("gust.assets.compression")
    +096  interface AssetCompressionConfiguration {
    +097    /** Sensible defaults for asset compression. */
    +098    AssetCompressionConfiguration DEFAULTS = new AssetCompressionConfiguration() {};
    +099
    +100    /** Whether to enable serving of pre-compressed assets. */
    +101    @Bindable(value = "enabled") default Boolean enabled() {
    +102      return true;
    +103    }
    +104
    +105    /** Whether to enable serving of pre-compressed assets. */
    +106    @Bindable(value = "modes") default SortedSet<CompressionMode> compressionModes() {
    +107      return ImmutableSortedSet.of(CompressionMode.GZIP, CompressionMode.BROTLI);
    +108    }
    +109
    +110    /** Whether to enable the `Vary` header with regard to compression. */
    +111    @Bindable(value = "vary") default Boolean enableVary() {
    +112      return true;
    +113    }
    +114  }
    +115
    +116  /** Describes settings that control the {@code Vary} header affixed to assets. */
    +117  @ConfigurationProperties("gust.assets.vary")
    +118  interface AssetVarianceConfiguration {
    +119    /** Sensible defaults for vary headers. */
    +120    AssetVarianceConfiguration DEFAULTS = new AssetVarianceConfiguration() {};
    +121
    +122    /** Whether to enable {@code Vary} headers at all. */
    +123    @Bindable(value = "enabled") default Boolean enabled() {
    +124      return true;
    +125    }
    +126
    +127    /** Whether to vary based on {@code Accept}. */
    +128    @Bindable(value = "accept") default Boolean accept() {
    +129      return true;
    +130    }
    +131
    +132    /** Whether to vary based on {@code Accept-Language}. */
    +133    @Bindable(value = "language") default Boolean language() {
    +134      return false;
    +135    }
    +136
    +137    /** Whether to vary based on {@code Accept-Charset}. */
    +138    @Bindable(value = "charset") default Boolean charset() {
    +139      return false;
    +140    }
    +141
    +142    /** Whether to vary based on the value of {@code Origin}. */
    +143    @Bindable(value = "origin") default Boolean origin() {
    +144      return false;
    +145    }
    +146  }
    +147
    +148  /** Describes the structure of asset caching configuration. */
    +149  @ConfigurationProperties("gust.assets.httpCaching")
    +150  interface AssetCachingConfiguration {
    +151    /** Sensible defaults for asset caching over HTTP. */
    +152    AssetCachingConfiguration DEFAULTS = new AssetCachingConfiguration() {};
    +153
    +154    /** Whether to enable intelligent HTTP caching for assets served dynamically. */
    +155    @Bindable(value = "enabled") default Boolean enabled() {
    +156      return false;
    +157    }
    +158
    +159    /** Main mode to apply with regard to HTTP caching for assets served dynamically. */
    +160    @Bindable(value = "mode") default String mode() {
    +161      return "private";
    +162    }
    +163
    +164    /** Additional directives to inject into the HTTP caching header. */
    +165    @Bindable(value = "additionalDirectives") default Optional<List<String>> additionalDirectives() {
    +166      return Optional.empty();
    +167    }
    +168
    +169    /** Time-to-live value to apply to the main HTTP cache directive. Units tunable with {@link #ttlUnit()}. */
    +170    @Bindable(value = "ttl") default Long ttl() {
    +171      return 300L;
    +172    }
    +173
    +174    /** Whether to enable a shared-cache directive in the HTTP cache settings. */
    +175    @Bindable(value = "shared") default Boolean enableShared() {
    +176      return false;
    +177    }
    +178
    +179    /** When a shared-cache directive is enabled, this sets the TTL for shared caches. */
    +180    @Bindable(value = "sharedTtl") default Long sharedTtl() {
    +181      return 86400L;
    +182    }
    +183
    +184    /** Time unit to apply to the value specified by {@link #ttl()}. Defaults to {@code SECONDS}. */
    +185    default TimeUnit ttlUnit() {
    +186      return TimeUnit.SECONDS;
    +187    }
    +188
    +189    /** Time unit to apply to the value specified by {@link #sharedTtl()}. Defaults to {@code SECONDS}. */
    +190    default TimeUnit sharedTtlUnit() {
    +191      return TimeUnit.SECONDS;
    +192    }
    +193  }
    +194
    +195  /** Describes the structure of CDN-related asset settings. */
    +196  @ConfigurationProperties("gust.assets.cdn")
    +197  interface ContentDistributionConfiguration {
    +198    /** Sensible defaults for asset CDN settings. */
    +199    ContentDistributionConfiguration DEFAULTS = new ContentDistributionConfiguration() {};
    +200
    +201    /** Whether to enable CDN features. */
    +202    @Bindable(value = "enabled") default Boolean enabled() {
    +203      return true;
    +204    }
    +205
    +206    /** CDN host names to use for assets. A random selection is made from this list for each page render. */
    +207    @Bindable(value = "hostnames") default List<String> hostnames() {
    +208      return Collections.emptyList();
    +209    }
    +210  }
    +211}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/AssetConfiguration.AssetVarianceConfiguration.html b/docs/java/src-html/gust/backend/AssetConfiguration.AssetVarianceConfiguration.html new file mode 100644 index 000000000..4631ea95e --- /dev/null +++ b/docs/java/src-html/gust/backend/AssetConfiguration.AssetVarianceConfiguration.html @@ -0,0 +1,285 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015
    +016import com.google.common.collect.ImmutableSortedSet;
    +017import io.micronaut.context.annotation.ConfigurationProperties;
    +018import io.micronaut.core.bind.annotation.Bindable;
    +019import tools.elide.core.data.CompressionMode;
    +020import tools.elide.page.Context.CrossOriginResourcePolicy;
    +021
    +022import java.util.*;
    +023import java.util.concurrent.TimeUnit;
    +024
    +025
    +026/** App configuration bindings for asset management and serving. */
    +027@ConfigurationProperties("gust.assets")
    +028public interface AssetConfiguration {
    +029  /** Sensible defaults for asset configuration. */
    +030  AssetConfiguration DEFAULTS = new AssetConfiguration() {};
    +031
    +032  /** Specifies a bump value to apply to all asset URLs. */
    +033  @Bindable(value = "bump") default Optional<Integer> bump() {
    +034    return Optional.empty();
    +035  }
    +036
    +037  /** Specifies whether to affix {@code ETag} values. */
    +038  @Bindable(value = "etags") default Boolean enableETags() {
    +039    return true;
    +040  }
    +041
    +042  /** Specifies whether to affix {@code Last-Modified} values. */
    +043  @Bindable(value = "lastModified") default Boolean enableLastModified() {
    +044    return true;
    +045  }
    +046
    +047  /** Specifies whether to affix a {@code X-Content-Type-Options} policy for {@code nosniff}. */
    +048  @Bindable(value = "noSniff") default Boolean enableNoSniff() {
    +049    return true;
    +050  }
    +051
    +052  /** Specifies settings regarding CDN use. */
    +053  @Bindable(value = "cdn") default ContentDistributionConfiguration cdn() {
    +054    return ContentDistributionConfiguration.DEFAULTS;
    +055  }
    +056
    +057  /** Specifies settings for {@code Vary} headers. */
    +058  @Bindable(value = "vary") default AssetVarianceConfiguration variance() {
    +059    return AssetVarianceConfiguration.DEFAULTS;
    +060  }
    +061
    +062  /** Specifies settings for HTTP compression. */
    +063  @Bindable(value = "compression") default AssetCompressionConfiguration compression() {
    +064    return AssetCompressionConfiguration.DEFAULTS;
    +065  }
    +066
    +067  /** Specifies settings for HTTP caching. */
    +068  @Bindable(value = "httpCaching") default AssetCachingConfiguration httpCaching() {
    +069    return AssetCachingConfiguration.DEFAULTS;
    +070  }
    +071
    +072  /** {@code Cross-Origin-Resource-Policy} configuration for dynamic content. */
    +073  @Bindable(value = "resourcePolicy") default CrossOriginResourceConfiguration crossOriginResources() {
    +074    return CrossOriginResourceConfiguration.DEFAULTS;
    +075  }
    +076
    +077  /** Describes settings regarding {@code Cross-Origin-Resource-Policy} headers for dynamic content. */
    +078  @ConfigurationProperties("gust.serving.resourcePolicy")
    +079  interface CrossOriginResourceConfiguration {
    +080    /** Sensible defaults for cross-origin resource policy. */
    +081    CrossOriginResourceConfiguration DEFAULTS = new CrossOriginResourceConfiguration() {};
    +082
    +083    /** Whether to enable {@code Cross-Origin-Resource-Policy} headers for dynamically-served content. */
    +084    @Bindable(value = "enabled") default Boolean enabled() {
    +085      return true;
    +086    }
    +087
    +088    /** Specifies the default policy to employ for {@code Cross-Origin-Resource-Policy} for dynamic content. */
    +089    @Bindable(value = "policy") default CrossOriginResourcePolicy policy() {
    +090      return CrossOriginResourcePolicy.SAME_SITE;
    +091    }
    +092  }
    +093
    +094  /** Describes settings regarding compressed asset serving. */
    +095  @ConfigurationProperties("gust.assets.compression")
    +096  interface AssetCompressionConfiguration {
    +097    /** Sensible defaults for asset compression. */
    +098    AssetCompressionConfiguration DEFAULTS = new AssetCompressionConfiguration() {};
    +099
    +100    /** Whether to enable serving of pre-compressed assets. */
    +101    @Bindable(value = "enabled") default Boolean enabled() {
    +102      return true;
    +103    }
    +104
    +105    /** Whether to enable serving of pre-compressed assets. */
    +106    @Bindable(value = "modes") default SortedSet<CompressionMode> compressionModes() {
    +107      return ImmutableSortedSet.of(CompressionMode.GZIP, CompressionMode.BROTLI);
    +108    }
    +109
    +110    /** Whether to enable the `Vary` header with regard to compression. */
    +111    @Bindable(value = "vary") default Boolean enableVary() {
    +112      return true;
    +113    }
    +114  }
    +115
    +116  /** Describes settings that control the {@code Vary} header affixed to assets. */
    +117  @ConfigurationProperties("gust.assets.vary")
    +118  interface AssetVarianceConfiguration {
    +119    /** Sensible defaults for vary headers. */
    +120    AssetVarianceConfiguration DEFAULTS = new AssetVarianceConfiguration() {};
    +121
    +122    /** Whether to enable {@code Vary} headers at all. */
    +123    @Bindable(value = "enabled") default Boolean enabled() {
    +124      return true;
    +125    }
    +126
    +127    /** Whether to vary based on {@code Accept}. */
    +128    @Bindable(value = "accept") default Boolean accept() {
    +129      return true;
    +130    }
    +131
    +132    /** Whether to vary based on {@code Accept-Language}. */
    +133    @Bindable(value = "language") default Boolean language() {
    +134      return false;
    +135    }
    +136
    +137    /** Whether to vary based on {@code Accept-Charset}. */
    +138    @Bindable(value = "charset") default Boolean charset() {
    +139      return false;
    +140    }
    +141
    +142    /** Whether to vary based on the value of {@code Origin}. */
    +143    @Bindable(value = "origin") default Boolean origin() {
    +144      return false;
    +145    }
    +146  }
    +147
    +148  /** Describes the structure of asset caching configuration. */
    +149  @ConfigurationProperties("gust.assets.httpCaching")
    +150  interface AssetCachingConfiguration {
    +151    /** Sensible defaults for asset caching over HTTP. */
    +152    AssetCachingConfiguration DEFAULTS = new AssetCachingConfiguration() {};
    +153
    +154    /** Whether to enable intelligent HTTP caching for assets served dynamically. */
    +155    @Bindable(value = "enabled") default Boolean enabled() {
    +156      return false;
    +157    }
    +158
    +159    /** Main mode to apply with regard to HTTP caching for assets served dynamically. */
    +160    @Bindable(value = "mode") default String mode() {
    +161      return "private";
    +162    }
    +163
    +164    /** Additional directives to inject into the HTTP caching header. */
    +165    @Bindable(value = "additionalDirectives") default Optional<List<String>> additionalDirectives() {
    +166      return Optional.empty();
    +167    }
    +168
    +169    /** Time-to-live value to apply to the main HTTP cache directive. Units tunable with {@link #ttlUnit()}. */
    +170    @Bindable(value = "ttl") default Long ttl() {
    +171      return 300L;
    +172    }
    +173
    +174    /** Whether to enable a shared-cache directive in the HTTP cache settings. */
    +175    @Bindable(value = "shared") default Boolean enableShared() {
    +176      return false;
    +177    }
    +178
    +179    /** When a shared-cache directive is enabled, this sets the TTL for shared caches. */
    +180    @Bindable(value = "sharedTtl") default Long sharedTtl() {
    +181      return 86400L;
    +182    }
    +183
    +184    /** Time unit to apply to the value specified by {@link #ttl()}. Defaults to {@code SECONDS}. */
    +185    default TimeUnit ttlUnit() {
    +186      return TimeUnit.SECONDS;
    +187    }
    +188
    +189    /** Time unit to apply to the value specified by {@link #sharedTtl()}. Defaults to {@code SECONDS}. */
    +190    default TimeUnit sharedTtlUnit() {
    +191      return TimeUnit.SECONDS;
    +192    }
    +193  }
    +194
    +195  /** Describes the structure of CDN-related asset settings. */
    +196  @ConfigurationProperties("gust.assets.cdn")
    +197  interface ContentDistributionConfiguration {
    +198    /** Sensible defaults for asset CDN settings. */
    +199    ContentDistributionConfiguration DEFAULTS = new ContentDistributionConfiguration() {};
    +200
    +201    /** Whether to enable CDN features. */
    +202    @Bindable(value = "enabled") default Boolean enabled() {
    +203      return true;
    +204    }
    +205
    +206    /** CDN host names to use for assets. A random selection is made from this list for each page render. */
    +207    @Bindable(value = "hostnames") default List<String> hostnames() {
    +208      return Collections.emptyList();
    +209    }
    +210  }
    +211}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/AssetConfiguration.ContentDistributionConfiguration.html b/docs/java/src-html/gust/backend/AssetConfiguration.ContentDistributionConfiguration.html new file mode 100644 index 000000000..4631ea95e --- /dev/null +++ b/docs/java/src-html/gust/backend/AssetConfiguration.ContentDistributionConfiguration.html @@ -0,0 +1,285 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015
    +016import com.google.common.collect.ImmutableSortedSet;
    +017import io.micronaut.context.annotation.ConfigurationProperties;
    +018import io.micronaut.core.bind.annotation.Bindable;
    +019import tools.elide.core.data.CompressionMode;
    +020import tools.elide.page.Context.CrossOriginResourcePolicy;
    +021
    +022import java.util.*;
    +023import java.util.concurrent.TimeUnit;
    +024
    +025
    +026/** App configuration bindings for asset management and serving. */
    +027@ConfigurationProperties("gust.assets")
    +028public interface AssetConfiguration {
    +029  /** Sensible defaults for asset configuration. */
    +030  AssetConfiguration DEFAULTS = new AssetConfiguration() {};
    +031
    +032  /** Specifies a bump value to apply to all asset URLs. */
    +033  @Bindable(value = "bump") default Optional<Integer> bump() {
    +034    return Optional.empty();
    +035  }
    +036
    +037  /** Specifies whether to affix {@code ETag} values. */
    +038  @Bindable(value = "etags") default Boolean enableETags() {
    +039    return true;
    +040  }
    +041
    +042  /** Specifies whether to affix {@code Last-Modified} values. */
    +043  @Bindable(value = "lastModified") default Boolean enableLastModified() {
    +044    return true;
    +045  }
    +046
    +047  /** Specifies whether to affix a {@code X-Content-Type-Options} policy for {@code nosniff}. */
    +048  @Bindable(value = "noSniff") default Boolean enableNoSniff() {
    +049    return true;
    +050  }
    +051
    +052  /** Specifies settings regarding CDN use. */
    +053  @Bindable(value = "cdn") default ContentDistributionConfiguration cdn() {
    +054    return ContentDistributionConfiguration.DEFAULTS;
    +055  }
    +056
    +057  /** Specifies settings for {@code Vary} headers. */
    +058  @Bindable(value = "vary") default AssetVarianceConfiguration variance() {
    +059    return AssetVarianceConfiguration.DEFAULTS;
    +060  }
    +061
    +062  /** Specifies settings for HTTP compression. */
    +063  @Bindable(value = "compression") default AssetCompressionConfiguration compression() {
    +064    return AssetCompressionConfiguration.DEFAULTS;
    +065  }
    +066
    +067  /** Specifies settings for HTTP caching. */
    +068  @Bindable(value = "httpCaching") default AssetCachingConfiguration httpCaching() {
    +069    return AssetCachingConfiguration.DEFAULTS;
    +070  }
    +071
    +072  /** {@code Cross-Origin-Resource-Policy} configuration for dynamic content. */
    +073  @Bindable(value = "resourcePolicy") default CrossOriginResourceConfiguration crossOriginResources() {
    +074    return CrossOriginResourceConfiguration.DEFAULTS;
    +075  }
    +076
    +077  /** Describes settings regarding {@code Cross-Origin-Resource-Policy} headers for dynamic content. */
    +078  @ConfigurationProperties("gust.serving.resourcePolicy")
    +079  interface CrossOriginResourceConfiguration {
    +080    /** Sensible defaults for cross-origin resource policy. */
    +081    CrossOriginResourceConfiguration DEFAULTS = new CrossOriginResourceConfiguration() {};
    +082
    +083    /** Whether to enable {@code Cross-Origin-Resource-Policy} headers for dynamically-served content. */
    +084    @Bindable(value = "enabled") default Boolean enabled() {
    +085      return true;
    +086    }
    +087
    +088    /** Specifies the default policy to employ for {@code Cross-Origin-Resource-Policy} for dynamic content. */
    +089    @Bindable(value = "policy") default CrossOriginResourcePolicy policy() {
    +090      return CrossOriginResourcePolicy.SAME_SITE;
    +091    }
    +092  }
    +093
    +094  /** Describes settings regarding compressed asset serving. */
    +095  @ConfigurationProperties("gust.assets.compression")
    +096  interface AssetCompressionConfiguration {
    +097    /** Sensible defaults for asset compression. */
    +098    AssetCompressionConfiguration DEFAULTS = new AssetCompressionConfiguration() {};
    +099
    +100    /** Whether to enable serving of pre-compressed assets. */
    +101    @Bindable(value = "enabled") default Boolean enabled() {
    +102      return true;
    +103    }
    +104
    +105    /** Whether to enable serving of pre-compressed assets. */
    +106    @Bindable(value = "modes") default SortedSet<CompressionMode> compressionModes() {
    +107      return ImmutableSortedSet.of(CompressionMode.GZIP, CompressionMode.BROTLI);
    +108    }
    +109
    +110    /** Whether to enable the `Vary` header with regard to compression. */
    +111    @Bindable(value = "vary") default Boolean enableVary() {
    +112      return true;
    +113    }
    +114  }
    +115
    +116  /** Describes settings that control the {@code Vary} header affixed to assets. */
    +117  @ConfigurationProperties("gust.assets.vary")
    +118  interface AssetVarianceConfiguration {
    +119    /** Sensible defaults for vary headers. */
    +120    AssetVarianceConfiguration DEFAULTS = new AssetVarianceConfiguration() {};
    +121
    +122    /** Whether to enable {@code Vary} headers at all. */
    +123    @Bindable(value = "enabled") default Boolean enabled() {
    +124      return true;
    +125    }
    +126
    +127    /** Whether to vary based on {@code Accept}. */
    +128    @Bindable(value = "accept") default Boolean accept() {
    +129      return true;
    +130    }
    +131
    +132    /** Whether to vary based on {@code Accept-Language}. */
    +133    @Bindable(value = "language") default Boolean language() {
    +134      return false;
    +135    }
    +136
    +137    /** Whether to vary based on {@code Accept-Charset}. */
    +138    @Bindable(value = "charset") default Boolean charset() {
    +139      return false;
    +140    }
    +141
    +142    /** Whether to vary based on the value of {@code Origin}. */
    +143    @Bindable(value = "origin") default Boolean origin() {
    +144      return false;
    +145    }
    +146  }
    +147
    +148  /** Describes the structure of asset caching configuration. */
    +149  @ConfigurationProperties("gust.assets.httpCaching")
    +150  interface AssetCachingConfiguration {
    +151    /** Sensible defaults for asset caching over HTTP. */
    +152    AssetCachingConfiguration DEFAULTS = new AssetCachingConfiguration() {};
    +153
    +154    /** Whether to enable intelligent HTTP caching for assets served dynamically. */
    +155    @Bindable(value = "enabled") default Boolean enabled() {
    +156      return false;
    +157    }
    +158
    +159    /** Main mode to apply with regard to HTTP caching for assets served dynamically. */
    +160    @Bindable(value = "mode") default String mode() {
    +161      return "private";
    +162    }
    +163
    +164    /** Additional directives to inject into the HTTP caching header. */
    +165    @Bindable(value = "additionalDirectives") default Optional<List<String>> additionalDirectives() {
    +166      return Optional.empty();
    +167    }
    +168
    +169    /** Time-to-live value to apply to the main HTTP cache directive. Units tunable with {@link #ttlUnit()}. */
    +170    @Bindable(value = "ttl") default Long ttl() {
    +171      return 300L;
    +172    }
    +173
    +174    /** Whether to enable a shared-cache directive in the HTTP cache settings. */
    +175    @Bindable(value = "shared") default Boolean enableShared() {
    +176      return false;
    +177    }
    +178
    +179    /** When a shared-cache directive is enabled, this sets the TTL for shared caches. */
    +180    @Bindable(value = "sharedTtl") default Long sharedTtl() {
    +181      return 86400L;
    +182    }
    +183
    +184    /** Time unit to apply to the value specified by {@link #ttl()}. Defaults to {@code SECONDS}. */
    +185    default TimeUnit ttlUnit() {
    +186      return TimeUnit.SECONDS;
    +187    }
    +188
    +189    /** Time unit to apply to the value specified by {@link #sharedTtl()}. Defaults to {@code SECONDS}. */
    +190    default TimeUnit sharedTtlUnit() {
    +191      return TimeUnit.SECONDS;
    +192    }
    +193  }
    +194
    +195  /** Describes the structure of CDN-related asset settings. */
    +196  @ConfigurationProperties("gust.assets.cdn")
    +197  interface ContentDistributionConfiguration {
    +198    /** Sensible defaults for asset CDN settings. */
    +199    ContentDistributionConfiguration DEFAULTS = new ContentDistributionConfiguration() {};
    +200
    +201    /** Whether to enable CDN features. */
    +202    @Bindable(value = "enabled") default Boolean enabled() {
    +203      return true;
    +204    }
    +205
    +206    /** CDN host names to use for assets. A random selection is made from this list for each page render. */
    +207    @Bindable(value = "hostnames") default List<String> hostnames() {
    +208      return Collections.emptyList();
    +209    }
    +210  }
    +211}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/AssetConfiguration.CrossOriginResourceConfiguration.html b/docs/java/src-html/gust/backend/AssetConfiguration.CrossOriginResourceConfiguration.html new file mode 100644 index 000000000..4631ea95e --- /dev/null +++ b/docs/java/src-html/gust/backend/AssetConfiguration.CrossOriginResourceConfiguration.html @@ -0,0 +1,285 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015
    +016import com.google.common.collect.ImmutableSortedSet;
    +017import io.micronaut.context.annotation.ConfigurationProperties;
    +018import io.micronaut.core.bind.annotation.Bindable;
    +019import tools.elide.core.data.CompressionMode;
    +020import tools.elide.page.Context.CrossOriginResourcePolicy;
    +021
    +022import java.util.*;
    +023import java.util.concurrent.TimeUnit;
    +024
    +025
    +026/** App configuration bindings for asset management and serving. */
    +027@ConfigurationProperties("gust.assets")
    +028public interface AssetConfiguration {
    +029  /** Sensible defaults for asset configuration. */
    +030  AssetConfiguration DEFAULTS = new AssetConfiguration() {};
    +031
    +032  /** Specifies a bump value to apply to all asset URLs. */
    +033  @Bindable(value = "bump") default Optional<Integer> bump() {
    +034    return Optional.empty();
    +035  }
    +036
    +037  /** Specifies whether to affix {@code ETag} values. */
    +038  @Bindable(value = "etags") default Boolean enableETags() {
    +039    return true;
    +040  }
    +041
    +042  /** Specifies whether to affix {@code Last-Modified} values. */
    +043  @Bindable(value = "lastModified") default Boolean enableLastModified() {
    +044    return true;
    +045  }
    +046
    +047  /** Specifies whether to affix a {@code X-Content-Type-Options} policy for {@code nosniff}. */
    +048  @Bindable(value = "noSniff") default Boolean enableNoSniff() {
    +049    return true;
    +050  }
    +051
    +052  /** Specifies settings regarding CDN use. */
    +053  @Bindable(value = "cdn") default ContentDistributionConfiguration cdn() {
    +054    return ContentDistributionConfiguration.DEFAULTS;
    +055  }
    +056
    +057  /** Specifies settings for {@code Vary} headers. */
    +058  @Bindable(value = "vary") default AssetVarianceConfiguration variance() {
    +059    return AssetVarianceConfiguration.DEFAULTS;
    +060  }
    +061
    +062  /** Specifies settings for HTTP compression. */
    +063  @Bindable(value = "compression") default AssetCompressionConfiguration compression() {
    +064    return AssetCompressionConfiguration.DEFAULTS;
    +065  }
    +066
    +067  /** Specifies settings for HTTP caching. */
    +068  @Bindable(value = "httpCaching") default AssetCachingConfiguration httpCaching() {
    +069    return AssetCachingConfiguration.DEFAULTS;
    +070  }
    +071
    +072  /** {@code Cross-Origin-Resource-Policy} configuration for dynamic content. */
    +073  @Bindable(value = "resourcePolicy") default CrossOriginResourceConfiguration crossOriginResources() {
    +074    return CrossOriginResourceConfiguration.DEFAULTS;
    +075  }
    +076
    +077  /** Describes settings regarding {@code Cross-Origin-Resource-Policy} headers for dynamic content. */
    +078  @ConfigurationProperties("gust.serving.resourcePolicy")
    +079  interface CrossOriginResourceConfiguration {
    +080    /** Sensible defaults for cross-origin resource policy. */
    +081    CrossOriginResourceConfiguration DEFAULTS = new CrossOriginResourceConfiguration() {};
    +082
    +083    /** Whether to enable {@code Cross-Origin-Resource-Policy} headers for dynamically-served content. */
    +084    @Bindable(value = "enabled") default Boolean enabled() {
    +085      return true;
    +086    }
    +087
    +088    /** Specifies the default policy to employ for {@code Cross-Origin-Resource-Policy} for dynamic content. */
    +089    @Bindable(value = "policy") default CrossOriginResourcePolicy policy() {
    +090      return CrossOriginResourcePolicy.SAME_SITE;
    +091    }
    +092  }
    +093
    +094  /** Describes settings regarding compressed asset serving. */
    +095  @ConfigurationProperties("gust.assets.compression")
    +096  interface AssetCompressionConfiguration {
    +097    /** Sensible defaults for asset compression. */
    +098    AssetCompressionConfiguration DEFAULTS = new AssetCompressionConfiguration() {};
    +099
    +100    /** Whether to enable serving of pre-compressed assets. */
    +101    @Bindable(value = "enabled") default Boolean enabled() {
    +102      return true;
    +103    }
    +104
    +105    /** Whether to enable serving of pre-compressed assets. */
    +106    @Bindable(value = "modes") default SortedSet<CompressionMode> compressionModes() {
    +107      return ImmutableSortedSet.of(CompressionMode.GZIP, CompressionMode.BROTLI);
    +108    }
    +109
    +110    /** Whether to enable the `Vary` header with regard to compression. */
    +111    @Bindable(value = "vary") default Boolean enableVary() {
    +112      return true;
    +113    }
    +114  }
    +115
    +116  /** Describes settings that control the {@code Vary} header affixed to assets. */
    +117  @ConfigurationProperties("gust.assets.vary")
    +118  interface AssetVarianceConfiguration {
    +119    /** Sensible defaults for vary headers. */
    +120    AssetVarianceConfiguration DEFAULTS = new AssetVarianceConfiguration() {};
    +121
    +122    /** Whether to enable {@code Vary} headers at all. */
    +123    @Bindable(value = "enabled") default Boolean enabled() {
    +124      return true;
    +125    }
    +126
    +127    /** Whether to vary based on {@code Accept}. */
    +128    @Bindable(value = "accept") default Boolean accept() {
    +129      return true;
    +130    }
    +131
    +132    /** Whether to vary based on {@code Accept-Language}. */
    +133    @Bindable(value = "language") default Boolean language() {
    +134      return false;
    +135    }
    +136
    +137    /** Whether to vary based on {@code Accept-Charset}. */
    +138    @Bindable(value = "charset") default Boolean charset() {
    +139      return false;
    +140    }
    +141
    +142    /** Whether to vary based on the value of {@code Origin}. */
    +143    @Bindable(value = "origin") default Boolean origin() {
    +144      return false;
    +145    }
    +146  }
    +147
    +148  /** Describes the structure of asset caching configuration. */
    +149  @ConfigurationProperties("gust.assets.httpCaching")
    +150  interface AssetCachingConfiguration {
    +151    /** Sensible defaults for asset caching over HTTP. */
    +152    AssetCachingConfiguration DEFAULTS = new AssetCachingConfiguration() {};
    +153
    +154    /** Whether to enable intelligent HTTP caching for assets served dynamically. */
    +155    @Bindable(value = "enabled") default Boolean enabled() {
    +156      return false;
    +157    }
    +158
    +159    /** Main mode to apply with regard to HTTP caching for assets served dynamically. */
    +160    @Bindable(value = "mode") default String mode() {
    +161      return "private";
    +162    }
    +163
    +164    /** Additional directives to inject into the HTTP caching header. */
    +165    @Bindable(value = "additionalDirectives") default Optional<List<String>> additionalDirectives() {
    +166      return Optional.empty();
    +167    }
    +168
    +169    /** Time-to-live value to apply to the main HTTP cache directive. Units tunable with {@link #ttlUnit()}. */
    +170    @Bindable(value = "ttl") default Long ttl() {
    +171      return 300L;
    +172    }
    +173
    +174    /** Whether to enable a shared-cache directive in the HTTP cache settings. */
    +175    @Bindable(value = "shared") default Boolean enableShared() {
    +176      return false;
    +177    }
    +178
    +179    /** When a shared-cache directive is enabled, this sets the TTL for shared caches. */
    +180    @Bindable(value = "sharedTtl") default Long sharedTtl() {
    +181      return 86400L;
    +182    }
    +183
    +184    /** Time unit to apply to the value specified by {@link #ttl()}. Defaults to {@code SECONDS}. */
    +185    default TimeUnit ttlUnit() {
    +186      return TimeUnit.SECONDS;
    +187    }
    +188
    +189    /** Time unit to apply to the value specified by {@link #sharedTtl()}. Defaults to {@code SECONDS}. */
    +190    default TimeUnit sharedTtlUnit() {
    +191      return TimeUnit.SECONDS;
    +192    }
    +193  }
    +194
    +195  /** Describes the structure of CDN-related asset settings. */
    +196  @ConfigurationProperties("gust.assets.cdn")
    +197  interface ContentDistributionConfiguration {
    +198    /** Sensible defaults for asset CDN settings. */
    +199    ContentDistributionConfiguration DEFAULTS = new ContentDistributionConfiguration() {};
    +200
    +201    /** Whether to enable CDN features. */
    +202    @Bindable(value = "enabled") default Boolean enabled() {
    +203      return true;
    +204    }
    +205
    +206    /** CDN host names to use for assets. A random selection is made from this list for each page render. */
    +207    @Bindable(value = "hostnames") default List<String> hostnames() {
    +208      return Collections.emptyList();
    +209    }
    +210  }
    +211}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/AssetConfiguration.html b/docs/java/src-html/gust/backend/AssetConfiguration.html new file mode 100644 index 000000000..4631ea95e --- /dev/null +++ b/docs/java/src-html/gust/backend/AssetConfiguration.html @@ -0,0 +1,285 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015
    +016import com.google.common.collect.ImmutableSortedSet;
    +017import io.micronaut.context.annotation.ConfigurationProperties;
    +018import io.micronaut.core.bind.annotation.Bindable;
    +019import tools.elide.core.data.CompressionMode;
    +020import tools.elide.page.Context.CrossOriginResourcePolicy;
    +021
    +022import java.util.*;
    +023import java.util.concurrent.TimeUnit;
    +024
    +025
    +026/** App configuration bindings for asset management and serving. */
    +027@ConfigurationProperties("gust.assets")
    +028public interface AssetConfiguration {
    +029  /** Sensible defaults for asset configuration. */
    +030  AssetConfiguration DEFAULTS = new AssetConfiguration() {};
    +031
    +032  /** Specifies a bump value to apply to all asset URLs. */
    +033  @Bindable(value = "bump") default Optional<Integer> bump() {
    +034    return Optional.empty();
    +035  }
    +036
    +037  /** Specifies whether to affix {@code ETag} values. */
    +038  @Bindable(value = "etags") default Boolean enableETags() {
    +039    return true;
    +040  }
    +041
    +042  /** Specifies whether to affix {@code Last-Modified} values. */
    +043  @Bindable(value = "lastModified") default Boolean enableLastModified() {
    +044    return true;
    +045  }
    +046
    +047  /** Specifies whether to affix a {@code X-Content-Type-Options} policy for {@code nosniff}. */
    +048  @Bindable(value = "noSniff") default Boolean enableNoSniff() {
    +049    return true;
    +050  }
    +051
    +052  /** Specifies settings regarding CDN use. */
    +053  @Bindable(value = "cdn") default ContentDistributionConfiguration cdn() {
    +054    return ContentDistributionConfiguration.DEFAULTS;
    +055  }
    +056
    +057  /** Specifies settings for {@code Vary} headers. */
    +058  @Bindable(value = "vary") default AssetVarianceConfiguration variance() {
    +059    return AssetVarianceConfiguration.DEFAULTS;
    +060  }
    +061
    +062  /** Specifies settings for HTTP compression. */
    +063  @Bindable(value = "compression") default AssetCompressionConfiguration compression() {
    +064    return AssetCompressionConfiguration.DEFAULTS;
    +065  }
    +066
    +067  /** Specifies settings for HTTP caching. */
    +068  @Bindable(value = "httpCaching") default AssetCachingConfiguration httpCaching() {
    +069    return AssetCachingConfiguration.DEFAULTS;
    +070  }
    +071
    +072  /** {@code Cross-Origin-Resource-Policy} configuration for dynamic content. */
    +073  @Bindable(value = "resourcePolicy") default CrossOriginResourceConfiguration crossOriginResources() {
    +074    return CrossOriginResourceConfiguration.DEFAULTS;
    +075  }
    +076
    +077  /** Describes settings regarding {@code Cross-Origin-Resource-Policy} headers for dynamic content. */
    +078  @ConfigurationProperties("gust.serving.resourcePolicy")
    +079  interface CrossOriginResourceConfiguration {
    +080    /** Sensible defaults for cross-origin resource policy. */
    +081    CrossOriginResourceConfiguration DEFAULTS = new CrossOriginResourceConfiguration() {};
    +082
    +083    /** Whether to enable {@code Cross-Origin-Resource-Policy} headers for dynamically-served content. */
    +084    @Bindable(value = "enabled") default Boolean enabled() {
    +085      return true;
    +086    }
    +087
    +088    /** Specifies the default policy to employ for {@code Cross-Origin-Resource-Policy} for dynamic content. */
    +089    @Bindable(value = "policy") default CrossOriginResourcePolicy policy() {
    +090      return CrossOriginResourcePolicy.SAME_SITE;
    +091    }
    +092  }
    +093
    +094  /** Describes settings regarding compressed asset serving. */
    +095  @ConfigurationProperties("gust.assets.compression")
    +096  interface AssetCompressionConfiguration {
    +097    /** Sensible defaults for asset compression. */
    +098    AssetCompressionConfiguration DEFAULTS = new AssetCompressionConfiguration() {};
    +099
    +100    /** Whether to enable serving of pre-compressed assets. */
    +101    @Bindable(value = "enabled") default Boolean enabled() {
    +102      return true;
    +103    }
    +104
    +105    /** Whether to enable serving of pre-compressed assets. */
    +106    @Bindable(value = "modes") default SortedSet<CompressionMode> compressionModes() {
    +107      return ImmutableSortedSet.of(CompressionMode.GZIP, CompressionMode.BROTLI);
    +108    }
    +109
    +110    /** Whether to enable the `Vary` header with regard to compression. */
    +111    @Bindable(value = "vary") default Boolean enableVary() {
    +112      return true;
    +113    }
    +114  }
    +115
    +116  /** Describes settings that control the {@code Vary} header affixed to assets. */
    +117  @ConfigurationProperties("gust.assets.vary")
    +118  interface AssetVarianceConfiguration {
    +119    /** Sensible defaults for vary headers. */
    +120    AssetVarianceConfiguration DEFAULTS = new AssetVarianceConfiguration() {};
    +121
    +122    /** Whether to enable {@code Vary} headers at all. */
    +123    @Bindable(value = "enabled") default Boolean enabled() {
    +124      return true;
    +125    }
    +126
    +127    /** Whether to vary based on {@code Accept}. */
    +128    @Bindable(value = "accept") default Boolean accept() {
    +129      return true;
    +130    }
    +131
    +132    /** Whether to vary based on {@code Accept-Language}. */
    +133    @Bindable(value = "language") default Boolean language() {
    +134      return false;
    +135    }
    +136
    +137    /** Whether to vary based on {@code Accept-Charset}. */
    +138    @Bindable(value = "charset") default Boolean charset() {
    +139      return false;
    +140    }
    +141
    +142    /** Whether to vary based on the value of {@code Origin}. */
    +143    @Bindable(value = "origin") default Boolean origin() {
    +144      return false;
    +145    }
    +146  }
    +147
    +148  /** Describes the structure of asset caching configuration. */
    +149  @ConfigurationProperties("gust.assets.httpCaching")
    +150  interface AssetCachingConfiguration {
    +151    /** Sensible defaults for asset caching over HTTP. */
    +152    AssetCachingConfiguration DEFAULTS = new AssetCachingConfiguration() {};
    +153
    +154    /** Whether to enable intelligent HTTP caching for assets served dynamically. */
    +155    @Bindable(value = "enabled") default Boolean enabled() {
    +156      return false;
    +157    }
    +158
    +159    /** Main mode to apply with regard to HTTP caching for assets served dynamically. */
    +160    @Bindable(value = "mode") default String mode() {
    +161      return "private";
    +162    }
    +163
    +164    /** Additional directives to inject into the HTTP caching header. */
    +165    @Bindable(value = "additionalDirectives") default Optional<List<String>> additionalDirectives() {
    +166      return Optional.empty();
    +167    }
    +168
    +169    /** Time-to-live value to apply to the main HTTP cache directive. Units tunable with {@link #ttlUnit()}. */
    +170    @Bindable(value = "ttl") default Long ttl() {
    +171      return 300L;
    +172    }
    +173
    +174    /** Whether to enable a shared-cache directive in the HTTP cache settings. */
    +175    @Bindable(value = "shared") default Boolean enableShared() {
    +176      return false;
    +177    }
    +178
    +179    /** When a shared-cache directive is enabled, this sets the TTL for shared caches. */
    +180    @Bindable(value = "sharedTtl") default Long sharedTtl() {
    +181      return 86400L;
    +182    }
    +183
    +184    /** Time unit to apply to the value specified by {@link #ttl()}. Defaults to {@code SECONDS}. */
    +185    default TimeUnit ttlUnit() {
    +186      return TimeUnit.SECONDS;
    +187    }
    +188
    +189    /** Time unit to apply to the value specified by {@link #sharedTtl()}. Defaults to {@code SECONDS}. */
    +190    default TimeUnit sharedTtlUnit() {
    +191      return TimeUnit.SECONDS;
    +192    }
    +193  }
    +194
    +195  /** Describes the structure of CDN-related asset settings. */
    +196  @ConfigurationProperties("gust.assets.cdn")
    +197  interface ContentDistributionConfiguration {
    +198    /** Sensible defaults for asset CDN settings. */
    +199    ContentDistributionConfiguration DEFAULTS = new ContentDistributionConfiguration() {};
    +200
    +201    /** Whether to enable CDN features. */
    +202    @Bindable(value = "enabled") default Boolean enabled() {
    +203      return true;
    +204    }
    +205
    +206    /** CDN host names to use for assets. A random selection is made from this list for each page render. */
    +207    @Bindable(value = "hostnames") default List<String> hostnames() {
    +208      return Collections.emptyList();
    +209    }
    +210  }
    +211}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/AssetController.AssetsAwareCspFilter.html b/docs/java/src-html/gust/backend/AssetController.AssetsAwareCspFilter.html new file mode 100644 index 000000000..ca3a21b49 --- /dev/null +++ b/docs/java/src-html/gust/backend/AssetController.AssetsAwareCspFilter.html @@ -0,0 +1,721 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015
    +016import com.google.common.annotations.VisibleForTesting;
    +017import com.google.common.base.Joiner;
    +018import com.google.common.collect.ImmutableSet;
    +019import com.google.common.collect.Sets;
    +020import com.google.protobuf.Timestamp;
    +021import gust.Core;
    +022import gust.backend.runtime.AssetManager;
    +023import gust.backend.runtime.Logging;
    +024import io.micronaut.context.annotation.Replaces;
    +025import io.micronaut.core.util.StringUtils;
    +026import io.micronaut.http.*;
    +027import io.micronaut.http.annotation.Controller;
    +028import io.micronaut.http.annotation.Get;
    +029import io.micronaut.http.filter.ServerFilterChain;
    +030import io.micronaut.security.annotation.Secured;
    +031import io.micronaut.validation.Validated;
    +032import io.micronaut.views.csp.CspConfiguration;
    +033import io.micronaut.views.csp.CspFilter;
    +034import io.reactivex.Flowable;
    +035import org.reactivestreams.Publisher;
    +036import org.slf4j.Logger;
    +037import tools.elide.core.data.CompressedData;
    +038import tools.elide.core.data.CompressionMode;
    +039import tools.elide.page.Context.CrossOriginResourcePolicy;
    +040
    +041import javax.annotation.Nonnull;
    +042import javax.annotation.Nullable;
    +043import javax.inject.Inject;
    +044
    +045import java.nio.charset.StandardCharsets;
    +046import java.text.ParseException;
    +047import java.text.SimpleDateFormat;
    +048import java.time.Instant;
    +049import java.util.*;
    +050import java.util.concurrent.TimeUnit;
    +051import java.util.stream.Collectors;
    +052
    +053import static java.lang.String.format;
    +054
    +055
    +056/**
    +057 * Built-in backend controller for serving dynamic managed assets. Assets managed in this manner are hooked into the
    +058 * build pipeline, so that a binary asset manifest is produced at the root of the application JAR.
    +059 *
    +060 * <p>{@link AssetManager} loads the binary manifest/bundle, and then using this controller, we serve the URLs and
    +061 * assets mentioned therein. Controllers may then reference these assets (to be served here), via utility methods on
    +062 * {@link BaseController}.</p>
    +063 */
    +064@Controller("/_/assets")
    +065@Secured("isAnonymous()")
    +066public class AssetController {
    +067  /** Private logging pipe. */
    +068  private static final @Nonnull Logger logging = Logging.logger(AssetController.class);
    +069
    +070  /** Testing flag, indicating that compression should be forced, regardless of whether it saves time/space. */
    +071  private static final boolean FORCE_COMPRESSION_IF_SUPPORTED = false;
    +072
    +073  /** Static {@code Content-Encoding} value for non-compressed content. */
    +074  private static final @Nonnull String IDENTITY_CONTENT_ENCODING = "identity";
    +075
    +076  /** Static {@code Content-Encoding} value for gzip-compressed content. */
    +077  private static final @Nonnull String GZIP_CONTENT_ENCODING = "gzip";
    +078
    +079  /** Static {@code Content-Encoding} value for Brotli-compressed content. */
    +080  private static final @Nonnull String BROTLI_CONTENT_ENCODING = "br";
    +081
    +082  /** Static {@code Cross-Origin-Resource-Policy} header name. */
    +083  private static final @Nonnull String CROSS_ORIGIN_RESOURCE_POLICY_HEADER = "Cross-Origin-Resource-Policy";
    +084
    +085  /** Date/time tool for HTTP-formatted timestamps. */
    +086  private static final @Nonnull SimpleDateFormat isoDateTimeFormat = (
    +087    new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz"));
    +088
    +089  /** Main asset manager singleton. */
    +090  private final @Nonnull AssetManager assetManager;
    +091
    +092  /** Configuration for the asset engine. */
    +093  private final @Nonnull AssetConfiguration config;
    +094
    +095  /** Replacement for {@link CspFilter} which disables itself when assets (or non-HTML content) are served. */
    +096  @Replaces(CspFilter.class)
    +097  public static final class AssetsAwareCspFilter extends CspFilter {
    +098    AssetsAwareCspFilter(Optional<CspConfiguration> cspConfiguration) {
    +099      super(cspConfiguration.isPresent() ? cspConfiguration.get() :
    +100        new CspConfiguration() {
    +101          @Override
    +102          public boolean isEnabled() { return false; }
    +103
    +104          @Override
    +105          public Optional<String> getPolicyDirectives() { return Optional.empty(); }
    +106        } /** pass in defaults */);
    +107    }
    +108
    +109    private static boolean nonAssetRequest(@Nonnull HttpRequest request) {
    +110      return !(request.getPath().startsWith(Core.getDynamicAssetPrefix()));
    +111    }
    +112
    +113    private @Nullable String nonceValue(@Nonnull HttpRequest<?> request) {
    +114      return (this.cspConfiguration.isNonceEnabled() && nonAssetRequest(request)) ?
    +115        this.cspConfiguration.generateNonce() : null;
    +116    }
    +117
    +118    @Override
    +119    public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
    +120      String nonce = this.nonceValue(request);
    +121      if (logging.isDebugEnabled())
    +122        logging.debug(format("Generated nonce: '%s'.", nonce));
    +123      return Flowable.fromPublisher(
    +124        chain.proceed(request.setAttribute("cspNonce", nonce))).doOnNext((response) -> {
    +125        if (nonAssetRequest(request)
    +126            && request.getContentType().orElse(MediaType.TEXT_HTML_TYPE).equals(MediaType.TEXT_HTML_TYPE)) {
    +127          if (logging.isDebugEnabled())
    +128            logging.debug("Encountered a non-asset request. Applying CSP.");
    +129
    +130          // it's not an asset request. apply CSP.
    +131          this.cspConfiguration.getPolicyDirectives().map(StringUtils::trimToNull).ifPresent((directives) -> {
    +132            String header = this.cspConfiguration.isReportOnly() ?
    +133              "Content-Security-Policy-Report-Only" : "Content-Security-Policy";
    +134            String headerValue;
    +135            if (directives.contains("{#nonceValue}")) {
    +136              if (nonce == null) {
    +137                throw new IllegalArgumentException(
    +138                  "Must enable CSP nonce generation to use '{#nonceValue}' placeholder.");
    +139              }
    +140
    +141              headerValue = directives.replace("{#nonceValue}", nonce);
    +142            } else {
    +143              headerValue = directives;
    +144            }
    +145            response.getHeaders().add(header, headerValue);
    +146          });
    +147        } else {
    +148          // it's an asset request. let it through without appending CSP.
    +149          if (logging.isDebugEnabled())
    +150            logging.debug("CSP policy skipped because this request is being served as an asset.");
    +151        }
    +152      });
    +153    }
    +154  }
    +155
    +156  /** Construct a new asset controller from scratch. Usually invoked from DI. */
    +157  @Inject AssetController(@Nonnull AssetConfiguration config,
    +158                          @Nonnull AssetManager assetManager) {
    +159    this.config = config;
    +160    this.assetManager = assetManager;
    +161  }
    +162
    +163  /**
    +164   * Produce a token for the specified {@code Cross-Origin-Resource-Policy}.
    +165   *
    +166   * @param policy Policy to produce a token for.
    +167   * @return String token.
    +168   */
    +169  @VisibleForTesting
    +170  @SuppressWarnings("WeakerAccess")
    +171  static @Nonnull Optional<String> tokenForCrossOriginResourcePolicy(@Nonnull CrossOriginResourcePolicy policy) {
    +172    switch (policy) {
    +173      case SAME_SITE: return Optional.of("same-site");
    +174      case SAME_ORIGIN: return Optional.of("same-origin");
    +175      case CROSS_ORIGIN: return Optional.of("cross-origin");
    +176      default: return Optional.empty();
    +177    }
    +178  }
    +179
    +180  /** Resolve a {@code Content-Encoding} value for the supplied {@code compressionMode}. */
    +181  private @Nonnull CharSequence resolveContentEncoding(@Nonnull CompressionMode compressionMode) {
    +182    switch (compressionMode) {
    +183      case IDENTITY: return IDENTITY_CONTENT_ENCODING;
    +184      case GZIP: return GZIP_CONTENT_ENCODING;
    +185      case BROTLI: return BROTLI_CONTENT_ENCODING;
    +186      default:
    +187        throw new IllegalStateException(format("Unrecognized compression mode: '%s'.", compressionMode.name()));
    +188    }
    +189  }
    +190
    +191  /** Resolve a {@code Content-Type} value for the supplied {@code ext}. */
    +192  private @Nonnull MediaType resolveMediaType(@Nonnull String ext) {
    +193    final MediaType mediaType;
    +194    Optional<MediaType> mediaTypeResolved = MediaType.forExtension(ext);
    +195    if (!mediaTypeResolved.isPresent()) {
    +196      logging.error(format("Failed to resolve MIME type for asset extension '%s'", ext));
    +197      mediaType = MediaType.APPLICATION_OCTET_STREAM_TYPE;
    +198    } else {
    +199      String charsetToken = StandardCharsets.UTF_8.displayName();
    +200      mediaType = new MediaType(
    +201        mediaTypeResolved.get().getName(),
    +202        mediaTypeResolved.get().getExtension(),
    +203        Collections.singletonMap("charset", charsetToken));
    +204      if (logging.isTraceEnabled())
    +205        logging.trace(format("Resolved MIME type for asset as '%s' (extension '%s', charset '%s').",
    +206          mediaType.getType(),
    +207          ext,
    +208          charsetToken));
    +209    }
    +210    return mediaType;
    +211  }
    +212
    +213  /** Resolve an HTTP-compliant timestamp value from a Protobuf timestamp, for the {@code Last-Modified} header. */
    +214  private @Nonnull Optional<String> resolveLastModified(@Nonnull Timestamp timestamp) {
    +215    //noinspection ConstantConditions
    +216    if (timestamp == null || !timestamp.isInitialized() || timestamp.getSeconds() < 100) {
    +217      return Optional.empty();
    +218    }
    +219    return Optional.of(
    +220      isoDateTimeFormat.format(Date.from(Instant.ofEpochSecond(timestamp.getSeconds()))));
    +221  }
    +222
    +223  /** Setup HTTP cache control settings. */
    +224  private void resolveCacheControl(@Nonnull MutableHttpResponse response) {
    +225    if (response.getHeaders().contains("Set-Cookie")) {
    +226      logging.warn("Refusing to enable HTTP cache control on response: noticed `Set-Cookie` header.");
    +227      return;
    +228    }
    +229
    +230    // resolve main TTL
    +231    var mode = config.httpCaching().mode();
    +232    var ttl = config.httpCaching().ttl();
    +233    var ttlUnit = config.httpCaching().ttlUnit();
    +234    var ttlValue = ttlUnit.convert(ttl, TimeUnit.SECONDS);
    +235
    +236    StringBuilder spec = new StringBuilder();
    +237    if (mode != null && !mode.isEmpty() && ttlValue > -1L) {
    +238      if (logging.isDebugEnabled())
    +239        logging.debug(format("Indicating `Cache-Control` mode and TTL: %s, max-age=%s.",
    +240          mode,
    +241          String.valueOf(ttl)));
    +242
    +243      spec.append(format("%s; max-age=%s",
    +244        mode,
    +245        String.valueOf(ttl)));
    +246
    +247      if (config.httpCaching().enableShared()) {
    +248        // affix the shared-cache settings, too, if so directed
    +249        var sharedTtl = config.httpCaching().sharedTtl();
    +250        var sharedTtlUnit = config.httpCaching().sharedTtlUnit();
    +251        var sharedTtlValue = sharedTtlUnit.convert(sharedTtl, TimeUnit.SECONDS);
    +252        if (sharedTtlValue > -1L) {
    +253          if (logging.isDebugEnabled())
    +254            logging.debug(format("Indicating `Cache-Control` shared TTL: s-max-age=%s.",
    +255              String.valueOf(sharedTtlValue)));
    +256
    +257          spec.append(format(" s-max-age=%s%s",
    +258            String.valueOf(sharedTtlValue),
    +259            config.httpCaching().additionalDirectives().isPresent() ?
    +260              " " + Joiner.on(" ").join(config.httpCaching().additionalDirectives().get()) :
    +261              ""));
    +262
    +263          if (logging.isDebugEnabled() && config.httpCaching().additionalDirectives().isPresent()) {
    +264            logging.debug(format("Indicating `Cache-Control` additional directives: %s.",
    +265              Joiner.on(" ").join(config.httpCaching().additionalDirectives().get())));
    +266          }
    +267        } else {
    +268          logging.warn("`Cache-Control` shared caching was enabled, but a TTL value was invalid or missing.");
    +269        }
    +270      } else if (logging.isDebugEnabled()) {
    +271        logging.debug("Shared HTTP caching is disabled.");
    +272      }
    +273    } else {
    +274      logging.warn("`Cache-Control` value was invalid or missing.");
    +275    }
    +276    var renderedSpec = spec.toString();
    +277    if (!renderedSpec.isEmpty()) {
    +278      response.header(HttpHeaders.CACHE_CONTROL, renderedSpec);
    +279    }
    +280  }
    +281
    +282  /** Affix headers for the asset to the response. */
    +283  private void affixHeaders(@Nonnull MediaType type,
    +284                            @Nonnull CompressionMode compression,
    +285                            @Nonnull Timestamp modified,
    +286                            @Nonnull AssetManager.ManagedAssetContent asset,
    +287                            @Nonnull MutableHttpResponse response) {
    +288    // start with content type and encodings
    +289    response.contentType(type);
    +290    var contentEncoding = resolveContentEncoding(compression);
    +291    response.contentEncoding(contentEncoding);
    +292    if (logging.isTraceEnabled()) {
    +293      logging.trace(format("Indicating `Content-Encoding`: '%s'.", contentEncoding));
    +294    }
    +295
    +296    // next up is `Vary`
    +297    if (config.variance().enabled()) {
    +298      Set<String> segments = new TreeSet<>();
    +299
    +300      // if the asset is a CSS or JS bundle, we must examine configuration to decide whether to specify `Accept` as a
    +301      // `Vary` header entry, which is generally done to enable differential serving of photos (as WebP, for instance).
    +302      if (type.getName().equals("css") || type.getName().equals("javascript") && config.variance().accept())
    +303        segments.add(HttpHeaders.ACCEPT);
    +304      else if (logging.isDebugEnabled())
    +305        logging.debug(format("Asset type '%s' did not qualify for `Accept` variance.", type));
    +306
    +307      // if the application is internationalized, we can indicate asset variance based on the provided request value of
    +308      // the `Accept-Language` header, so apply that if it is configured.
    +309      if (config.variance().language()) segments.add(HttpHeaders.ACCEPT_LANGUAGE);
    +310      else if (logging.isDebugEnabled()) logging.debug("Asset variance by language is disabled in asset config.");
    +311
    +312      // if the application is internationalized to the point of using custom/special charsets, we can indicate asset
    +313      // variance based on the browser's accepted character sets. this is generally a bad idea, but is needed for some
    +314      // corner cases, so we apply it here.
    +315      if (config.variance().charset()) segments.add(HttpHeaders.ACCEPT_CHARSET);
    +316      else if (logging.isDebugEnabled()) logging.debug("Asset variance by charset is disabled in asset config.");
    +317
    +318      // if the application hosts dynamically originated content (i.e. in a multi-tenant posture), config can opt-in to
    +319      // varying by browser origin.
    +320      if (config.variance().origin()) segments.add(HttpHeaders.ORIGIN);
    +321      else if (logging.isDebugEnabled()) logging.debug("Asset variance by variance is disabled in asset config.");
    +322
    +323      // if we have more than one representation of the asset, and compression is enabled, and `Vary` is not disabled,
    +324      // we must affix the `Vary` tag. generally for CSS and JS this might include `Accept`, so we look for that in the
    +325      // asset configuration, too.
    +326      if (config.compression().enabled() && config.compression().enableVary()
    +327          && asset.getContent().getVariantList().size() > 1)
    +328        segments.add(HttpHeaders.ACCEPT_ENCODING);
    +329      else if (logging.isDebugEnabled())
    +330        logging.debug("No need for compression variance: too few representations, or disabled by config.");
    +331
    +332      if (!segments.isEmpty()) {
    +333        String varyHeader = Joiner.on(", ").join(segments);
    +334        if (logging.isDebugEnabled())
    +335          logging.debug(format("Calculated `Vary` header value: '%s'.", varyHeader));
    +336        response.header(HttpHeaders.VARY, varyHeader);
    +337
    +338      } else if (logging.isDebugEnabled()) {
    +339        logging.debug("No segments to apply to the `Vary` header value. Skipping.");
    +340      }
    +341    }
    +342
    +343    // next up is etags and last-modified
    +344    if (config.enableETags()) {
    +345      if (logging.isDebugEnabled())
    +346        logging.debug(format("Indicating `ETag`: '%s'.", asset.getETag()));
    +347      response.header(HttpHeaders.ETAG, "\"" + asset.getETag() + "\"");
    +348    } else if (logging.isDebugEnabled()) {
    +349      logging.debug("`ETag`s are disabled.");
    +350    }
    +351
    +352    if (config.enableLastModified()) {
    +353      Optional<String> lastModifiedValue = resolveLastModified(modified);
    +354      if (lastModifiedValue.isPresent()) {
    +355        String lastModified = lastModifiedValue.get();
    +356        response.header(HttpHeaders.LAST_MODIFIED, lastModified);
    +357        if (logging.isDebugEnabled())
    +358          logging.debug(format("Indicating `Last-Modified`: '%s'.", lastModified));
    +359      } else {
    +360        logging.warn(format("`Last-Modified` headers are enabled, but none could be generated for asset '%s'.",
    +361          asset.getToken()));
    +362      }
    +363    } else if (logging.isDebugEnabled()) {
    +364      logging.debug("`Last-Modified` is disabled.");
    +365    }
    +366
    +367    // mention original filename if operating in debug mode
    +368    if (Core.isDebugMode()) {
    +369      logging.debug(format("Serving managed asset '%s'.", asset.getFilename()));
    +370      response.header(HttpHeaders.CONTENT_DISPOSITION, format("inline; filename=\"%s\"", asset.getFilename()));
    +371    }
    +372
    +373    // `Cross-Origin-Resource-Policy`
    +374    if (config.crossOriginResources().enabled()) {
    +375      Optional<String> policyToken = tokenForCrossOriginResourcePolicy(
    +376        config.crossOriginResources().policy());
    +377      if (policyToken.isPresent()) {
    +378        if (logging.isDebugEnabled())
    +379          logging.debug(format("Applying `Cross-Origin-Resource-Policy` '%s'.", policyToken.get()));
    +380        response.getHeaders().add(
    +381          CROSS_ORIGIN_RESOURCE_POLICY_HEADER,
    +382          policyToken.get());
    +383      }
    +384    } else if (logging.isTraceEnabled()) {
    +385      logging.trace("No `Cross-Origin-Resource-Policy` applied: policy token was not present.");
    +386    }
    +387
    +388    // apply HTTP caching, if enabled
    +389    if (config.httpCaching().enabled()) {
    +390      if (logging.isDebugEnabled())
    +391        logging.debug("HTTP caching is enabled.");
    +392      resolveCacheControl(response);
    +393    } else if (logging.isDebugEnabled()) {
    +394      logging.debug("HTTP caching is disabled.");
    +395    }
    +396
    +397    // apply no-sniff policy
    +398    if (config.enableNoSniff()) {
    +399      // @TODO get this into Micronaut main
    +400      response.header("X-Content-Type-Options", "nosniff");
    +401    }
    +402  }
    +403
    +404  /** Calculate the set of supported compression modes for a given HTTP request's Accept-Encoding header. */
    +405  private EnumSet<CompressionMode> supportedCompressionModes(@Nonnull HttpRequest request) {
    +406    if (request.getHeaders().contains(HttpHeaders.ACCEPT_ENCODING)) {
    +407      @Nonnull String encodingSpec = Objects.requireNonNull(request.getHeaders().get(HttpHeaders.ACCEPT_ENCODING));
    +408      EnumSet<CompressionMode> modes = EnumSet.allOf(CompressionMode.class);
    +409      modes.remove(CompressionMode.UNRECOGNIZED);
    +410
    +411      for (CompressionMode mode : CompressionMode.values()) {
    +412        final String token;
    +413        switch (mode) {
    +414          case GZIP: token = "gzip"; break;
    +415          case BROTLI: token = "br"; break;
    +416          case IDENTITY:
    +417          case UNRECOGNIZED: continue;
    +418          default:
    +419            throw new IllegalStateException(format("Unsupported compression mode: '%s'.", mode.name()));
    +420        }
    +421
    +422        if (!encodingSpec.contains(token))
    +423          modes.remove(mode);
    +424      }
    +425      return modes;
    +426    }
    +427    // no compression support indicated at all
    +428    return EnumSet.noneOf(CompressionMode.class);
    +429  }
    +430
    +431  /** Check if the request is conditional, and, if so, if the conditions match the static asset. */
    +432  private boolean conditionalRequestMatches(@Nonnull HttpRequest request,
    +433                                            @Nonnull AssetManager.ManagedAssetContent asset) {
    +434    if (config.enableETags() && request.getHeaders().contains(HttpHeaders.IF_NONE_MATCH)) {
    +435      // the request has an etag, and etags are on. match them.
    +436      String etagValue = request.getHeaders().get(HttpHeaders.IF_NONE_MATCH);
    +437      if (logging.isTraceEnabled()) {
    +438        logging.trace(format("`ETag` value from asset: '%s'", asset.getETag()));
    +439        logging.trace(format("`ETag` value from request: '%s'", etagValue));
    +440      }
    +441
    +442      boolean match = (asset.getETag().equals(etagValue));
    +443      if (match && logging.isDebugEnabled()) {
    +444        logging.debug("`ETag` value matched.");
    +445      } else if (logging.isDebugEnabled()) {
    +446        logging.debug("`ETag` value did not match.");
    +447      }
    +448      return match;
    +449
    +450    } else if (config.enableLastModified() && request.getHeaders().contains(HttpHeaders.IF_MODIFIED_SINCE)) {
    +451      // the request has an if-modified-since, and last-modified is on. calculate a result.
    +452      try {
    +453        var ifModifiedSince = request.getHeaders().get(HttpHeaders.IF_MODIFIED_SINCE);
    +454        if (logging.isTraceEnabled())
    +455          logging.trace(format("Parsing '%s' `If-Modified-Since` value...", ifModifiedSince));
    +456        Date parsed = isoDateTimeFormat.parse(ifModifiedSince);
    +457
    +458        if (parsed != null) {
    +459          long requestSince = parsed.toInstant().getEpochSecond();
    +460          long assetTimestamp = asset.getLastModified().getSeconds();
    +461          boolean match = assetTimestamp <= requestSince;
    +462
    +463          if (logging.isDebugEnabled()) {
    +464            logging.debug(format("Parsed `If-Modified-Since` at timestamp %s.", requestSince));
    +465            logging.debug(format("Asset timestamp loaded as %s.", assetTimestamp));
    +466            if (match) logging.debug("Asset timestamp occurs before-or-at request. Match.");
    +467            else logging.debug("Asset timestamp occurs after request. No match.");
    +468          }
    +469          return match;
    +470        }
    +471      } catch (ParseException err) {
    +472        logging.error(format("Failed to parse `If-Modified-Since`: %s.", err.getMessage()));
    +473      }
    +474    }
    +475    return false;  // either nothing is on, or the request was not conditional.
    +476  }
    +477
    +478  /** Choose the optimal variant to serve, and serve it. */
    +479  private Flowable<HttpResponse> chooseAndServeVariant(@Nonnull HttpRequest request,
    +480                                                       @Nonnull MediaType type,
    +481                                                       @Nonnull AssetManager.ManagedAssetContent asset,
    +482                                                       @Nonnull MutableHttpResponse<byte[]> response) {
    +483    if (this.conditionalRequestMatches(request, asset)) {
    +484      if (logging.isDebugEnabled())
    +485        logging.debug("Conditional request matches response. Serving 304-Not-Modified.");
    +486      return Flowable.just(HttpResponse.notModified());
    +487    } else if (logging.isDebugEnabled()) {
    +488      if (!config.enableETags() && !config.enableLastModified()) logging.debug(
    +489        "Conditional requests were not considered because `ETag`s and `Last-Modified` are disabled.");
    +490      else
    +491        logging.debug("Request was not conditional, or did not match.");
    +492    }
    +493
    +494    // based on the request, calculate supported compression modes
    +495    EnumSet<CompressionMode> supportedCompressions = supportedCompressionModes(request);
    +496    EnumSet<CompressionMode> supportedVariants = asset.getCompressionOptions();
    +497    ImmutableSet<CompressionMode> compressionOptions = Sets.immutableEnumSet(
    +498      Sets.intersection(
    +499        Sets.intersection(supportedCompressions, supportedVariants),
    +500        config.compression().compressionModes()));
    +501
    +502    if (logging.isTraceEnabled()) {
    +503      // describe the conditions that led to our compression decisions
    +504      logging.trace(format("Client indicated compression support: '%s'.", Joiner.on(", ").join(
    +505        supportedCompressions.stream().map(Enum::name).collect(Collectors.toSet()))));
    +506      logging.trace(format("Asset variant options: '%s'.", Joiner.on(", ").join(
    +507        supportedVariants.stream().map(Enum::name).collect(Collectors.toSet()))));
    +508    }
    +509
    +510    final Optional<CompressedData> resolvedData;
    +511
    +512    if (/* if we can't use compression... */ supportedCompressions.size() == 0 ||
    +513        /* or compression is disabled entirely... */ (!config.compression().enabled()) ||
    +514        /* or no compression algorithms are enabled... */ (config.compression().compressionModes().isEmpty()) ||
    +515        /* or the optimal compression is none... */ (asset.getOptimalCompression() == CompressionMode.IDENTITY &&
    +516        /* and we aren't forcing compression where available... */ !FORCE_COMPRESSION_IF_SUPPORTED) ||
    +517        /* or the variant has no compressed options... */ asset.getVariantCount() < 2) {
    +518      if (logging.isDebugEnabled()) {
    +519        // explain why we got here
    +520        if (supportedVariants.size() == 0) logging.debug("Compression disabled: unsupported by asset.");
    +521        else if (supportedCompressions.size() == 0) logging.debug("Compression disabled: unsupported by client.");
    +522        else if (compressionOptions.size() == 0) logging.debug("Compression disabled: no agreement with client.");
    +523        else if (asset.getVariantCount() < 2) logging.debug("Compression disabled: no variance for asset.");
    +524        else if (asset.getOptimalCompression() == CompressionMode.IDENTITY)
    +525          logging.debug("Compression disabled: un-compressed is optimal.");
    +526      }
    +527
    +528      // no ability to serve a compressed response.
    +529      resolvedData = asset.getContent().getVariantList()
    +530        .stream()
    +531        .filter((bundle) -> bundle.getCompression().equals(CompressionMode.IDENTITY))
    +532        .findFirst();
    +533    } else {
    +534      // if the optimal compression choice is supported by the client, choose it
    +535      var optimal = asset.getOptimalCompression();
    +536      if ((!FORCE_COMPRESSION_IF_SUPPORTED || !optimal.equals(CompressionMode.IDENTITY))
    +537          && compressionOptions.contains(optimal)) {
    +538        if (logging.isDebugEnabled())
    +539          logging.debug(format("Optimal compression (%s) is supported and was selected.",
    +540            optimal.name()));
    +541
    +542        resolvedData = asset.getContent().getVariantList()
    +543          .stream()
    +544          .filter((bundle) -> bundle.getCompression().equals(optimal))
    +545          .findFirst();
    +546      } else {
    +547        // otherwise, pick the next-optimal-choice supported by the client
    +548        resolvedData = compressionOptions.stream()
    +549          .flatMap((mode) -> asset.getContent().getVariantList().stream()
    +550            .filter((variant) -> !variant.getCompression().equals(CompressionMode.IDENTITY))
    +551            .filter((variant) -> compressionOptions.contains(variant.getCompression())))
    +552          .peek((variant) -> {
    +553            if (logging.isDebugEnabled()) {
    +554              logging.debug(format("Considering variant of size %sb (%s)...",
    +555                variant.getSize(),
    +556                variant.getCompression().name()));
    +557            }
    +558          })
    +559          .min(Comparator.comparing(CompressedData::getSize));
    +560
    +561        if (resolvedData.isPresent() && logging.isDebugEnabled()) {
    +562          logging.debug(format("Selected variant %s, of size %s bytes.",
    +563            resolvedData.get().getCompression().name(),
    +564            resolvedData.get().getSize()));
    +565        }
    +566      }
    +567    }
    +568
    +569    if (!resolvedData.isPresent()) {
    +570      // we were unable to agree on a content variant. this essentially should not happen.
    +571      logging.warn("Failed to agree with client on a variant for asset. Failing.");
    +572      return Flowable.just(HttpResponse.badRequest("UNSUPPORTED_ENCODING"));
    +573    } else {
    +574      // we've resolved the correct data for the body. calculate headers and send it.
    +575      this.affixHeaders(
    +576        type,
    +577        resolvedData.get().getCompression(),
    +578        asset.getLastModified(),
    +579        asset,
    +580        response);
    +581
    +582      byte[] assetPayload = resolvedData
    +583        .get()
    +584        .getData()
    +585        .getRaw()
    +586        .toByteArray();
    +587
    +588      if (logging.isDebugEnabled()) {
    +589        logging.debug(format("Serving payload of size (%s bytes) for asset '%s'.",
    +590          assetPayload.length,
    +591          asset.getToken()));
    +592      }
    +593      return Flowable.just(response.body(assetPayload));
    +594    }
    +595  }
    +596
    +597  /**
    +598   * Main GET serving endpoint for dynamic managed assets. Assets come through this endpoint mentioning their unique
    +599   * token and file extension, and we do the rest.
    +600   *
    +601   * <p>We accomplish this by (1) querying the {@link AssetManager} for a matching asset (404 is yielded if a match
    +602   * cannot be located), then (2) calculating headers and an appropriate variant for the subject asset, and finally (3)
    +603   * writing the headers and the asset body to the response.</p>
    +604   *
    +605   * @return Response
    +606   */
    +607  @Validated
    +608  @Get(value = "/{asset}.{ext}", produces = {
    +609    "text/html",
    +610    "text/css",
    +611    "application/javascript"
    +612  })
    +613  public @Nonnull Flowable<HttpResponse> asset(@Nonnull String asset,
    +614                                               @Nonnull String ext,
    +615                                               @Nonnull HttpRequest request) {
    +616    //noinspection ConstantConditions
    +617    if (asset == null || ext == null
    +618        || asset.isEmpty() || ext.isEmpty()
    +619        || asset.length() < 2 || ext.length() < 2) {
    +620      logging.warn("Invalid asset token or extension. Returning 400.");
    +621      return Flowable.just(HttpResponse.badRequest());
    +622    }
    +623
    +624    // okay, resolve content through the cache, backed by the asset manager
    +625    @Nonnull Optional<AssetManager.ManagedAssetContent> assetContent = this.assetManager.assetDataByToken(asset);
    +626    if (assetContent.isPresent() && assetContent.get().getVariantCount() > 0) {
    +627      // make sure the user is asking for this kind of content
    +628      var content = assetContent.get();
    +629      String[] ogFilename = content.getFilename().split("\\.");
    +630      if (ogFilename.length > 1 && ogFilename[ogFilename.length - 1].equals(ext)) {
    +631        // create response, affix other asset headers, and serve
    +632        return this.chooseAndServeVariant(
    +633          request,
    +634          resolveMediaType(ext),
    +635          content,
    +636          HttpResponse.ok());
    +637      } else {
    +638        logging.warn(format("Asset extension mismatch: '%s' does not match expected value for asset '%s' ('%s').",
    +639          ext,
    +640          asset,
    +641          ogFilename[ogFilename.length - 1]));
    +642      }
    +643    }
    +644    logging.warn(format("Asset '%s' not found.", asset));
    +645    return Flowable.just(HttpResponse.notFound());
    +646  }
    +647}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/AssetController.html b/docs/java/src-html/gust/backend/AssetController.html new file mode 100644 index 000000000..ca3a21b49 --- /dev/null +++ b/docs/java/src-html/gust/backend/AssetController.html @@ -0,0 +1,721 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015
    +016import com.google.common.annotations.VisibleForTesting;
    +017import com.google.common.base.Joiner;
    +018import com.google.common.collect.ImmutableSet;
    +019import com.google.common.collect.Sets;
    +020import com.google.protobuf.Timestamp;
    +021import gust.Core;
    +022import gust.backend.runtime.AssetManager;
    +023import gust.backend.runtime.Logging;
    +024import io.micronaut.context.annotation.Replaces;
    +025import io.micronaut.core.util.StringUtils;
    +026import io.micronaut.http.*;
    +027import io.micronaut.http.annotation.Controller;
    +028import io.micronaut.http.annotation.Get;
    +029import io.micronaut.http.filter.ServerFilterChain;
    +030import io.micronaut.security.annotation.Secured;
    +031import io.micronaut.validation.Validated;
    +032import io.micronaut.views.csp.CspConfiguration;
    +033import io.micronaut.views.csp.CspFilter;
    +034import io.reactivex.Flowable;
    +035import org.reactivestreams.Publisher;
    +036import org.slf4j.Logger;
    +037import tools.elide.core.data.CompressedData;
    +038import tools.elide.core.data.CompressionMode;
    +039import tools.elide.page.Context.CrossOriginResourcePolicy;
    +040
    +041import javax.annotation.Nonnull;
    +042import javax.annotation.Nullable;
    +043import javax.inject.Inject;
    +044
    +045import java.nio.charset.StandardCharsets;
    +046import java.text.ParseException;
    +047import java.text.SimpleDateFormat;
    +048import java.time.Instant;
    +049import java.util.*;
    +050import java.util.concurrent.TimeUnit;
    +051import java.util.stream.Collectors;
    +052
    +053import static java.lang.String.format;
    +054
    +055
    +056/**
    +057 * Built-in backend controller for serving dynamic managed assets. Assets managed in this manner are hooked into the
    +058 * build pipeline, so that a binary asset manifest is produced at the root of the application JAR.
    +059 *
    +060 * <p>{@link AssetManager} loads the binary manifest/bundle, and then using this controller, we serve the URLs and
    +061 * assets mentioned therein. Controllers may then reference these assets (to be served here), via utility methods on
    +062 * {@link BaseController}.</p>
    +063 */
    +064@Controller("/_/assets")
    +065@Secured("isAnonymous()")
    +066public class AssetController {
    +067  /** Private logging pipe. */
    +068  private static final @Nonnull Logger logging = Logging.logger(AssetController.class);
    +069
    +070  /** Testing flag, indicating that compression should be forced, regardless of whether it saves time/space. */
    +071  private static final boolean FORCE_COMPRESSION_IF_SUPPORTED = false;
    +072
    +073  /** Static {@code Content-Encoding} value for non-compressed content. */
    +074  private static final @Nonnull String IDENTITY_CONTENT_ENCODING = "identity";
    +075
    +076  /** Static {@code Content-Encoding} value for gzip-compressed content. */
    +077  private static final @Nonnull String GZIP_CONTENT_ENCODING = "gzip";
    +078
    +079  /** Static {@code Content-Encoding} value for Brotli-compressed content. */
    +080  private static final @Nonnull String BROTLI_CONTENT_ENCODING = "br";
    +081
    +082  /** Static {@code Cross-Origin-Resource-Policy} header name. */
    +083  private static final @Nonnull String CROSS_ORIGIN_RESOURCE_POLICY_HEADER = "Cross-Origin-Resource-Policy";
    +084
    +085  /** Date/time tool for HTTP-formatted timestamps. */
    +086  private static final @Nonnull SimpleDateFormat isoDateTimeFormat = (
    +087    new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz"));
    +088
    +089  /** Main asset manager singleton. */
    +090  private final @Nonnull AssetManager assetManager;
    +091
    +092  /** Configuration for the asset engine. */
    +093  private final @Nonnull AssetConfiguration config;
    +094
    +095  /** Replacement for {@link CspFilter} which disables itself when assets (or non-HTML content) are served. */
    +096  @Replaces(CspFilter.class)
    +097  public static final class AssetsAwareCspFilter extends CspFilter {
    +098    AssetsAwareCspFilter(Optional<CspConfiguration> cspConfiguration) {
    +099      super(cspConfiguration.isPresent() ? cspConfiguration.get() :
    +100        new CspConfiguration() {
    +101          @Override
    +102          public boolean isEnabled() { return false; }
    +103
    +104          @Override
    +105          public Optional<String> getPolicyDirectives() { return Optional.empty(); }
    +106        } /** pass in defaults */);
    +107    }
    +108
    +109    private static boolean nonAssetRequest(@Nonnull HttpRequest request) {
    +110      return !(request.getPath().startsWith(Core.getDynamicAssetPrefix()));
    +111    }
    +112
    +113    private @Nullable String nonceValue(@Nonnull HttpRequest<?> request) {
    +114      return (this.cspConfiguration.isNonceEnabled() && nonAssetRequest(request)) ?
    +115        this.cspConfiguration.generateNonce() : null;
    +116    }
    +117
    +118    @Override
    +119    public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
    +120      String nonce = this.nonceValue(request);
    +121      if (logging.isDebugEnabled())
    +122        logging.debug(format("Generated nonce: '%s'.", nonce));
    +123      return Flowable.fromPublisher(
    +124        chain.proceed(request.setAttribute("cspNonce", nonce))).doOnNext((response) -> {
    +125        if (nonAssetRequest(request)
    +126            && request.getContentType().orElse(MediaType.TEXT_HTML_TYPE).equals(MediaType.TEXT_HTML_TYPE)) {
    +127          if (logging.isDebugEnabled())
    +128            logging.debug("Encountered a non-asset request. Applying CSP.");
    +129
    +130          // it's not an asset request. apply CSP.
    +131          this.cspConfiguration.getPolicyDirectives().map(StringUtils::trimToNull).ifPresent((directives) -> {
    +132            String header = this.cspConfiguration.isReportOnly() ?
    +133              "Content-Security-Policy-Report-Only" : "Content-Security-Policy";
    +134            String headerValue;
    +135            if (directives.contains("{#nonceValue}")) {
    +136              if (nonce == null) {
    +137                throw new IllegalArgumentException(
    +138                  "Must enable CSP nonce generation to use '{#nonceValue}' placeholder.");
    +139              }
    +140
    +141              headerValue = directives.replace("{#nonceValue}", nonce);
    +142            } else {
    +143              headerValue = directives;
    +144            }
    +145            response.getHeaders().add(header, headerValue);
    +146          });
    +147        } else {
    +148          // it's an asset request. let it through without appending CSP.
    +149          if (logging.isDebugEnabled())
    +150            logging.debug("CSP policy skipped because this request is being served as an asset.");
    +151        }
    +152      });
    +153    }
    +154  }
    +155
    +156  /** Construct a new asset controller from scratch. Usually invoked from DI. */
    +157  @Inject AssetController(@Nonnull AssetConfiguration config,
    +158                          @Nonnull AssetManager assetManager) {
    +159    this.config = config;
    +160    this.assetManager = assetManager;
    +161  }
    +162
    +163  /**
    +164   * Produce a token for the specified {@code Cross-Origin-Resource-Policy}.
    +165   *
    +166   * @param policy Policy to produce a token for.
    +167   * @return String token.
    +168   */
    +169  @VisibleForTesting
    +170  @SuppressWarnings("WeakerAccess")
    +171  static @Nonnull Optional<String> tokenForCrossOriginResourcePolicy(@Nonnull CrossOriginResourcePolicy policy) {
    +172    switch (policy) {
    +173      case SAME_SITE: return Optional.of("same-site");
    +174      case SAME_ORIGIN: return Optional.of("same-origin");
    +175      case CROSS_ORIGIN: return Optional.of("cross-origin");
    +176      default: return Optional.empty();
    +177    }
    +178  }
    +179
    +180  /** Resolve a {@code Content-Encoding} value for the supplied {@code compressionMode}. */
    +181  private @Nonnull CharSequence resolveContentEncoding(@Nonnull CompressionMode compressionMode) {
    +182    switch (compressionMode) {
    +183      case IDENTITY: return IDENTITY_CONTENT_ENCODING;
    +184      case GZIP: return GZIP_CONTENT_ENCODING;
    +185      case BROTLI: return BROTLI_CONTENT_ENCODING;
    +186      default:
    +187        throw new IllegalStateException(format("Unrecognized compression mode: '%s'.", compressionMode.name()));
    +188    }
    +189  }
    +190
    +191  /** Resolve a {@code Content-Type} value for the supplied {@code ext}. */
    +192  private @Nonnull MediaType resolveMediaType(@Nonnull String ext) {
    +193    final MediaType mediaType;
    +194    Optional<MediaType> mediaTypeResolved = MediaType.forExtension(ext);
    +195    if (!mediaTypeResolved.isPresent()) {
    +196      logging.error(format("Failed to resolve MIME type for asset extension '%s'", ext));
    +197      mediaType = MediaType.APPLICATION_OCTET_STREAM_TYPE;
    +198    } else {
    +199      String charsetToken = StandardCharsets.UTF_8.displayName();
    +200      mediaType = new MediaType(
    +201        mediaTypeResolved.get().getName(),
    +202        mediaTypeResolved.get().getExtension(),
    +203        Collections.singletonMap("charset", charsetToken));
    +204      if (logging.isTraceEnabled())
    +205        logging.trace(format("Resolved MIME type for asset as '%s' (extension '%s', charset '%s').",
    +206          mediaType.getType(),
    +207          ext,
    +208          charsetToken));
    +209    }
    +210    return mediaType;
    +211  }
    +212
    +213  /** Resolve an HTTP-compliant timestamp value from a Protobuf timestamp, for the {@code Last-Modified} header. */
    +214  private @Nonnull Optional<String> resolveLastModified(@Nonnull Timestamp timestamp) {
    +215    //noinspection ConstantConditions
    +216    if (timestamp == null || !timestamp.isInitialized() || timestamp.getSeconds() < 100) {
    +217      return Optional.empty();
    +218    }
    +219    return Optional.of(
    +220      isoDateTimeFormat.format(Date.from(Instant.ofEpochSecond(timestamp.getSeconds()))));
    +221  }
    +222
    +223  /** Setup HTTP cache control settings. */
    +224  private void resolveCacheControl(@Nonnull MutableHttpResponse response) {
    +225    if (response.getHeaders().contains("Set-Cookie")) {
    +226      logging.warn("Refusing to enable HTTP cache control on response: noticed `Set-Cookie` header.");
    +227      return;
    +228    }
    +229
    +230    // resolve main TTL
    +231    var mode = config.httpCaching().mode();
    +232    var ttl = config.httpCaching().ttl();
    +233    var ttlUnit = config.httpCaching().ttlUnit();
    +234    var ttlValue = ttlUnit.convert(ttl, TimeUnit.SECONDS);
    +235
    +236    StringBuilder spec = new StringBuilder();
    +237    if (mode != null && !mode.isEmpty() && ttlValue > -1L) {
    +238      if (logging.isDebugEnabled())
    +239        logging.debug(format("Indicating `Cache-Control` mode and TTL: %s, max-age=%s.",
    +240          mode,
    +241          String.valueOf(ttl)));
    +242
    +243      spec.append(format("%s; max-age=%s",
    +244        mode,
    +245        String.valueOf(ttl)));
    +246
    +247      if (config.httpCaching().enableShared()) {
    +248        // affix the shared-cache settings, too, if so directed
    +249        var sharedTtl = config.httpCaching().sharedTtl();
    +250        var sharedTtlUnit = config.httpCaching().sharedTtlUnit();
    +251        var sharedTtlValue = sharedTtlUnit.convert(sharedTtl, TimeUnit.SECONDS);
    +252        if (sharedTtlValue > -1L) {
    +253          if (logging.isDebugEnabled())
    +254            logging.debug(format("Indicating `Cache-Control` shared TTL: s-max-age=%s.",
    +255              String.valueOf(sharedTtlValue)));
    +256
    +257          spec.append(format(" s-max-age=%s%s",
    +258            String.valueOf(sharedTtlValue),
    +259            config.httpCaching().additionalDirectives().isPresent() ?
    +260              " " + Joiner.on(" ").join(config.httpCaching().additionalDirectives().get()) :
    +261              ""));
    +262
    +263          if (logging.isDebugEnabled() && config.httpCaching().additionalDirectives().isPresent()) {
    +264            logging.debug(format("Indicating `Cache-Control` additional directives: %s.",
    +265              Joiner.on(" ").join(config.httpCaching().additionalDirectives().get())));
    +266          }
    +267        } else {
    +268          logging.warn("`Cache-Control` shared caching was enabled, but a TTL value was invalid or missing.");
    +269        }
    +270      } else if (logging.isDebugEnabled()) {
    +271        logging.debug("Shared HTTP caching is disabled.");
    +272      }
    +273    } else {
    +274      logging.warn("`Cache-Control` value was invalid or missing.");
    +275    }
    +276    var renderedSpec = spec.toString();
    +277    if (!renderedSpec.isEmpty()) {
    +278      response.header(HttpHeaders.CACHE_CONTROL, renderedSpec);
    +279    }
    +280  }
    +281
    +282  /** Affix headers for the asset to the response. */
    +283  private void affixHeaders(@Nonnull MediaType type,
    +284                            @Nonnull CompressionMode compression,
    +285                            @Nonnull Timestamp modified,
    +286                            @Nonnull AssetManager.ManagedAssetContent asset,
    +287                            @Nonnull MutableHttpResponse response) {
    +288    // start with content type and encodings
    +289    response.contentType(type);
    +290    var contentEncoding = resolveContentEncoding(compression);
    +291    response.contentEncoding(contentEncoding);
    +292    if (logging.isTraceEnabled()) {
    +293      logging.trace(format("Indicating `Content-Encoding`: '%s'.", contentEncoding));
    +294    }
    +295
    +296    // next up is `Vary`
    +297    if (config.variance().enabled()) {
    +298      Set<String> segments = new TreeSet<>();
    +299
    +300      // if the asset is a CSS or JS bundle, we must examine configuration to decide whether to specify `Accept` as a
    +301      // `Vary` header entry, which is generally done to enable differential serving of photos (as WebP, for instance).
    +302      if (type.getName().equals("css") || type.getName().equals("javascript") && config.variance().accept())
    +303        segments.add(HttpHeaders.ACCEPT);
    +304      else if (logging.isDebugEnabled())
    +305        logging.debug(format("Asset type '%s' did not qualify for `Accept` variance.", type));
    +306
    +307      // if the application is internationalized, we can indicate asset variance based on the provided request value of
    +308      // the `Accept-Language` header, so apply that if it is configured.
    +309      if (config.variance().language()) segments.add(HttpHeaders.ACCEPT_LANGUAGE);
    +310      else if (logging.isDebugEnabled()) logging.debug("Asset variance by language is disabled in asset config.");
    +311
    +312      // if the application is internationalized to the point of using custom/special charsets, we can indicate asset
    +313      // variance based on the browser's accepted character sets. this is generally a bad idea, but is needed for some
    +314      // corner cases, so we apply it here.
    +315      if (config.variance().charset()) segments.add(HttpHeaders.ACCEPT_CHARSET);
    +316      else if (logging.isDebugEnabled()) logging.debug("Asset variance by charset is disabled in asset config.");
    +317
    +318      // if the application hosts dynamically originated content (i.e. in a multi-tenant posture), config can opt-in to
    +319      // varying by browser origin.
    +320      if (config.variance().origin()) segments.add(HttpHeaders.ORIGIN);
    +321      else if (logging.isDebugEnabled()) logging.debug("Asset variance by variance is disabled in asset config.");
    +322
    +323      // if we have more than one representation of the asset, and compression is enabled, and `Vary` is not disabled,
    +324      // we must affix the `Vary` tag. generally for CSS and JS this might include `Accept`, so we look for that in the
    +325      // asset configuration, too.
    +326      if (config.compression().enabled() && config.compression().enableVary()
    +327          && asset.getContent().getVariantList().size() > 1)
    +328        segments.add(HttpHeaders.ACCEPT_ENCODING);
    +329      else if (logging.isDebugEnabled())
    +330        logging.debug("No need for compression variance: too few representations, or disabled by config.");
    +331
    +332      if (!segments.isEmpty()) {
    +333        String varyHeader = Joiner.on(", ").join(segments);
    +334        if (logging.isDebugEnabled())
    +335          logging.debug(format("Calculated `Vary` header value: '%s'.", varyHeader));
    +336        response.header(HttpHeaders.VARY, varyHeader);
    +337
    +338      } else if (logging.isDebugEnabled()) {
    +339        logging.debug("No segments to apply to the `Vary` header value. Skipping.");
    +340      }
    +341    }
    +342
    +343    // next up is etags and last-modified
    +344    if (config.enableETags()) {
    +345      if (logging.isDebugEnabled())
    +346        logging.debug(format("Indicating `ETag`: '%s'.", asset.getETag()));
    +347      response.header(HttpHeaders.ETAG, "\"" + asset.getETag() + "\"");
    +348    } else if (logging.isDebugEnabled()) {
    +349      logging.debug("`ETag`s are disabled.");
    +350    }
    +351
    +352    if (config.enableLastModified()) {
    +353      Optional<String> lastModifiedValue = resolveLastModified(modified);
    +354      if (lastModifiedValue.isPresent()) {
    +355        String lastModified = lastModifiedValue.get();
    +356        response.header(HttpHeaders.LAST_MODIFIED, lastModified);
    +357        if (logging.isDebugEnabled())
    +358          logging.debug(format("Indicating `Last-Modified`: '%s'.", lastModified));
    +359      } else {
    +360        logging.warn(format("`Last-Modified` headers are enabled, but none could be generated for asset '%s'.",
    +361          asset.getToken()));
    +362      }
    +363    } else if (logging.isDebugEnabled()) {
    +364      logging.debug("`Last-Modified` is disabled.");
    +365    }
    +366
    +367    // mention original filename if operating in debug mode
    +368    if (Core.isDebugMode()) {
    +369      logging.debug(format("Serving managed asset '%s'.", asset.getFilename()));
    +370      response.header(HttpHeaders.CONTENT_DISPOSITION, format("inline; filename=\"%s\"", asset.getFilename()));
    +371    }
    +372
    +373    // `Cross-Origin-Resource-Policy`
    +374    if (config.crossOriginResources().enabled()) {
    +375      Optional<String> policyToken = tokenForCrossOriginResourcePolicy(
    +376        config.crossOriginResources().policy());
    +377      if (policyToken.isPresent()) {
    +378        if (logging.isDebugEnabled())
    +379          logging.debug(format("Applying `Cross-Origin-Resource-Policy` '%s'.", policyToken.get()));
    +380        response.getHeaders().add(
    +381          CROSS_ORIGIN_RESOURCE_POLICY_HEADER,
    +382          policyToken.get());
    +383      }
    +384    } else if (logging.isTraceEnabled()) {
    +385      logging.trace("No `Cross-Origin-Resource-Policy` applied: policy token was not present.");
    +386    }
    +387
    +388    // apply HTTP caching, if enabled
    +389    if (config.httpCaching().enabled()) {
    +390      if (logging.isDebugEnabled())
    +391        logging.debug("HTTP caching is enabled.");
    +392      resolveCacheControl(response);
    +393    } else if (logging.isDebugEnabled()) {
    +394      logging.debug("HTTP caching is disabled.");
    +395    }
    +396
    +397    // apply no-sniff policy
    +398    if (config.enableNoSniff()) {
    +399      // @TODO get this into Micronaut main
    +400      response.header("X-Content-Type-Options", "nosniff");
    +401    }
    +402  }
    +403
    +404  /** Calculate the set of supported compression modes for a given HTTP request's Accept-Encoding header. */
    +405  private EnumSet<CompressionMode> supportedCompressionModes(@Nonnull HttpRequest request) {
    +406    if (request.getHeaders().contains(HttpHeaders.ACCEPT_ENCODING)) {
    +407      @Nonnull String encodingSpec = Objects.requireNonNull(request.getHeaders().get(HttpHeaders.ACCEPT_ENCODING));
    +408      EnumSet<CompressionMode> modes = EnumSet.allOf(CompressionMode.class);
    +409      modes.remove(CompressionMode.UNRECOGNIZED);
    +410
    +411      for (CompressionMode mode : CompressionMode.values()) {
    +412        final String token;
    +413        switch (mode) {
    +414          case GZIP: token = "gzip"; break;
    +415          case BROTLI: token = "br"; break;
    +416          case IDENTITY:
    +417          case UNRECOGNIZED: continue;
    +418          default:
    +419            throw new IllegalStateException(format("Unsupported compression mode: '%s'.", mode.name()));
    +420        }
    +421
    +422        if (!encodingSpec.contains(token))
    +423          modes.remove(mode);
    +424      }
    +425      return modes;
    +426    }
    +427    // no compression support indicated at all
    +428    return EnumSet.noneOf(CompressionMode.class);
    +429  }
    +430
    +431  /** Check if the request is conditional, and, if so, if the conditions match the static asset. */
    +432  private boolean conditionalRequestMatches(@Nonnull HttpRequest request,
    +433                                            @Nonnull AssetManager.ManagedAssetContent asset) {
    +434    if (config.enableETags() && request.getHeaders().contains(HttpHeaders.IF_NONE_MATCH)) {
    +435      // the request has an etag, and etags are on. match them.
    +436      String etagValue = request.getHeaders().get(HttpHeaders.IF_NONE_MATCH);
    +437      if (logging.isTraceEnabled()) {
    +438        logging.trace(format("`ETag` value from asset: '%s'", asset.getETag()));
    +439        logging.trace(format("`ETag` value from request: '%s'", etagValue));
    +440      }
    +441
    +442      boolean match = (asset.getETag().equals(etagValue));
    +443      if (match && logging.isDebugEnabled()) {
    +444        logging.debug("`ETag` value matched.");
    +445      } else if (logging.isDebugEnabled()) {
    +446        logging.debug("`ETag` value did not match.");
    +447      }
    +448      return match;
    +449
    +450    } else if (config.enableLastModified() && request.getHeaders().contains(HttpHeaders.IF_MODIFIED_SINCE)) {
    +451      // the request has an if-modified-since, and last-modified is on. calculate a result.
    +452      try {
    +453        var ifModifiedSince = request.getHeaders().get(HttpHeaders.IF_MODIFIED_SINCE);
    +454        if (logging.isTraceEnabled())
    +455          logging.trace(format("Parsing '%s' `If-Modified-Since` value...", ifModifiedSince));
    +456        Date parsed = isoDateTimeFormat.parse(ifModifiedSince);
    +457
    +458        if (parsed != null) {
    +459          long requestSince = parsed.toInstant().getEpochSecond();
    +460          long assetTimestamp = asset.getLastModified().getSeconds();
    +461          boolean match = assetTimestamp <= requestSince;
    +462
    +463          if (logging.isDebugEnabled()) {
    +464            logging.debug(format("Parsed `If-Modified-Since` at timestamp %s.", requestSince));
    +465            logging.debug(format("Asset timestamp loaded as %s.", assetTimestamp));
    +466            if (match) logging.debug("Asset timestamp occurs before-or-at request. Match.");
    +467            else logging.debug("Asset timestamp occurs after request. No match.");
    +468          }
    +469          return match;
    +470        }
    +471      } catch (ParseException err) {
    +472        logging.error(format("Failed to parse `If-Modified-Since`: %s.", err.getMessage()));
    +473      }
    +474    }
    +475    return false;  // either nothing is on, or the request was not conditional.
    +476  }
    +477
    +478  /** Choose the optimal variant to serve, and serve it. */
    +479  private Flowable<HttpResponse> chooseAndServeVariant(@Nonnull HttpRequest request,
    +480                                                       @Nonnull MediaType type,
    +481                                                       @Nonnull AssetManager.ManagedAssetContent asset,
    +482                                                       @Nonnull MutableHttpResponse<byte[]> response) {
    +483    if (this.conditionalRequestMatches(request, asset)) {
    +484      if (logging.isDebugEnabled())
    +485        logging.debug("Conditional request matches response. Serving 304-Not-Modified.");
    +486      return Flowable.just(HttpResponse.notModified());
    +487    } else if (logging.isDebugEnabled()) {
    +488      if (!config.enableETags() && !config.enableLastModified()) logging.debug(
    +489        "Conditional requests were not considered because `ETag`s and `Last-Modified` are disabled.");
    +490      else
    +491        logging.debug("Request was not conditional, or did not match.");
    +492    }
    +493
    +494    // based on the request, calculate supported compression modes
    +495    EnumSet<CompressionMode> supportedCompressions = supportedCompressionModes(request);
    +496    EnumSet<CompressionMode> supportedVariants = asset.getCompressionOptions();
    +497    ImmutableSet<CompressionMode> compressionOptions = Sets.immutableEnumSet(
    +498      Sets.intersection(
    +499        Sets.intersection(supportedCompressions, supportedVariants),
    +500        config.compression().compressionModes()));
    +501
    +502    if (logging.isTraceEnabled()) {
    +503      // describe the conditions that led to our compression decisions
    +504      logging.trace(format("Client indicated compression support: '%s'.", Joiner.on(", ").join(
    +505        supportedCompressions.stream().map(Enum::name).collect(Collectors.toSet()))));
    +506      logging.trace(format("Asset variant options: '%s'.", Joiner.on(", ").join(
    +507        supportedVariants.stream().map(Enum::name).collect(Collectors.toSet()))));
    +508    }
    +509
    +510    final Optional<CompressedData> resolvedData;
    +511
    +512    if (/* if we can't use compression... */ supportedCompressions.size() == 0 ||
    +513        /* or compression is disabled entirely... */ (!config.compression().enabled()) ||
    +514        /* or no compression algorithms are enabled... */ (config.compression().compressionModes().isEmpty()) ||
    +515        /* or the optimal compression is none... */ (asset.getOptimalCompression() == CompressionMode.IDENTITY &&
    +516        /* and we aren't forcing compression where available... */ !FORCE_COMPRESSION_IF_SUPPORTED) ||
    +517        /* or the variant has no compressed options... */ asset.getVariantCount() < 2) {
    +518      if (logging.isDebugEnabled()) {
    +519        // explain why we got here
    +520        if (supportedVariants.size() == 0) logging.debug("Compression disabled: unsupported by asset.");
    +521        else if (supportedCompressions.size() == 0) logging.debug("Compression disabled: unsupported by client.");
    +522        else if (compressionOptions.size() == 0) logging.debug("Compression disabled: no agreement with client.");
    +523        else if (asset.getVariantCount() < 2) logging.debug("Compression disabled: no variance for asset.");
    +524        else if (asset.getOptimalCompression() == CompressionMode.IDENTITY)
    +525          logging.debug("Compression disabled: un-compressed is optimal.");
    +526      }
    +527
    +528      // no ability to serve a compressed response.
    +529      resolvedData = asset.getContent().getVariantList()
    +530        .stream()
    +531        .filter((bundle) -> bundle.getCompression().equals(CompressionMode.IDENTITY))
    +532        .findFirst();
    +533    } else {
    +534      // if the optimal compression choice is supported by the client, choose it
    +535      var optimal = asset.getOptimalCompression();
    +536      if ((!FORCE_COMPRESSION_IF_SUPPORTED || !optimal.equals(CompressionMode.IDENTITY))
    +537          && compressionOptions.contains(optimal)) {
    +538        if (logging.isDebugEnabled())
    +539          logging.debug(format("Optimal compression (%s) is supported and was selected.",
    +540            optimal.name()));
    +541
    +542        resolvedData = asset.getContent().getVariantList()
    +543          .stream()
    +544          .filter((bundle) -> bundle.getCompression().equals(optimal))
    +545          .findFirst();
    +546      } else {
    +547        // otherwise, pick the next-optimal-choice supported by the client
    +548        resolvedData = compressionOptions.stream()
    +549          .flatMap((mode) -> asset.getContent().getVariantList().stream()
    +550            .filter((variant) -> !variant.getCompression().equals(CompressionMode.IDENTITY))
    +551            .filter((variant) -> compressionOptions.contains(variant.getCompression())))
    +552          .peek((variant) -> {
    +553            if (logging.isDebugEnabled()) {
    +554              logging.debug(format("Considering variant of size %sb (%s)...",
    +555                variant.getSize(),
    +556                variant.getCompression().name()));
    +557            }
    +558          })
    +559          .min(Comparator.comparing(CompressedData::getSize));
    +560
    +561        if (resolvedData.isPresent() && logging.isDebugEnabled()) {
    +562          logging.debug(format("Selected variant %s, of size %s bytes.",
    +563            resolvedData.get().getCompression().name(),
    +564            resolvedData.get().getSize()));
    +565        }
    +566      }
    +567    }
    +568
    +569    if (!resolvedData.isPresent()) {
    +570      // we were unable to agree on a content variant. this essentially should not happen.
    +571      logging.warn("Failed to agree with client on a variant for asset. Failing.");
    +572      return Flowable.just(HttpResponse.badRequest("UNSUPPORTED_ENCODING"));
    +573    } else {
    +574      // we've resolved the correct data for the body. calculate headers and send it.
    +575      this.affixHeaders(
    +576        type,
    +577        resolvedData.get().getCompression(),
    +578        asset.getLastModified(),
    +579        asset,
    +580        response);
    +581
    +582      byte[] assetPayload = resolvedData
    +583        .get()
    +584        .getData()
    +585        .getRaw()
    +586        .toByteArray();
    +587
    +588      if (logging.isDebugEnabled()) {
    +589        logging.debug(format("Serving payload of size (%s bytes) for asset '%s'.",
    +590          assetPayload.length,
    +591          asset.getToken()));
    +592      }
    +593      return Flowable.just(response.body(assetPayload));
    +594    }
    +595  }
    +596
    +597  /**
    +598   * Main GET serving endpoint for dynamic managed assets. Assets come through this endpoint mentioning their unique
    +599   * token and file extension, and we do the rest.
    +600   *
    +601   * <p>We accomplish this by (1) querying the {@link AssetManager} for a matching asset (404 is yielded if a match
    +602   * cannot be located), then (2) calculating headers and an appropriate variant for the subject asset, and finally (3)
    +603   * writing the headers and the asset body to the response.</p>
    +604   *
    +605   * @return Response
    +606   */
    +607  @Validated
    +608  @Get(value = "/{asset}.{ext}", produces = {
    +609    "text/html",
    +610    "text/css",
    +611    "application/javascript"
    +612  })
    +613  public @Nonnull Flowable<HttpResponse> asset(@Nonnull String asset,
    +614                                               @Nonnull String ext,
    +615                                               @Nonnull HttpRequest request) {
    +616    //noinspection ConstantConditions
    +617    if (asset == null || ext == null
    +618        || asset.isEmpty() || ext.isEmpty()
    +619        || asset.length() < 2 || ext.length() < 2) {
    +620      logging.warn("Invalid asset token or extension. Returning 400.");
    +621      return Flowable.just(HttpResponse.badRequest());
    +622    }
    +623
    +624    // okay, resolve content through the cache, backed by the asset manager
    +625    @Nonnull Optional<AssetManager.ManagedAssetContent> assetContent = this.assetManager.assetDataByToken(asset);
    +626    if (assetContent.isPresent() && assetContent.get().getVariantCount() > 0) {
    +627      // make sure the user is asking for this kind of content
    +628      var content = assetContent.get();
    +629      String[] ogFilename = content.getFilename().split("\\.");
    +630      if (ogFilename.length > 1 && ogFilename[ogFilename.length - 1].equals(ext)) {
    +631        // create response, affix other asset headers, and serve
    +632        return this.chooseAndServeVariant(
    +633          request,
    +634          resolveMediaType(ext),
    +635          content,
    +636          HttpResponse.ok());
    +637      } else {
    +638        logging.warn(format("Asset extension mismatch: '%s' does not match expected value for asset '%s' ('%s').",
    +639          ext,
    +640          asset,
    +641          ogFilename[ogFilename.length - 1]));
    +642      }
    +643    }
    +644    logging.warn(format("Asset '%s' not found.", asset));
    +645    return Flowable.just(HttpResponse.notFound());
    +646  }
    +647}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/BaseController.html b/docs/java/src-html/gust/backend/BaseController.html new file mode 100644 index 000000000..7b3e6b904 --- /dev/null +++ b/docs/java/src-html/gust/backend/BaseController.html @@ -0,0 +1,111 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015
    +016/**
    +017 * Supplies shared logic to all framework-provided controller base classes. Responsible for managing such things as the
    +018 * {@link PageContextManager}, and any other request-scoped state.
    +019 *
    +020 * <p>Implementors of this class are provided with convenient access to initialized app logic and clients (such as gRPC
    +021 * clients or database clients). However, controller authors need not select this route for convenience sake if they
    +022 * have a better base class in mind: all the functionality provided here can easily be obtained via dependency
    +023 * injection.</p>
    +024 */
    +025public abstract class BaseController {
    +026  /** Holds request-bound page context as it is built. */
    +027  protected final PageContextManager context;
    +028
    +029  /**
    +030   * Initialize a base Gust controller from scratch.
    +031   *
    +032   * @param context Page context manager, injected.
    +033   */
    +034  public BaseController(PageContextManager context) {
    +035    this.context = context;
    +036  }
    +037}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/DynamicServingConfiguration.ClientHintsConfiguration.html b/docs/java/src-html/gust/backend/DynamicServingConfiguration.ClientHintsConfiguration.html new file mode 100644 index 000000000..06a991b78 --- /dev/null +++ b/docs/java/src-html/gust/backend/DynamicServingConfiguration.ClientHintsConfiguration.html @@ -0,0 +1,291 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015import com.google.common.collect.ImmutableSet;
    +016import com.google.common.collect.ImmutableSortedSet;
    +017import com.google.common.collect.Sets;
    +018import io.micronaut.context.annotation.ConfigurationProperties;
    +019import io.micronaut.core.bind.annotation.Bindable;
    +020import tools.elide.page.Context.ClientHint;
    +021import tools.elide.page.Context.FramingPolicy;
    +022import tools.elide.page.Context.ReferrerPolicy;
    +023
    +024import java.util.*;
    +025import java.util.concurrent.TimeUnit;
    +026
    +027
    +028/** Supplies configuration structure for dynamically-served app pages through Gust. */
    +029@ConfigurationProperties("gust.serving")
    +030public interface DynamicServingConfiguration {
    +031  /** Set of sensible defaults for Gust dynamic serving. */
    +032  DynamicServingConfiguration DEFAULTS = new DynamicServingConfiguration() {};
    +033
    +034  /** Value to set for {@code Content-Language}. May be overridden by context. */
    +035  @Bindable("language") default Optional<String> language() {
    +036    return Optional.of("en-US");
    +037  }
    +038
    +039  /** {@code ETag} configuration for dynamic content. */
    +040  @Bindable("etags") default DynamicETagsConfiguration etags() {
    +041    return DynamicETagsConfiguration.DEFAULTS;
    +042  }
    +043
    +044  /** {@code Vary} configuration for dynamic content. */
    +045  @Bindable("vary") default DynamicVarianceConfiguration variance() {
    +046    return DynamicVarianceConfiguration.DEFAULTS;
    +047  }
    +048
    +049  /** {@code X-Frame-Options} configuration for dynamic content. */
    +050  @Bindable("framingPolicy") default FramingPolicy framingPolicy() {
    +051    return FramingPolicy.DENY;
    +052  }
    +053
    +054  /** Hostnames to pre-connect to from the browser. */
    +055  @Bindable("preconnect") default List<String> preconnect() {
    +056    return Collections.emptyList();
    +057  }
    +058
    +059  /** Hostnames to pre-load into the browser's DNS. */
    +060  @Bindable("dnsPrefetch") default List<String> dnsPrefetch() {
    +061    return Collections.emptyList();
    +062  }
    +063
    +064  /** Whether to apply {@code nosniff} to {@code X-Content-Type-Options} for dynamic content. */
    +065  @Bindable("noSniff") default Boolean noSniff() {
    +066    return true;
    +067  }
    +068
    +069  /** {@code Feature-Policy} configuration for dynamic content. */
    +070  @Bindable("featurePolicy") default FeaturePolicyConfiguration featurePolicy() {
    +071    return FeaturePolicyConfiguration.DEFAULTS;
    +072  }
    +073
    +074  /** {@code Referrer-Policy} configuration for dynamic content. */
    +075  @Bindable("referrerPolicy") default ReferrerPolicy referrerPolicy() {
    +076    return ReferrerPolicy.STRICT_ORIGIN;
    +077  }
    +078
    +079  /** {@code X-XSS-Protection} configuration for dynamic content. */
    +080  @Bindable("xssProtection") default XSSProtectionConfiguration xssProtection() {
    +081    return XSSProtectionConfiguration.DEFAULTS;
    +082  }
    +083
    +084  /** Settings related to support for Client Hints. */
    +085  @Bindable("clientHints") default ClientHintsConfiguration clientHints() {
    +086    return ClientHintsConfiguration.DEFAULTS;
    +087  }
    +088
    +089  /** Arbitrary headers to add to all responses. */
    +090  @Bindable("additionalHeaders") default Map<String, String> additionalHeaders() {
    +091    return Collections.emptyMap();
    +092  }
    +093
    +094  /** Describes settings regarding {@code ETag} headers for dynamic content. */
    +095  @ConfigurationProperties("gust.serving.etags")
    +096  interface DynamicETagsConfiguration {
    +097    /** Sensible defaults for dynamic etags. */
    +098    DynamicETagsConfiguration DEFAULTS = new DynamicETagsConfiguration() {};
    +099
    +100    /** Whether to enable {@code ETag} headers for dynamically-served content. */
    +101    @Bindable("enabled") default Boolean enabled() {
    +102      return false;
    +103    }
    +104  }
    +105
    +106  /** Describes settings regarding {@code Feature-Policy} headers for dynamic content. */
    +107  @ConfigurationProperties("gust.serving.featurePolicy")
    +108  interface FeaturePolicyConfiguration {
    +109    /** Sensible defaults for {@code Feature-Policy}. */
    +110    FeaturePolicyConfiguration DEFAULTS = new FeaturePolicyConfiguration() {};
    +111
    +112    /** Whether to enable {@code Feature-Policy} headers for dynamically-served content. */
    +113    @Bindable("enabled") default Boolean enabled() {
    +114      return true;
    +115    }
    +116
    +117    /** Specifies the default {@code Feature-Policy} to apply to dynamically-served content. */
    +118    @Bindable("policy") default SortedSet<String> policy() {
    +119      return ImmutableSortedSet.of(
    +120        "document-domain 'none';",
    +121        "sync-xhr 'none';"
    +122      );
    +123    }
    +124  }
    +125
    +126  /** Describes settings related to Client Hints support. */
    +127  @ConfigurationProperties("gust.serving.clientHints")
    +128  interface ClientHintsConfiguration {
    +129    /** Sensible defaults for client hints. */
    +130    ClientHintsConfiguration DEFAULTS = new ClientHintsConfiguration() {};
    +131
    +132    /** Whether to enable support for client hints. */
    +133    @Bindable("enabled") default Boolean enabled() {
    +134      return true;
    +135    }
    +136
    +137    /** Return the set of hints supported by the server. */
    +138    @Bindable("hints") default ImmutableSet<ClientHint> hints() {
    +139      return Sets.immutableEnumSet(
    +140          ClientHint.ECT,
    +141          ClientHint.RTT,
    +142          ClientHint.DPR,
    +143          ClientHint.WIDTH,
    +144          ClientHint.VIEWPORT_WIDTH,
    +145          ClientHint.DOWNLINK);
    +146    }
    +147
    +148    /** Client Hints configuration time-to-live value. */
    +149    @Bindable("ttl") default Optional<Long> ttl() {
    +150      return Optional.of(7L);
    +151    }
    +152
    +153    /** Client Hints configuration time-to-live unit. Defaults to {@code DAYS}. */
    +154    @Bindable("ttlUnit") default TimeUnit ttlUnit() {
    +155      return TimeUnit.DAYS;
    +156    }
    +157  }
    +158
    +159  /** Describes settings regarding {@code Vary} headers for dynamic content. */
    +160  @ConfigurationProperties("gust.serving.vary")
    +161  interface DynamicVarianceConfiguration {
    +162    /** Sensible defaults for dynamic page variance. */
    +163    DynamicVarianceConfiguration DEFAULTS = new DynamicVarianceConfiguration() {};
    +164
    +165    /** Whether to enable {@code Vary} headers for dynamically-served content. */
    +166    @Bindable("enabled") default Boolean enabled() {
    +167      return true;
    +168    }
    +169
    +170    /** Whether to indicate response variance by {@code Accept}. */
    +171    @Bindable("accept") default Boolean accept() {
    +172      return true;
    +173    }
    +174
    +175    /** Whether to indicate response variance by {@code Accept-Charset}. */
    +176    @Bindable("charset") default Boolean charset() {
    +177      return false;
    +178    }
    +179
    +180    /** Whether to indicate response variance by {@code Accept-Encoding}. */
    +181    @Bindable("encoding") default Boolean encoding() {
    +182      return true;
    +183    }
    +184
    +185    /** Whether to indicate response variance by {@code Accept-Language}. */
    +186    @Bindable("language") default Boolean language() {
    +187      return false;
    +188    }
    +189
    +190    /** Whether to indicate response variance by {@code Origin}. */
    +191    @Bindable("origin") default Boolean origin() {
    +192      return false;
    +193    }
    +194  }
    +195
    +196  /** Describes settings regarding {@code X-XSS-Protection} headers for dynamic content. */
    +197  @ConfigurationProperties("gust.serving.xssProtection")
    +198  interface XSSProtectionConfiguration {
    +199    /** Sensible defaults for cross-site scripting protection. */
    +200    XSSProtectionConfiguration DEFAULTS = new XSSProtectionConfiguration() {};
    +201
    +202    /** Whether to enable old-style XSS protection. */
    +203    @Bindable("enabled") default Boolean enabled() {
    +204      return true;
    +205    }
    +206
    +207    /** Whether to specify XSS protection as active. */
    +208    @Bindable("filter") default Boolean filter() {
    +209      return true;
    +210    }
    +211
    +212    /** Whether to add the {@code block} flag. */
    +213    @Bindable("block") default Boolean block() {
    +214      return true;
    +215    }
    +216  }
    +217}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/DynamicServingConfiguration.DynamicETagsConfiguration.html b/docs/java/src-html/gust/backend/DynamicServingConfiguration.DynamicETagsConfiguration.html new file mode 100644 index 000000000..06a991b78 --- /dev/null +++ b/docs/java/src-html/gust/backend/DynamicServingConfiguration.DynamicETagsConfiguration.html @@ -0,0 +1,291 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015import com.google.common.collect.ImmutableSet;
    +016import com.google.common.collect.ImmutableSortedSet;
    +017import com.google.common.collect.Sets;
    +018import io.micronaut.context.annotation.ConfigurationProperties;
    +019import io.micronaut.core.bind.annotation.Bindable;
    +020import tools.elide.page.Context.ClientHint;
    +021import tools.elide.page.Context.FramingPolicy;
    +022import tools.elide.page.Context.ReferrerPolicy;
    +023
    +024import java.util.*;
    +025import java.util.concurrent.TimeUnit;
    +026
    +027
    +028/** Supplies configuration structure for dynamically-served app pages through Gust. */
    +029@ConfigurationProperties("gust.serving")
    +030public interface DynamicServingConfiguration {
    +031  /** Set of sensible defaults for Gust dynamic serving. */
    +032  DynamicServingConfiguration DEFAULTS = new DynamicServingConfiguration() {};
    +033
    +034  /** Value to set for {@code Content-Language}. May be overridden by context. */
    +035  @Bindable("language") default Optional<String> language() {
    +036    return Optional.of("en-US");
    +037  }
    +038
    +039  /** {@code ETag} configuration for dynamic content. */
    +040  @Bindable("etags") default DynamicETagsConfiguration etags() {
    +041    return DynamicETagsConfiguration.DEFAULTS;
    +042  }
    +043
    +044  /** {@code Vary} configuration for dynamic content. */
    +045  @Bindable("vary") default DynamicVarianceConfiguration variance() {
    +046    return DynamicVarianceConfiguration.DEFAULTS;
    +047  }
    +048
    +049  /** {@code X-Frame-Options} configuration for dynamic content. */
    +050  @Bindable("framingPolicy") default FramingPolicy framingPolicy() {
    +051    return FramingPolicy.DENY;
    +052  }
    +053
    +054  /** Hostnames to pre-connect to from the browser. */
    +055  @Bindable("preconnect") default List<String> preconnect() {
    +056    return Collections.emptyList();
    +057  }
    +058
    +059  /** Hostnames to pre-load into the browser's DNS. */
    +060  @Bindable("dnsPrefetch") default List<String> dnsPrefetch() {
    +061    return Collections.emptyList();
    +062  }
    +063
    +064  /** Whether to apply {@code nosniff} to {@code X-Content-Type-Options} for dynamic content. */
    +065  @Bindable("noSniff") default Boolean noSniff() {
    +066    return true;
    +067  }
    +068
    +069  /** {@code Feature-Policy} configuration for dynamic content. */
    +070  @Bindable("featurePolicy") default FeaturePolicyConfiguration featurePolicy() {
    +071    return FeaturePolicyConfiguration.DEFAULTS;
    +072  }
    +073
    +074  /** {@code Referrer-Policy} configuration for dynamic content. */
    +075  @Bindable("referrerPolicy") default ReferrerPolicy referrerPolicy() {
    +076    return ReferrerPolicy.STRICT_ORIGIN;
    +077  }
    +078
    +079  /** {@code X-XSS-Protection} configuration for dynamic content. */
    +080  @Bindable("xssProtection") default XSSProtectionConfiguration xssProtection() {
    +081    return XSSProtectionConfiguration.DEFAULTS;
    +082  }
    +083
    +084  /** Settings related to support for Client Hints. */
    +085  @Bindable("clientHints") default ClientHintsConfiguration clientHints() {
    +086    return ClientHintsConfiguration.DEFAULTS;
    +087  }
    +088
    +089  /** Arbitrary headers to add to all responses. */
    +090  @Bindable("additionalHeaders") default Map<String, String> additionalHeaders() {
    +091    return Collections.emptyMap();
    +092  }
    +093
    +094  /** Describes settings regarding {@code ETag} headers for dynamic content. */
    +095  @ConfigurationProperties("gust.serving.etags")
    +096  interface DynamicETagsConfiguration {
    +097    /** Sensible defaults for dynamic etags. */
    +098    DynamicETagsConfiguration DEFAULTS = new DynamicETagsConfiguration() {};
    +099
    +100    /** Whether to enable {@code ETag} headers for dynamically-served content. */
    +101    @Bindable("enabled") default Boolean enabled() {
    +102      return false;
    +103    }
    +104  }
    +105
    +106  /** Describes settings regarding {@code Feature-Policy} headers for dynamic content. */
    +107  @ConfigurationProperties("gust.serving.featurePolicy")
    +108  interface FeaturePolicyConfiguration {
    +109    /** Sensible defaults for {@code Feature-Policy}. */
    +110    FeaturePolicyConfiguration DEFAULTS = new FeaturePolicyConfiguration() {};
    +111
    +112    /** Whether to enable {@code Feature-Policy} headers for dynamically-served content. */
    +113    @Bindable("enabled") default Boolean enabled() {
    +114      return true;
    +115    }
    +116
    +117    /** Specifies the default {@code Feature-Policy} to apply to dynamically-served content. */
    +118    @Bindable("policy") default SortedSet<String> policy() {
    +119      return ImmutableSortedSet.of(
    +120        "document-domain 'none';",
    +121        "sync-xhr 'none';"
    +122      );
    +123    }
    +124  }
    +125
    +126  /** Describes settings related to Client Hints support. */
    +127  @ConfigurationProperties("gust.serving.clientHints")
    +128  interface ClientHintsConfiguration {
    +129    /** Sensible defaults for client hints. */
    +130    ClientHintsConfiguration DEFAULTS = new ClientHintsConfiguration() {};
    +131
    +132    /** Whether to enable support for client hints. */
    +133    @Bindable("enabled") default Boolean enabled() {
    +134      return true;
    +135    }
    +136
    +137    /** Return the set of hints supported by the server. */
    +138    @Bindable("hints") default ImmutableSet<ClientHint> hints() {
    +139      return Sets.immutableEnumSet(
    +140          ClientHint.ECT,
    +141          ClientHint.RTT,
    +142          ClientHint.DPR,
    +143          ClientHint.WIDTH,
    +144          ClientHint.VIEWPORT_WIDTH,
    +145          ClientHint.DOWNLINK);
    +146    }
    +147
    +148    /** Client Hints configuration time-to-live value. */
    +149    @Bindable("ttl") default Optional<Long> ttl() {
    +150      return Optional.of(7L);
    +151    }
    +152
    +153    /** Client Hints configuration time-to-live unit. Defaults to {@code DAYS}. */
    +154    @Bindable("ttlUnit") default TimeUnit ttlUnit() {
    +155      return TimeUnit.DAYS;
    +156    }
    +157  }
    +158
    +159  /** Describes settings regarding {@code Vary} headers for dynamic content. */
    +160  @ConfigurationProperties("gust.serving.vary")
    +161  interface DynamicVarianceConfiguration {
    +162    /** Sensible defaults for dynamic page variance. */
    +163    DynamicVarianceConfiguration DEFAULTS = new DynamicVarianceConfiguration() {};
    +164
    +165    /** Whether to enable {@code Vary} headers for dynamically-served content. */
    +166    @Bindable("enabled") default Boolean enabled() {
    +167      return true;
    +168    }
    +169
    +170    /** Whether to indicate response variance by {@code Accept}. */
    +171    @Bindable("accept") default Boolean accept() {
    +172      return true;
    +173    }
    +174
    +175    /** Whether to indicate response variance by {@code Accept-Charset}. */
    +176    @Bindable("charset") default Boolean charset() {
    +177      return false;
    +178    }
    +179
    +180    /** Whether to indicate response variance by {@code Accept-Encoding}. */
    +181    @Bindable("encoding") default Boolean encoding() {
    +182      return true;
    +183    }
    +184
    +185    /** Whether to indicate response variance by {@code Accept-Language}. */
    +186    @Bindable("language") default Boolean language() {
    +187      return false;
    +188    }
    +189
    +190    /** Whether to indicate response variance by {@code Origin}. */
    +191    @Bindable("origin") default Boolean origin() {
    +192      return false;
    +193    }
    +194  }
    +195
    +196  /** Describes settings regarding {@code X-XSS-Protection} headers for dynamic content. */
    +197  @ConfigurationProperties("gust.serving.xssProtection")
    +198  interface XSSProtectionConfiguration {
    +199    /** Sensible defaults for cross-site scripting protection. */
    +200    XSSProtectionConfiguration DEFAULTS = new XSSProtectionConfiguration() {};
    +201
    +202    /** Whether to enable old-style XSS protection. */
    +203    @Bindable("enabled") default Boolean enabled() {
    +204      return true;
    +205    }
    +206
    +207    /** Whether to specify XSS protection as active. */
    +208    @Bindable("filter") default Boolean filter() {
    +209      return true;
    +210    }
    +211
    +212    /** Whether to add the {@code block} flag. */
    +213    @Bindable("block") default Boolean block() {
    +214      return true;
    +215    }
    +216  }
    +217}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/DynamicServingConfiguration.DynamicVarianceConfiguration.html b/docs/java/src-html/gust/backend/DynamicServingConfiguration.DynamicVarianceConfiguration.html new file mode 100644 index 000000000..06a991b78 --- /dev/null +++ b/docs/java/src-html/gust/backend/DynamicServingConfiguration.DynamicVarianceConfiguration.html @@ -0,0 +1,291 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015import com.google.common.collect.ImmutableSet;
    +016import com.google.common.collect.ImmutableSortedSet;
    +017import com.google.common.collect.Sets;
    +018import io.micronaut.context.annotation.ConfigurationProperties;
    +019import io.micronaut.core.bind.annotation.Bindable;
    +020import tools.elide.page.Context.ClientHint;
    +021import tools.elide.page.Context.FramingPolicy;
    +022import tools.elide.page.Context.ReferrerPolicy;
    +023
    +024import java.util.*;
    +025import java.util.concurrent.TimeUnit;
    +026
    +027
    +028/** Supplies configuration structure for dynamically-served app pages through Gust. */
    +029@ConfigurationProperties("gust.serving")
    +030public interface DynamicServingConfiguration {
    +031  /** Set of sensible defaults for Gust dynamic serving. */
    +032  DynamicServingConfiguration DEFAULTS = new DynamicServingConfiguration() {};
    +033
    +034  /** Value to set for {@code Content-Language}. May be overridden by context. */
    +035  @Bindable("language") default Optional<String> language() {
    +036    return Optional.of("en-US");
    +037  }
    +038
    +039  /** {@code ETag} configuration for dynamic content. */
    +040  @Bindable("etags") default DynamicETagsConfiguration etags() {
    +041    return DynamicETagsConfiguration.DEFAULTS;
    +042  }
    +043
    +044  /** {@code Vary} configuration for dynamic content. */
    +045  @Bindable("vary") default DynamicVarianceConfiguration variance() {
    +046    return DynamicVarianceConfiguration.DEFAULTS;
    +047  }
    +048
    +049  /** {@code X-Frame-Options} configuration for dynamic content. */
    +050  @Bindable("framingPolicy") default FramingPolicy framingPolicy() {
    +051    return FramingPolicy.DENY;
    +052  }
    +053
    +054  /** Hostnames to pre-connect to from the browser. */
    +055  @Bindable("preconnect") default List<String> preconnect() {
    +056    return Collections.emptyList();
    +057  }
    +058
    +059  /** Hostnames to pre-load into the browser's DNS. */
    +060  @Bindable("dnsPrefetch") default List<String> dnsPrefetch() {
    +061    return Collections.emptyList();
    +062  }
    +063
    +064  /** Whether to apply {@code nosniff} to {@code X-Content-Type-Options} for dynamic content. */
    +065  @Bindable("noSniff") default Boolean noSniff() {
    +066    return true;
    +067  }
    +068
    +069  /** {@code Feature-Policy} configuration for dynamic content. */
    +070  @Bindable("featurePolicy") default FeaturePolicyConfiguration featurePolicy() {
    +071    return FeaturePolicyConfiguration.DEFAULTS;
    +072  }
    +073
    +074  /** {@code Referrer-Policy} configuration for dynamic content. */
    +075  @Bindable("referrerPolicy") default ReferrerPolicy referrerPolicy() {
    +076    return ReferrerPolicy.STRICT_ORIGIN;
    +077  }
    +078
    +079  /** {@code X-XSS-Protection} configuration for dynamic content. */
    +080  @Bindable("xssProtection") default XSSProtectionConfiguration xssProtection() {
    +081    return XSSProtectionConfiguration.DEFAULTS;
    +082  }
    +083
    +084  /** Settings related to support for Client Hints. */
    +085  @Bindable("clientHints") default ClientHintsConfiguration clientHints() {
    +086    return ClientHintsConfiguration.DEFAULTS;
    +087  }
    +088
    +089  /** Arbitrary headers to add to all responses. */
    +090  @Bindable("additionalHeaders") default Map<String, String> additionalHeaders() {
    +091    return Collections.emptyMap();
    +092  }
    +093
    +094  /** Describes settings regarding {@code ETag} headers for dynamic content. */
    +095  @ConfigurationProperties("gust.serving.etags")
    +096  interface DynamicETagsConfiguration {
    +097    /** Sensible defaults for dynamic etags. */
    +098    DynamicETagsConfiguration DEFAULTS = new DynamicETagsConfiguration() {};
    +099
    +100    /** Whether to enable {@code ETag} headers for dynamically-served content. */
    +101    @Bindable("enabled") default Boolean enabled() {
    +102      return false;
    +103    }
    +104  }
    +105
    +106  /** Describes settings regarding {@code Feature-Policy} headers for dynamic content. */
    +107  @ConfigurationProperties("gust.serving.featurePolicy")
    +108  interface FeaturePolicyConfiguration {
    +109    /** Sensible defaults for {@code Feature-Policy}. */
    +110    FeaturePolicyConfiguration DEFAULTS = new FeaturePolicyConfiguration() {};
    +111
    +112    /** Whether to enable {@code Feature-Policy} headers for dynamically-served content. */
    +113    @Bindable("enabled") default Boolean enabled() {
    +114      return true;
    +115    }
    +116
    +117    /** Specifies the default {@code Feature-Policy} to apply to dynamically-served content. */
    +118    @Bindable("policy") default SortedSet<String> policy() {
    +119      return ImmutableSortedSet.of(
    +120        "document-domain 'none';",
    +121        "sync-xhr 'none';"
    +122      );
    +123    }
    +124  }
    +125
    +126  /** Describes settings related to Client Hints support. */
    +127  @ConfigurationProperties("gust.serving.clientHints")
    +128  interface ClientHintsConfiguration {
    +129    /** Sensible defaults for client hints. */
    +130    ClientHintsConfiguration DEFAULTS = new ClientHintsConfiguration() {};
    +131
    +132    /** Whether to enable support for client hints. */
    +133    @Bindable("enabled") default Boolean enabled() {
    +134      return true;
    +135    }
    +136
    +137    /** Return the set of hints supported by the server. */
    +138    @Bindable("hints") default ImmutableSet<ClientHint> hints() {
    +139      return Sets.immutableEnumSet(
    +140          ClientHint.ECT,
    +141          ClientHint.RTT,
    +142          ClientHint.DPR,
    +143          ClientHint.WIDTH,
    +144          ClientHint.VIEWPORT_WIDTH,
    +145          ClientHint.DOWNLINK);
    +146    }
    +147
    +148    /** Client Hints configuration time-to-live value. */
    +149    @Bindable("ttl") default Optional<Long> ttl() {
    +150      return Optional.of(7L);
    +151    }
    +152
    +153    /** Client Hints configuration time-to-live unit. Defaults to {@code DAYS}. */
    +154    @Bindable("ttlUnit") default TimeUnit ttlUnit() {
    +155      return TimeUnit.DAYS;
    +156    }
    +157  }
    +158
    +159  /** Describes settings regarding {@code Vary} headers for dynamic content. */
    +160  @ConfigurationProperties("gust.serving.vary")
    +161  interface DynamicVarianceConfiguration {
    +162    /** Sensible defaults for dynamic page variance. */
    +163    DynamicVarianceConfiguration DEFAULTS = new DynamicVarianceConfiguration() {};
    +164
    +165    /** Whether to enable {@code Vary} headers for dynamically-served content. */
    +166    @Bindable("enabled") default Boolean enabled() {
    +167      return true;
    +168    }
    +169
    +170    /** Whether to indicate response variance by {@code Accept}. */
    +171    @Bindable("accept") default Boolean accept() {
    +172      return true;
    +173    }
    +174
    +175    /** Whether to indicate response variance by {@code Accept-Charset}. */
    +176    @Bindable("charset") default Boolean charset() {
    +177      return false;
    +178    }
    +179
    +180    /** Whether to indicate response variance by {@code Accept-Encoding}. */
    +181    @Bindable("encoding") default Boolean encoding() {
    +182      return true;
    +183    }
    +184
    +185    /** Whether to indicate response variance by {@code Accept-Language}. */
    +186    @Bindable("language") default Boolean language() {
    +187      return false;
    +188    }
    +189
    +190    /** Whether to indicate response variance by {@code Origin}. */
    +191    @Bindable("origin") default Boolean origin() {
    +192      return false;
    +193    }
    +194  }
    +195
    +196  /** Describes settings regarding {@code X-XSS-Protection} headers for dynamic content. */
    +197  @ConfigurationProperties("gust.serving.xssProtection")
    +198  interface XSSProtectionConfiguration {
    +199    /** Sensible defaults for cross-site scripting protection. */
    +200    XSSProtectionConfiguration DEFAULTS = new XSSProtectionConfiguration() {};
    +201
    +202    /** Whether to enable old-style XSS protection. */
    +203    @Bindable("enabled") default Boolean enabled() {
    +204      return true;
    +205    }
    +206
    +207    /** Whether to specify XSS protection as active. */
    +208    @Bindable("filter") default Boolean filter() {
    +209      return true;
    +210    }
    +211
    +212    /** Whether to add the {@code block} flag. */
    +213    @Bindable("block") default Boolean block() {
    +214      return true;
    +215    }
    +216  }
    +217}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/DynamicServingConfiguration.FeaturePolicyConfiguration.html b/docs/java/src-html/gust/backend/DynamicServingConfiguration.FeaturePolicyConfiguration.html new file mode 100644 index 000000000..06a991b78 --- /dev/null +++ b/docs/java/src-html/gust/backend/DynamicServingConfiguration.FeaturePolicyConfiguration.html @@ -0,0 +1,291 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015import com.google.common.collect.ImmutableSet;
    +016import com.google.common.collect.ImmutableSortedSet;
    +017import com.google.common.collect.Sets;
    +018import io.micronaut.context.annotation.ConfigurationProperties;
    +019import io.micronaut.core.bind.annotation.Bindable;
    +020import tools.elide.page.Context.ClientHint;
    +021import tools.elide.page.Context.FramingPolicy;
    +022import tools.elide.page.Context.ReferrerPolicy;
    +023
    +024import java.util.*;
    +025import java.util.concurrent.TimeUnit;
    +026
    +027
    +028/** Supplies configuration structure for dynamically-served app pages through Gust. */
    +029@ConfigurationProperties("gust.serving")
    +030public interface DynamicServingConfiguration {
    +031  /** Set of sensible defaults for Gust dynamic serving. */
    +032  DynamicServingConfiguration DEFAULTS = new DynamicServingConfiguration() {};
    +033
    +034  /** Value to set for {@code Content-Language}. May be overridden by context. */
    +035  @Bindable("language") default Optional<String> language() {
    +036    return Optional.of("en-US");
    +037  }
    +038
    +039  /** {@code ETag} configuration for dynamic content. */
    +040  @Bindable("etags") default DynamicETagsConfiguration etags() {
    +041    return DynamicETagsConfiguration.DEFAULTS;
    +042  }
    +043
    +044  /** {@code Vary} configuration for dynamic content. */
    +045  @Bindable("vary") default DynamicVarianceConfiguration variance() {
    +046    return DynamicVarianceConfiguration.DEFAULTS;
    +047  }
    +048
    +049  /** {@code X-Frame-Options} configuration for dynamic content. */
    +050  @Bindable("framingPolicy") default FramingPolicy framingPolicy() {
    +051    return FramingPolicy.DENY;
    +052  }
    +053
    +054  /** Hostnames to pre-connect to from the browser. */
    +055  @Bindable("preconnect") default List<String> preconnect() {
    +056    return Collections.emptyList();
    +057  }
    +058
    +059  /** Hostnames to pre-load into the browser's DNS. */
    +060  @Bindable("dnsPrefetch") default List<String> dnsPrefetch() {
    +061    return Collections.emptyList();
    +062  }
    +063
    +064  /** Whether to apply {@code nosniff} to {@code X-Content-Type-Options} for dynamic content. */
    +065  @Bindable("noSniff") default Boolean noSniff() {
    +066    return true;
    +067  }
    +068
    +069  /** {@code Feature-Policy} configuration for dynamic content. */
    +070  @Bindable("featurePolicy") default FeaturePolicyConfiguration featurePolicy() {
    +071    return FeaturePolicyConfiguration.DEFAULTS;
    +072  }
    +073
    +074  /** {@code Referrer-Policy} configuration for dynamic content. */
    +075  @Bindable("referrerPolicy") default ReferrerPolicy referrerPolicy() {
    +076    return ReferrerPolicy.STRICT_ORIGIN;
    +077  }
    +078
    +079  /** {@code X-XSS-Protection} configuration for dynamic content. */
    +080  @Bindable("xssProtection") default XSSProtectionConfiguration xssProtection() {
    +081    return XSSProtectionConfiguration.DEFAULTS;
    +082  }
    +083
    +084  /** Settings related to support for Client Hints. */
    +085  @Bindable("clientHints") default ClientHintsConfiguration clientHints() {
    +086    return ClientHintsConfiguration.DEFAULTS;
    +087  }
    +088
    +089  /** Arbitrary headers to add to all responses. */
    +090  @Bindable("additionalHeaders") default Map<String, String> additionalHeaders() {
    +091    return Collections.emptyMap();
    +092  }
    +093
    +094  /** Describes settings regarding {@code ETag} headers for dynamic content. */
    +095  @ConfigurationProperties("gust.serving.etags")
    +096  interface DynamicETagsConfiguration {
    +097    /** Sensible defaults for dynamic etags. */
    +098    DynamicETagsConfiguration DEFAULTS = new DynamicETagsConfiguration() {};
    +099
    +100    /** Whether to enable {@code ETag} headers for dynamically-served content. */
    +101    @Bindable("enabled") default Boolean enabled() {
    +102      return false;
    +103    }
    +104  }
    +105
    +106  /** Describes settings regarding {@code Feature-Policy} headers for dynamic content. */
    +107  @ConfigurationProperties("gust.serving.featurePolicy")
    +108  interface FeaturePolicyConfiguration {
    +109    /** Sensible defaults for {@code Feature-Policy}. */
    +110    FeaturePolicyConfiguration DEFAULTS = new FeaturePolicyConfiguration() {};
    +111
    +112    /** Whether to enable {@code Feature-Policy} headers for dynamically-served content. */
    +113    @Bindable("enabled") default Boolean enabled() {
    +114      return true;
    +115    }
    +116
    +117    /** Specifies the default {@code Feature-Policy} to apply to dynamically-served content. */
    +118    @Bindable("policy") default SortedSet<String> policy() {
    +119      return ImmutableSortedSet.of(
    +120        "document-domain 'none';",
    +121        "sync-xhr 'none';"
    +122      );
    +123    }
    +124  }
    +125
    +126  /** Describes settings related to Client Hints support. */
    +127  @ConfigurationProperties("gust.serving.clientHints")
    +128  interface ClientHintsConfiguration {
    +129    /** Sensible defaults for client hints. */
    +130    ClientHintsConfiguration DEFAULTS = new ClientHintsConfiguration() {};
    +131
    +132    /** Whether to enable support for client hints. */
    +133    @Bindable("enabled") default Boolean enabled() {
    +134      return true;
    +135    }
    +136
    +137    /** Return the set of hints supported by the server. */
    +138    @Bindable("hints") default ImmutableSet<ClientHint> hints() {
    +139      return Sets.immutableEnumSet(
    +140          ClientHint.ECT,
    +141          ClientHint.RTT,
    +142          ClientHint.DPR,
    +143          ClientHint.WIDTH,
    +144          ClientHint.VIEWPORT_WIDTH,
    +145          ClientHint.DOWNLINK);
    +146    }
    +147
    +148    /** Client Hints configuration time-to-live value. */
    +149    @Bindable("ttl") default Optional<Long> ttl() {
    +150      return Optional.of(7L);
    +151    }
    +152
    +153    /** Client Hints configuration time-to-live unit. Defaults to {@code DAYS}. */
    +154    @Bindable("ttlUnit") default TimeUnit ttlUnit() {
    +155      return TimeUnit.DAYS;
    +156    }
    +157  }
    +158
    +159  /** Describes settings regarding {@code Vary} headers for dynamic content. */
    +160  @ConfigurationProperties("gust.serving.vary")
    +161  interface DynamicVarianceConfiguration {
    +162    /** Sensible defaults for dynamic page variance. */
    +163    DynamicVarianceConfiguration DEFAULTS = new DynamicVarianceConfiguration() {};
    +164
    +165    /** Whether to enable {@code Vary} headers for dynamically-served content. */
    +166    @Bindable("enabled") default Boolean enabled() {
    +167      return true;
    +168    }
    +169
    +170    /** Whether to indicate response variance by {@code Accept}. */
    +171    @Bindable("accept") default Boolean accept() {
    +172      return true;
    +173    }
    +174
    +175    /** Whether to indicate response variance by {@code Accept-Charset}. */
    +176    @Bindable("charset") default Boolean charset() {
    +177      return false;
    +178    }
    +179
    +180    /** Whether to indicate response variance by {@code Accept-Encoding}. */
    +181    @Bindable("encoding") default Boolean encoding() {
    +182      return true;
    +183    }
    +184
    +185    /** Whether to indicate response variance by {@code Accept-Language}. */
    +186    @Bindable("language") default Boolean language() {
    +187      return false;
    +188    }
    +189
    +190    /** Whether to indicate response variance by {@code Origin}. */
    +191    @Bindable("origin") default Boolean origin() {
    +192      return false;
    +193    }
    +194  }
    +195
    +196  /** Describes settings regarding {@code X-XSS-Protection} headers for dynamic content. */
    +197  @ConfigurationProperties("gust.serving.xssProtection")
    +198  interface XSSProtectionConfiguration {
    +199    /** Sensible defaults for cross-site scripting protection. */
    +200    XSSProtectionConfiguration DEFAULTS = new XSSProtectionConfiguration() {};
    +201
    +202    /** Whether to enable old-style XSS protection. */
    +203    @Bindable("enabled") default Boolean enabled() {
    +204      return true;
    +205    }
    +206
    +207    /** Whether to specify XSS protection as active. */
    +208    @Bindable("filter") default Boolean filter() {
    +209      return true;
    +210    }
    +211
    +212    /** Whether to add the {@code block} flag. */
    +213    @Bindable("block") default Boolean block() {
    +214      return true;
    +215    }
    +216  }
    +217}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/DynamicServingConfiguration.XSSProtectionConfiguration.html b/docs/java/src-html/gust/backend/DynamicServingConfiguration.XSSProtectionConfiguration.html new file mode 100644 index 000000000..06a991b78 --- /dev/null +++ b/docs/java/src-html/gust/backend/DynamicServingConfiguration.XSSProtectionConfiguration.html @@ -0,0 +1,291 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015import com.google.common.collect.ImmutableSet;
    +016import com.google.common.collect.ImmutableSortedSet;
    +017import com.google.common.collect.Sets;
    +018import io.micronaut.context.annotation.ConfigurationProperties;
    +019import io.micronaut.core.bind.annotation.Bindable;
    +020import tools.elide.page.Context.ClientHint;
    +021import tools.elide.page.Context.FramingPolicy;
    +022import tools.elide.page.Context.ReferrerPolicy;
    +023
    +024import java.util.*;
    +025import java.util.concurrent.TimeUnit;
    +026
    +027
    +028/** Supplies configuration structure for dynamically-served app pages through Gust. */
    +029@ConfigurationProperties("gust.serving")
    +030public interface DynamicServingConfiguration {
    +031  /** Set of sensible defaults for Gust dynamic serving. */
    +032  DynamicServingConfiguration DEFAULTS = new DynamicServingConfiguration() {};
    +033
    +034  /** Value to set for {@code Content-Language}. May be overridden by context. */
    +035  @Bindable("language") default Optional<String> language() {
    +036    return Optional.of("en-US");
    +037  }
    +038
    +039  /** {@code ETag} configuration for dynamic content. */
    +040  @Bindable("etags") default DynamicETagsConfiguration etags() {
    +041    return DynamicETagsConfiguration.DEFAULTS;
    +042  }
    +043
    +044  /** {@code Vary} configuration for dynamic content. */
    +045  @Bindable("vary") default DynamicVarianceConfiguration variance() {
    +046    return DynamicVarianceConfiguration.DEFAULTS;
    +047  }
    +048
    +049  /** {@code X-Frame-Options} configuration for dynamic content. */
    +050  @Bindable("framingPolicy") default FramingPolicy framingPolicy() {
    +051    return FramingPolicy.DENY;
    +052  }
    +053
    +054  /** Hostnames to pre-connect to from the browser. */
    +055  @Bindable("preconnect") default List<String> preconnect() {
    +056    return Collections.emptyList();
    +057  }
    +058
    +059  /** Hostnames to pre-load into the browser's DNS. */
    +060  @Bindable("dnsPrefetch") default List<String> dnsPrefetch() {
    +061    return Collections.emptyList();
    +062  }
    +063
    +064  /** Whether to apply {@code nosniff} to {@code X-Content-Type-Options} for dynamic content. */
    +065  @Bindable("noSniff") default Boolean noSniff() {
    +066    return true;
    +067  }
    +068
    +069  /** {@code Feature-Policy} configuration for dynamic content. */
    +070  @Bindable("featurePolicy") default FeaturePolicyConfiguration featurePolicy() {
    +071    return FeaturePolicyConfiguration.DEFAULTS;
    +072  }
    +073
    +074  /** {@code Referrer-Policy} configuration for dynamic content. */
    +075  @Bindable("referrerPolicy") default ReferrerPolicy referrerPolicy() {
    +076    return ReferrerPolicy.STRICT_ORIGIN;
    +077  }
    +078
    +079  /** {@code X-XSS-Protection} configuration for dynamic content. */
    +080  @Bindable("xssProtection") default XSSProtectionConfiguration xssProtection() {
    +081    return XSSProtectionConfiguration.DEFAULTS;
    +082  }
    +083
    +084  /** Settings related to support for Client Hints. */
    +085  @Bindable("clientHints") default ClientHintsConfiguration clientHints() {
    +086    return ClientHintsConfiguration.DEFAULTS;
    +087  }
    +088
    +089  /** Arbitrary headers to add to all responses. */
    +090  @Bindable("additionalHeaders") default Map<String, String> additionalHeaders() {
    +091    return Collections.emptyMap();
    +092  }
    +093
    +094  /** Describes settings regarding {@code ETag} headers for dynamic content. */
    +095  @ConfigurationProperties("gust.serving.etags")
    +096  interface DynamicETagsConfiguration {
    +097    /** Sensible defaults for dynamic etags. */
    +098    DynamicETagsConfiguration DEFAULTS = new DynamicETagsConfiguration() {};
    +099
    +100    /** Whether to enable {@code ETag} headers for dynamically-served content. */
    +101    @Bindable("enabled") default Boolean enabled() {
    +102      return false;
    +103    }
    +104  }
    +105
    +106  /** Describes settings regarding {@code Feature-Policy} headers for dynamic content. */
    +107  @ConfigurationProperties("gust.serving.featurePolicy")
    +108  interface FeaturePolicyConfiguration {
    +109    /** Sensible defaults for {@code Feature-Policy}. */
    +110    FeaturePolicyConfiguration DEFAULTS = new FeaturePolicyConfiguration() {};
    +111
    +112    /** Whether to enable {@code Feature-Policy} headers for dynamically-served content. */
    +113    @Bindable("enabled") default Boolean enabled() {
    +114      return true;
    +115    }
    +116
    +117    /** Specifies the default {@code Feature-Policy} to apply to dynamically-served content. */
    +118    @Bindable("policy") default SortedSet<String> policy() {
    +119      return ImmutableSortedSet.of(
    +120        "document-domain 'none';",
    +121        "sync-xhr 'none';"
    +122      );
    +123    }
    +124  }
    +125
    +126  /** Describes settings related to Client Hints support. */
    +127  @ConfigurationProperties("gust.serving.clientHints")
    +128  interface ClientHintsConfiguration {
    +129    /** Sensible defaults for client hints. */
    +130    ClientHintsConfiguration DEFAULTS = new ClientHintsConfiguration() {};
    +131
    +132    /** Whether to enable support for client hints. */
    +133    @Bindable("enabled") default Boolean enabled() {
    +134      return true;
    +135    }
    +136
    +137    /** Return the set of hints supported by the server. */
    +138    @Bindable("hints") default ImmutableSet<ClientHint> hints() {
    +139      return Sets.immutableEnumSet(
    +140          ClientHint.ECT,
    +141          ClientHint.RTT,
    +142          ClientHint.DPR,
    +143          ClientHint.WIDTH,
    +144          ClientHint.VIEWPORT_WIDTH,
    +145          ClientHint.DOWNLINK);
    +146    }
    +147
    +148    /** Client Hints configuration time-to-live value. */
    +149    @Bindable("ttl") default Optional<Long> ttl() {
    +150      return Optional.of(7L);
    +151    }
    +152
    +153    /** Client Hints configuration time-to-live unit. Defaults to {@code DAYS}. */
    +154    @Bindable("ttlUnit") default TimeUnit ttlUnit() {
    +155      return TimeUnit.DAYS;
    +156    }
    +157  }
    +158
    +159  /** Describes settings regarding {@code Vary} headers for dynamic content. */
    +160  @ConfigurationProperties("gust.serving.vary")
    +161  interface DynamicVarianceConfiguration {
    +162    /** Sensible defaults for dynamic page variance. */
    +163    DynamicVarianceConfiguration DEFAULTS = new DynamicVarianceConfiguration() {};
    +164
    +165    /** Whether to enable {@code Vary} headers for dynamically-served content. */
    +166    @Bindable("enabled") default Boolean enabled() {
    +167      return true;
    +168    }
    +169
    +170    /** Whether to indicate response variance by {@code Accept}. */
    +171    @Bindable("accept") default Boolean accept() {
    +172      return true;
    +173    }
    +174
    +175    /** Whether to indicate response variance by {@code Accept-Charset}. */
    +176    @Bindable("charset") default Boolean charset() {
    +177      return false;
    +178    }
    +179
    +180    /** Whether to indicate response variance by {@code Accept-Encoding}. */
    +181    @Bindable("encoding") default Boolean encoding() {
    +182      return true;
    +183    }
    +184
    +185    /** Whether to indicate response variance by {@code Accept-Language}. */
    +186    @Bindable("language") default Boolean language() {
    +187      return false;
    +188    }
    +189
    +190    /** Whether to indicate response variance by {@code Origin}. */
    +191    @Bindable("origin") default Boolean origin() {
    +192      return false;
    +193    }
    +194  }
    +195
    +196  /** Describes settings regarding {@code X-XSS-Protection} headers for dynamic content. */
    +197  @ConfigurationProperties("gust.serving.xssProtection")
    +198  interface XSSProtectionConfiguration {
    +199    /** Sensible defaults for cross-site scripting protection. */
    +200    XSSProtectionConfiguration DEFAULTS = new XSSProtectionConfiguration() {};
    +201
    +202    /** Whether to enable old-style XSS protection. */
    +203    @Bindable("enabled") default Boolean enabled() {
    +204      return true;
    +205    }
    +206
    +207    /** Whether to specify XSS protection as active. */
    +208    @Bindable("filter") default Boolean filter() {
    +209      return true;
    +210    }
    +211
    +212    /** Whether to add the {@code block} flag. */
    +213    @Bindable("block") default Boolean block() {
    +214      return true;
    +215    }
    +216  }
    +217}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/DynamicServingConfiguration.html b/docs/java/src-html/gust/backend/DynamicServingConfiguration.html new file mode 100644 index 000000000..06a991b78 --- /dev/null +++ b/docs/java/src-html/gust/backend/DynamicServingConfiguration.html @@ -0,0 +1,291 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015import com.google.common.collect.ImmutableSet;
    +016import com.google.common.collect.ImmutableSortedSet;
    +017import com.google.common.collect.Sets;
    +018import io.micronaut.context.annotation.ConfigurationProperties;
    +019import io.micronaut.core.bind.annotation.Bindable;
    +020import tools.elide.page.Context.ClientHint;
    +021import tools.elide.page.Context.FramingPolicy;
    +022import tools.elide.page.Context.ReferrerPolicy;
    +023
    +024import java.util.*;
    +025import java.util.concurrent.TimeUnit;
    +026
    +027
    +028/** Supplies configuration structure for dynamically-served app pages through Gust. */
    +029@ConfigurationProperties("gust.serving")
    +030public interface DynamicServingConfiguration {
    +031  /** Set of sensible defaults for Gust dynamic serving. */
    +032  DynamicServingConfiguration DEFAULTS = new DynamicServingConfiguration() {};
    +033
    +034  /** Value to set for {@code Content-Language}. May be overridden by context. */
    +035  @Bindable("language") default Optional<String> language() {
    +036    return Optional.of("en-US");
    +037  }
    +038
    +039  /** {@code ETag} configuration for dynamic content. */
    +040  @Bindable("etags") default DynamicETagsConfiguration etags() {
    +041    return DynamicETagsConfiguration.DEFAULTS;
    +042  }
    +043
    +044  /** {@code Vary} configuration for dynamic content. */
    +045  @Bindable("vary") default DynamicVarianceConfiguration variance() {
    +046    return DynamicVarianceConfiguration.DEFAULTS;
    +047  }
    +048
    +049  /** {@code X-Frame-Options} configuration for dynamic content. */
    +050  @Bindable("framingPolicy") default FramingPolicy framingPolicy() {
    +051    return FramingPolicy.DENY;
    +052  }
    +053
    +054  /** Hostnames to pre-connect to from the browser. */
    +055  @Bindable("preconnect") default List<String> preconnect() {
    +056    return Collections.emptyList();
    +057  }
    +058
    +059  /** Hostnames to pre-load into the browser's DNS. */
    +060  @Bindable("dnsPrefetch") default List<String> dnsPrefetch() {
    +061    return Collections.emptyList();
    +062  }
    +063
    +064  /** Whether to apply {@code nosniff} to {@code X-Content-Type-Options} for dynamic content. */
    +065  @Bindable("noSniff") default Boolean noSniff() {
    +066    return true;
    +067  }
    +068
    +069  /** {@code Feature-Policy} configuration for dynamic content. */
    +070  @Bindable("featurePolicy") default FeaturePolicyConfiguration featurePolicy() {
    +071    return FeaturePolicyConfiguration.DEFAULTS;
    +072  }
    +073
    +074  /** {@code Referrer-Policy} configuration for dynamic content. */
    +075  @Bindable("referrerPolicy") default ReferrerPolicy referrerPolicy() {
    +076    return ReferrerPolicy.STRICT_ORIGIN;
    +077  }
    +078
    +079  /** {@code X-XSS-Protection} configuration for dynamic content. */
    +080  @Bindable("xssProtection") default XSSProtectionConfiguration xssProtection() {
    +081    return XSSProtectionConfiguration.DEFAULTS;
    +082  }
    +083
    +084  /** Settings related to support for Client Hints. */
    +085  @Bindable("clientHints") default ClientHintsConfiguration clientHints() {
    +086    return ClientHintsConfiguration.DEFAULTS;
    +087  }
    +088
    +089  /** Arbitrary headers to add to all responses. */
    +090  @Bindable("additionalHeaders") default Map<String, String> additionalHeaders() {
    +091    return Collections.emptyMap();
    +092  }
    +093
    +094  /** Describes settings regarding {@code ETag} headers for dynamic content. */
    +095  @ConfigurationProperties("gust.serving.etags")
    +096  interface DynamicETagsConfiguration {
    +097    /** Sensible defaults for dynamic etags. */
    +098    DynamicETagsConfiguration DEFAULTS = new DynamicETagsConfiguration() {};
    +099
    +100    /** Whether to enable {@code ETag} headers for dynamically-served content. */
    +101    @Bindable("enabled") default Boolean enabled() {
    +102      return false;
    +103    }
    +104  }
    +105
    +106  /** Describes settings regarding {@code Feature-Policy} headers for dynamic content. */
    +107  @ConfigurationProperties("gust.serving.featurePolicy")
    +108  interface FeaturePolicyConfiguration {
    +109    /** Sensible defaults for {@code Feature-Policy}. */
    +110    FeaturePolicyConfiguration DEFAULTS = new FeaturePolicyConfiguration() {};
    +111
    +112    /** Whether to enable {@code Feature-Policy} headers for dynamically-served content. */
    +113    @Bindable("enabled") default Boolean enabled() {
    +114      return true;
    +115    }
    +116
    +117    /** Specifies the default {@code Feature-Policy} to apply to dynamically-served content. */
    +118    @Bindable("policy") default SortedSet<String> policy() {
    +119      return ImmutableSortedSet.of(
    +120        "document-domain 'none';",
    +121        "sync-xhr 'none';"
    +122      );
    +123    }
    +124  }
    +125
    +126  /** Describes settings related to Client Hints support. */
    +127  @ConfigurationProperties("gust.serving.clientHints")
    +128  interface ClientHintsConfiguration {
    +129    /** Sensible defaults for client hints. */
    +130    ClientHintsConfiguration DEFAULTS = new ClientHintsConfiguration() {};
    +131
    +132    /** Whether to enable support for client hints. */
    +133    @Bindable("enabled") default Boolean enabled() {
    +134      return true;
    +135    }
    +136
    +137    /** Return the set of hints supported by the server. */
    +138    @Bindable("hints") default ImmutableSet<ClientHint> hints() {
    +139      return Sets.immutableEnumSet(
    +140          ClientHint.ECT,
    +141          ClientHint.RTT,
    +142          ClientHint.DPR,
    +143          ClientHint.WIDTH,
    +144          ClientHint.VIEWPORT_WIDTH,
    +145          ClientHint.DOWNLINK);
    +146    }
    +147
    +148    /** Client Hints configuration time-to-live value. */
    +149    @Bindable("ttl") default Optional<Long> ttl() {
    +150      return Optional.of(7L);
    +151    }
    +152
    +153    /** Client Hints configuration time-to-live unit. Defaults to {@code DAYS}. */
    +154    @Bindable("ttlUnit") default TimeUnit ttlUnit() {
    +155      return TimeUnit.DAYS;
    +156    }
    +157  }
    +158
    +159  /** Describes settings regarding {@code Vary} headers for dynamic content. */
    +160  @ConfigurationProperties("gust.serving.vary")
    +161  interface DynamicVarianceConfiguration {
    +162    /** Sensible defaults for dynamic page variance. */
    +163    DynamicVarianceConfiguration DEFAULTS = new DynamicVarianceConfiguration() {};
    +164
    +165    /** Whether to enable {@code Vary} headers for dynamically-served content. */
    +166    @Bindable("enabled") default Boolean enabled() {
    +167      return true;
    +168    }
    +169
    +170    /** Whether to indicate response variance by {@code Accept}. */
    +171    @Bindable("accept") default Boolean accept() {
    +172      return true;
    +173    }
    +174
    +175    /** Whether to indicate response variance by {@code Accept-Charset}. */
    +176    @Bindable("charset") default Boolean charset() {
    +177      return false;
    +178    }
    +179
    +180    /** Whether to indicate response variance by {@code Accept-Encoding}. */
    +181    @Bindable("encoding") default Boolean encoding() {
    +182      return true;
    +183    }
    +184
    +185    /** Whether to indicate response variance by {@code Accept-Language}. */
    +186    @Bindable("language") default Boolean language() {
    +187      return false;
    +188    }
    +189
    +190    /** Whether to indicate response variance by {@code Origin}. */
    +191    @Bindable("origin") default Boolean origin() {
    +192      return false;
    +193    }
    +194  }
    +195
    +196  /** Describes settings regarding {@code X-XSS-Protection} headers for dynamic content. */
    +197  @ConfigurationProperties("gust.serving.xssProtection")
    +198  interface XSSProtectionConfiguration {
    +199    /** Sensible defaults for cross-site scripting protection. */
    +200    XSSProtectionConfiguration DEFAULTS = new XSSProtectionConfiguration() {};
    +201
    +202    /** Whether to enable old-style XSS protection. */
    +203    @Bindable("enabled") default Boolean enabled() {
    +204      return true;
    +205    }
    +206
    +207    /** Whether to specify XSS protection as active. */
    +208    @Bindable("filter") default Boolean filter() {
    +209      return true;
    +210    }
    +211
    +212    /** Whether to add the {@code block} flag. */
    +213    @Bindable("block") default Boolean block() {
    +214      return true;
    +215    }
    +216  }
    +217}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/PageContext.html b/docs/java/src-html/gust/backend/PageContext.html new file mode 100644 index 000000000..bb9297176 --- /dev/null +++ b/docs/java/src-html/gust/backend/PageContext.html @@ -0,0 +1,496 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015import com.google.common.collect.ImmutableMap;
    +016import com.google.template.soy.msgs.SoyMsgBundle;
    +017import io.micronaut.views.soy.SoyContext;
    +018import io.micronaut.views.soy.SoyNamingMapProvider;
    +019import tools.elide.page.Context;
    +020
    +021import javax.annotation.Nonnull;
    +022import javax.annotation.Nullable;
    +023import javax.annotation.concurrent.Immutable;
    +024import java.io.File;
    +025import java.io.IOException;
    +026import java.net.URL;
    +027import java.util.Collections;
    +028import java.util.Map;
    +029import java.util.Optional;
    +030import java.util.function.Predicate;
    +031
    +032
    +033/**
    +034 * Supplies page context to a Micronaut/Soy render routine, based on the context proto provided/filled out by a given
    +035 * server-side controller.
    +036 *
    +037 * <p>Because this flow occurs in two stages (i.e. building or calculating context, then, subsequently, rendering
    +038 * context), the logic here is implemented to be entirely immutable, and so this object should be used in a transitory
    +039 * way to mediate between a single Soy routine and the context attached to it.</p>
    +040 */
    +041@Immutable
    +042@SuppressWarnings({"unused", "WeakerAccess"})
    +043public final class PageContext implements PageRender {
    +044  /** Shared singleton instance of an empty page context. */
    +045  private static final PageContext _EMPTY = new PageContext(
    +046    Context.getDefaultInstance(),
    +047    null,
    +048    null,
    +049    null,
    +050    null,
    +051    null);
    +052
    +053  /** Raw context. */
    +054  private final @Nonnull SoyContext rawContext;
    +055
    +056  /** Proto-based context. */
    +057  private final @Nonnull Context protoContext;
    +058
    +059  /**
    +060   * Private constructor for proto-based page context with an option for additional regular <pre>@param</pre> props, or
    +061   * additional <pre>@inject</pre> values, and/or an override naming map provider.
    +062   *
    +063   * @param proto Context proto to inject with this render operation.
    +064   * @param context Map of page context information.
    +065   * @param injected Additional injected properties to apply.
    +066   * @param namingMapProvider Style rewrite naming provider to apply/override (if enabled).
    +067   * @param i18n Internationalization context to apply.
    +068   * @param delegatePredicate Predicate for selecting a Soy delegate package, if applicable.
    +069   */
    +070  private PageContext(@Nullable Context proto,
    +071                      @Nullable Map<String, Object> context,
    +072                      @Nullable Map<String, Object> injected,
    +073                      @Nullable SoyNamingMapProvider namingMapProvider,
    +074                      @Nullable SoyContext.SoyI18NContext i18n,
    +075                      @Nullable Predicate<String> delegatePredicate) {
    +076    this.protoContext = proto != null ? proto : Context.getDefaultInstance();
    +077    this.rawContext = SoyContext.fromMap(
    +078      context != null ? context : Collections.emptyMap(),
    +079      Optional.ofNullable(injected),
    +080      Optional.ofNullable(namingMapProvider),
    +081      Optional.ofNullable(i18n),
    +082      Optional.ofNullable(delegatePredicate));
    +083  }
    +084
    +085  // -- Factories: Maps -- //
    +086
    +087  /**
    +088   * Factory to create an empty page context. Under the hood, this uses a static singleton representing an empty context
    +089   * to avoid re-creating the object repeatedly.
    +090   *
    +091   * @return Pre-fabricated empty page context.
    +092   */
    +093  public static PageContext empty() {
    +094    return PageContext._EMPTY;
    +095  }
    +096
    +097  /**
    +098   * Factory to create a page context object from a regular Java map, of string context properties to values of any
    +099   * object type. Under the hood, this is processed and converted/wrapped into Soy values.
    +100   *
    +101   * @param context Context with which to render a Soy template.
    +102   * @return Instance of page context, enclosing the provided context.
    +103   */
    +104  public static PageContext fromMap(@Nonnull Map<String, Object> context) {
    +105    return new PageContext(null, context, null, null, null, null);
    +106  }
    +107
    +108  /**
    +109   * Factory to create a page context object from a regular Java map, of string context properties to values of any
    +110   * object type. Additionally, this interface allows specification of properties declared via <pre>@inject</pre>. Under
    +111   * the hood, all context is processed and converted/wrapped into Soy values.
    +112   *
    +113   * @param context Context with which to render a Soy template - i.e. regular <pre>@param</pre> declarations.
    +114   * @param injected Injected parameters to provide to the render operation - available via <pre>@inject</pre>.
    +115   * @return Fabricated page context object.
    +116   */
    +117  public static PageContext fromMap(@Nonnull Map<String, Object> context,
    +118                                    @Nonnull Map<String, Object> injected) {
    +119    return new PageContext(null, context, injected, null, null, null);
    +120  }
    +121
    +122  /**
    +123   * Factory to create a page context object from a regular Java map, of string context properties to values of any
    +124   * object type. Additionally, this interface allows specification of properties declared via <pre>@inject</pre>, and
    +125   * also a {@link SoyNamingMapProvider} to override any globally-installed map. Under the hood, all context is
    +126   * processed and converted/wrapped into Soy values.
    +127   *
    +128   * <p>Note that style rewriting must be enabled for the <pre>namingMapProvider</pre> override to take effect.</p>
    +129   *
    +130   * @param context Context with which to render a Soy template - i.e. regular <pre>@param</pre> declarations.
    +131   * @param injected Injected parameters to provide to the render operation - available via <pre>@inject</pre>.
    +132   * @param namingMapProvider Override any globally-installed naming map provider.
    +133   * @return Fabricated page context object.
    +134   */
    +135  public static PageContext fromMap(@Nonnull Map<String, Object> context,
    +136                                    @Nonnull Map<String, Object> injected,
    +137                                    @Nullable SoyNamingMapProvider namingMapProvider) {
    +138    return new PageContext(null, context, injected, namingMapProvider, null, null);
    +139  }
    +140
    +141  /**
    +142   * Factory to create a page context object from a regular Java map, of string context properties to values of any
    +143   * object type. Additionally, this interface allows specification of properties declared via <pre>@inject</pre>, and
    +144   * also a {@link SoyNamingMapProvider} to override any globally-installed map. Under the hood, all context is
    +145   * processed and converted/wrapped into Soy values.
    +146   *
    +147   * <p>Note that style rewriting must be enabled for the <pre>namingMapProvider</pre> override to take effect.</p>
    +148   *
    +149   * <p>If the invoking developer wishes to apply internationalization via an XLIFF message bundle or pre-constructed
    +150   * Soy Messages bundle, they may do so via `i18n`.</p>
    +151   *
    +152   * @param context Context with which to render a Soy template - i.e. regular <pre>@param</pre> declarations.
    +153   * @param injected Injected parameters to provide to the render operation - available via <pre>@inject</pre>.
    +154   * @param namingMapProvider Override any globally-installed naming map provider.
    +155   * @param i18n Internationalization context to apply.
    +156   * @param delegatePredicate Predicate for selecting a delegate package.
    +157   * @return Fabricated page context object.
    +158   */
    +159  public static PageContext fromMap(@Nonnull Map<String, Object> context,
    +160                                    @Nonnull Map<String, Object> injected,
    +161                                    @Nullable SoyNamingMapProvider namingMapProvider,
    +162                                    @Nullable SoyContext.SoyI18NContext i18n,
    +163                                    @Nullable Predicate<String> delegatePredicate) {
    +164    return new PageContext(null, context, injected, namingMapProvider, i18n, delegatePredicate);
    +165  }
    +166
    +167  // -- Factories: Protos -- //
    +168
    +169  /**
    +170   * Factory to create a page context object from a proto message containing structured data, which is injected into the
    +171   * render flow at `context`. Templates may opt-in to receive this value via a parameter declaration such as
    +172   * <pre>@inject context: gust.page.Context</pre>.
    +173   *
    +174   * @param pageContext Protobuf page context.
    +175   * @return Page context object.
    +176   */
    +177  public static @Nonnull PageContext fromProto(@Nonnull Context pageContext) {
    +178    return new PageContext(
    +179      pageContext,
    +180      null,
    +181      null,
    +182      null,
    +183      null,
    +184      null
    +185    );
    +186  }
    +187
    +188  /**
    +189   * Factory to create a page context object from a proto message containing structured data, which is injected into the
    +190   * render flow at `context`. Templates may opt-in to receive this value via a parameter declaration such as
    +191   * <pre>@inject context: gust.page.Context</pre>.
    +192   *
    +193   * <p>This method offers the additional ability to specify <pre>props</pre>, which should correspond with any
    +194   * <pre>@param</pre> declarations for the subject template to be rendered.</p>
    +195   *
    +196   * @param pageContext Protobuf page context.
    +197   * @param props Parameters to render the template with.
    +198   * @return Page context object.
    +199   */
    +200  public static @Nonnull PageContext fromProto(@Nonnull Context pageContext,
    +201                                               @Nonnull Map<String, Object> props) {
    +202    return new PageContext(
    +203      pageContext,
    +204      props,
    +205      null,
    +206      null,
    +207      null,
    +208      null
    +209    );
    +210  }
    +211
    +212  /**
    +213   * Factory to create a page context object from a proto message containing structured data, which is injected into the
    +214   * render flow at `context`. Templates may opt-in to receive this value via a parameter declaration such as
    +215   * <pre>@inject context: gust.page.Context</pre>.
    +216   *
    +217   * <p>This method offers the additional ability to specify <pre>props</pre> and <pre>injected</pre> values. Props
    +218   * should correspond with any <pre>@param</pre> declarations for the subject template to be rendered. Injected values
    +219   * are opted-into with <pre>@inject</pre>, and are overlaid on any existing injected values (may not override
    +220   * <pre>context</pre>).</p>
    +221   *
    +222   * @param pageContext Protobuf page context.
    +223   * @param props Parameters to render the template with.
    +224   * @param injected Additional injected values (may not contain a value at key <pre>context</pre>).
    +225   * @return Page context object.
    +226   */
    +227  public static @Nonnull PageContext fromProto(@Nonnull Context pageContext,
    +228                                               @Nonnull Map<String, Object> props,
    +229                                               @Nonnull Map<String, Object> injected) {
    +230    return new PageContext(
    +231      pageContext,
    +232      props,
    +233      injected,
    +234      null,
    +235      null,
    +236      null
    +237    );
    +238  }
    +239
    +240  /**
    +241   * Factory to create a page context object from a proto message containing structured data, which is injected into the
    +242   * render flow at `context`. Templates may opt-in to receive this value via a parameter declaration such as
    +243   * <pre>@inject context: gust.page.Context</pre>.
    +244   *
    +245   * <p>This method offers the additional ability to specify <pre>props</pre> and <pre>injected</pre> values. Props
    +246   * should correspond with any <pre>@param</pre> declarations for the subject template to be rendered. Injected values
    +247   * are opted-into with <pre>@inject</pre>, and are overlaid on any existing injected values (may not override
    +248   * <pre>context</pre>).</p>
    +249   *
    +250   * <p>If desired, an invoking developer may wish to specify a <pre>namingMapProvider</pre>. To have any effect, style
    +251   * renaming must be active in application config. The naming map provider passed here overrides any globally-installed
    +252   * style renaming map provider.</p>
    +253   *
    +254   * @param pageContext Protobuf page context.
    +255   * @param props Parameters to render the template with.
    +256   * @param injected Additional injected values (may not contain a value at key <pre>context</pre>).
    +257   * @param namingMapProvider Naming map provider to override any globally-installed provider with, if enabled.
    +258   * @return Page context object.
    +259   */
    +260  public static @Nonnull PageContext fromProto(@Nonnull Context pageContext,
    +261                                               @Nonnull Map<String, Object> props,
    +262                                               @Nonnull Map<String, Object> injected,
    +263                                               @Nullable SoyNamingMapProvider namingMapProvider) {
    +264    return new PageContext(
    +265      pageContext,
    +266      props,
    +267      injected,
    +268      namingMapProvider,
    +269      null,
    +270      null
    +271    );
    +272  }
    +273
    +274  /**
    +275   * Factory to create a page context object from a proto message containing structured data, which is injected into the
    +276   * render flow at `context`. Templates may opt-in to receive this value via a parameter declaration such as
    +277   * <pre>@inject context: gust.page.Context</pre>.
    +278   *
    +279   * <p>This method offers the additional ability to specify <pre>props</pre> and <pre>injected</pre> values. Props
    +280   * should correspond with any <pre>@param</pre> declarations for the subject template to be rendered. Injected values
    +281   * are opted-into with <pre>@inject</pre>, and are overlaid on any existing injected values (may not override
    +282   * <pre>context</pre>).</p>
    +283   *
    +284   * <p>If desired, an invoking developer may wish to specify a <pre>namingMapProvider</pre>. To have any effect, style
    +285   * renaming must be active in application config. The naming map provider passed here overrides any globally-installed
    +286   * style renaming map provider.</p>
    +287   *
    +288   * <p>In addition to the other method variant above, this method allows internationalization via the `i18n` parameter,
    +289   * which accepts a {@link SoyContext.SoyI18NContext} instance. A messages file, resource, or pre-constructed Soy
    +290   * Message Bundle may be passed for translation during render.</p>
    +291   *
    +292   * @param pageContext Protobuf page context.
    +293   * @param props Parameters to render the template with.
    +294   * @param injected Additional injected values (may not contain a value at key <pre>context</pre>).
    +295   * @param namingMapProvider Naming map provider to override any globally-installed provider with, if enabled.
    +296   * @param i18n Internationalization context to apply.
    +297   * @param delegatePredicate Predicate for selecting a Soy delegate package, as applicable.
    +298   * @return Page context object.
    +299   */
    +300  public static @Nonnull PageContext fromProto(@Nonnull Context pageContext,
    +301                                               @Nonnull Map<String, Object> props,
    +302                                               @Nonnull Map<String, Object> injected,
    +303                                               @Nullable SoyNamingMapProvider namingMapProvider,
    +304                                               @Nullable SoyContext.SoyI18NContext i18n,
    +305                                               @Nullable Predicate<String> delegatePredicate) {
    +306    return new PageContext(
    +307      pageContext,
    +308      props,
    +309      injected,
    +310      namingMapProvider,
    +311      i18n,
    +312      delegatePredicate
    +313    );
    +314  }
    +315
    +316  // -- Interface: Soy Proto Context -- //
    +317
    +318  /**
    +319   * Retrieve serializable server-side-rendered page context, which should be assigned to the render flow bound to this
    +320   * context mediator.
    +321   *
    +322   * @return Server-side rendered page context.
    +323   */
    +324  @Nonnull @Override
    +325  public Context getPageContext() {
    +326    return this.protoContext;
    +327  }
    +328
    +329  // -- Interface: Soy Context Mediation -- //
    +330
    +331  /**
    +332   * Retrieve properties which should be made available via regular, declared `@param` statements.
    +333   *
    +334   * @return Map of regular template properties.
    +335   */
    +336  @Nonnull @Override
    +337  public Map<String, Object> getProperties() {
    +338    return rawContext.getProperties();
    +339  }
    +340
    +341  /**
    +342   * Retrieve properties and values that should be made available via `@inject`.
    +343   *
    +344   * @param framework Framework-injected properties.
    +345   * @return Map of injected properties and their values.
    +346   */
    +347  @Nonnull @Override
    +348  public Map<String, Object> getInjectedProperties(@Nonnull Map<String, Object> framework) {
    +349    return ImmutableMap
    +350      .<String, Object>builder()
    +351      .put(PAGE_CONTEXT_IJ_NAME, protoContext)
    +352      .putAll(rawContext.getInjectedProperties(framework))
    +353      .build();
    +354  }
    +355
    +356  /**
    +357   * Specify a Soy renaming map which overrides the globally-installed map, if any. Renaming must still be activated via
    +358   * config, or manually, for the return value of this method to have any effect.
    +359   *
    +360   * @return {@link SoyNamingMapProvider} that should be used for this render routine.
    +361   */
    +362  @Nonnull @Override
    +363  public Optional<SoyNamingMapProvider> overrideNamingMap() {
    +364    return rawContext.overrideNamingMap();
    +365  }
    +366
    +367  /**
    +368   * Specify whether to enable translation via Soy message bundles. The default implementation of this method simply
    +369   * checks whether a translation file has been set by the controller.
    +370   *
    +371   * @return Whether to enable translation.
    +372   */
    +373  @Override
    +374  public boolean translate() {
    +375    return rawContext.translate();
    +376  }
    +377
    +378  /**
    +379   * Return the file that should be loaded and interpreted to perform translation when rendering this page. If no file,
    +380   * resource, or bundle is provided, no translation occurs.
    +381   *
    +382   * @return File to apply for translations.
    +383   */
    +384  @Nonnull @Override
    +385  public Optional<File> messagesFile() {
    +386    return rawContext.messagesFile();
    +387  }
    +388
    +389  /**
    +390   * Return the URL to the resource that should be loaded and interpreted to perform translation when rendering this
    +391   * page. If no resource, file, or bundle is provided, no translation occurs.
    +392   *
    +393   * @return Resource to apply for translations.
    +394   */
    +395  @Nonnull @Override
    +396  public Optional<URL> messagesResource() {
    +397    return rawContext.messagesResource();
    +398  }
    +399
    +400  /**
    +401   * Return the pre-fabricated Soy message bundle, or the interpreted Soy message bundle based on the installed messages
    +402   * file or resource URL.
    +403   *
    +404   * @return Pre-fabricated or interpreted message bundle.
    +405   * @throws IOException If the bundle failed to load.
    +406   */
    +407  @Nonnull @Override
    +408  public Optional<SoyMsgBundle> messageBundle() throws IOException {
    +409    return rawContext.messageBundle();
    +410  }
    +411
    +412  /**
    +413   * Return the delegate package that should be active when rendering the desired Soy output, if applicable. If no
    +414   * delegate package should be applied, an empty {@link Optional} is returned.
    +415   *
    +416   * @return Predicate specifying the delegate package, or {@link Optional#empty()}.
    +417   */
    +418  @Nonnull @Override
    +419  public Optional<Predicate<String>> delegatePackage() {
    +420    return rawContext.delegatePackage();
    +421  }
    +422}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/PageContextManager.html b/docs/java/src-html/gust/backend/PageContextManager.html new file mode 100644 index 000000000..2068573e8 --- /dev/null +++ b/docs/java/src-html/gust/backend/PageContextManager.html @@ -0,0 +1,1959 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015import com.google.common.annotations.VisibleForTesting;
    +016import com.google.common.base.Joiner;
    +017import com.google.common.collect.ImmutableSortedSet;
    +018import com.google.common.collect.Sets;
    +019import com.google.common.html.types.TrustedResourceUrlProto;
    +020import com.google.errorprone.annotations.CanIgnoreReturnValue;
    +021import com.google.protobuf.ByteString;
    +022import com.google.template.soy.msgs.SoyMsgBundle;
    +023import com.google.template.soy.data.SanitizedContent;
    +024import com.google.template.soy.data.UnsafeSanitizedContentOrdainer;
    +025import gust.backend.runtime.AssetManager;
    +026import gust.backend.runtime.AssetManager.ManagedAsset;
    +027import gust.backend.runtime.Logging;
    +028import gust.util.Hex;
    +029import io.micronaut.http.*;
    +030import io.micronaut.http.context.ServerRequestContext;
    +031import io.micronaut.runtime.http.scope.RequestScope;
    +032import io.micronaut.views.soy.SoyContext;
    +033import io.micronaut.views.soy.SoyNamingMapProvider;
    +034import org.slf4j.Logger;
    +035import tools.elide.assets.AssetBundle.StyleBundle.StyleAsset;
    +036import tools.elide.assets.AssetBundle.ScriptBundle.ScriptAsset;
    +037import tools.elide.page.Context;
    +038import tools.elide.page.Context.ClientHint;
    +039import tools.elide.page.Context.FramingPolicy;
    +040import tools.elide.page.Context.ClientHints;
    +041import tools.elide.page.Context.ConnectionHint;
    +042import tools.elide.page.Context.ReferrerPolicy;
    +043import tools.elide.page.Context.Styles.Stylesheet;
    +044import tools.elide.page.Context.Scripts.JavaScript;
    +045
    +046import javax.annotation.Nonnull;
    +047import javax.annotation.Nullable;
    +048import java.io.Closeable;
    +049import java.io.File;
    +050import java.io.IOException;
    +051import java.net.URI;
    +052import java.net.URL;
    +053import java.security.MessageDigest;
    +054import java.util.*;
    +055import java.util.concurrent.ConcurrentMap;
    +056import java.util.concurrent.ConcurrentSkipListMap;
    +057import java.util.concurrent.ConcurrentSkipListSet;
    +058import java.util.concurrent.atomic.AtomicBoolean;
    +059import java.util.function.Consumer;
    +060import java.util.function.Predicate;
    +061import java.util.stream.Collectors;
    +062
    +063import static java.lang.String.format;
    +064
    +065
    +066/**
    +067 * Manages the process of filling out {@link PageContext} objects before they are sealed, and delivered to Closure/Soy
    +068 * to be reduced and rendered into content.
    +069 *
    +070 * <p>This object may be used from controllers via dependency injection, or used via the base controller classes
    +071 * provided as part of the framework.</p>
    +072 */
    +073@RequestScope
    +074@SuppressWarnings("unused")
    +075public class PageContextManager implements Closeable, AutoCloseable, PageRender {
    +076  private static final Logger logging = Logging.logger(PageContextManager.class);
    +077
    +078  private static final int ETAG_LENGTH = 6;
    +079
    +080  private static final String DPR_HEADER = "DPR";
    +081  private static final String ECT_HEADER = "ECT";
    +082  private static final String RTT_HEADER = "RTT";
    +083  private static final String LINK_HEADER = "Link";
    +084  private static final String MEMORY_HEADER = "Device-Memory";
    +085  private static final String DOWNLINK_HEADER = "Downlink";
    +086  private static final String VIEWPORT_WIDTH_HEADER = "Viewport-Width";
    +087  private static final String ACCEPT_CH_HEADER = "Accept-CH";
    +088  private static final String ACCEPT_CH_LIFETIME_HEADER = "Accept-CH-Lifetime";
    +089  private static final String FEATURE_POLICY_HEADER = "Feature-Policy";
    +090  private static final String X_FRAME_OPTIONS_HEADER = "X-Frame-Options";
    +091  private static final String X_CONTENT_TYPE_OPTIONS_HEADER = "X-Content-Type-Options";
    +092  private static final String X_XSS_PROTECTION_HEADER = "X-XSS-Protection";
    +093  private static final String LINK_DNS_PREFETCH_TOKEN = "dns-prefetch";
    +094  private static final String LINK_PRECONNECT_TOKEN = "preconnect";
    +095  private static final String REFERRER_POLICY_HEADER = "Referrer-Policy";
    +096  private static final ConnectionHint DEFAULT_ECT = ConnectionHint.FAST;
    +097
    +098  private static final String CDN_PREFIX_IJ_PROP = "cdn_prefix";
    +099
    +100  private static final String LIVE_RELOAD_TARGET_PROP = "live_reload_url";
    +101  private static final String LIVE_RELOAD_SWITCH_PROP = "live_reload_enabled";
    +102  private static final String LIVE_RELOAD_JS = "http://localhost:35729/livereload.js";
    +103
    +104  /** Access to the asset manager. */
    +105  private final @Nonnull AssetManager assetManager;
    +106
    +107  /** Page context builder. */
    +108  private final @Nonnull Context.Builder context;
    +109
    +110  /** Content type to serve from this endpoint. */
    +111  private @Nonnull String contentTypeValue = MediaType.TEXT_HTML;
    +112
    +113  /** HTTP request bound to this flow. */
    +114  @SuppressWarnings("rawtypes")
    +115  private final @Nonnull HttpRequest request;
    +116
    +117  /** Set of interpreted/immutable client hints. */
    +118  private final @Nonnull ClientHints hints;
    +119
    +120  /** Main properties to apply during Soy render. */
    +121  private final @Nonnull ConcurrentMap<String, Object> props;
    +122
    +123  /** Additional injected values to apply during Soy render. */
    +124  private final @Nonnull ConcurrentMap<String, Object> injected;
    +125
    +126  /** Set of headers that cause this response flow to vary. */
    +127  private final @Nonnull SortedSet<String> varySegments;
    +128
    +129  /** Naming map provider to apply during the Soy render flow. */
    +130  private @Nonnull Optional<SoyNamingMapProvider> namingMapProvider;
    +131
    +132  /** Predicate for selecting a Soy delegate package, if applicable. */
    +133  private @Nonnull Optional<Predicate<String>> delegatePredicate;
    +134
    +135  /** Translation file to apply during the Soy render flow. */
    +136  private @Nonnull Optional<File> translationsFile = Optional.empty();
    +137
    +138  /** Translation resource to apply during the Soy render flow. */
    +139  private @Nonnull Optional<URL> translationsResource = Optional.empty();
    +140
    +141  /** Pre-constructed message bundle to apply during the Soy render flow. */
    +142  private @Nonnull Optional<SoyMsgBundle> messageBundle = Optional.empty();
    +143
    +144  /** Built context: assembled when we "close" the page context manager. */
    +145  private @Nullable PageContext builtContext = null;
    +146
    +147  /** Whether to enable live-reload mode or not. */
    +148  private final boolean liveReload;
    +149
    +150  /** Whether we have closed context building or not. */
    +151  private @Nonnull final AtomicBoolean closed = new AtomicBoolean(false);
    +152
    +153  /** CDN prefix to apply to this HTTP cycle. */
    +154  private @Nonnull volatile Optional<String> cdnPrefix = Optional.empty();
    +155
    +156  /**
    +157   * Constructor for page context.
    +158   *
    +159   * @param assetManager Manager for static embedded application assets.
    +160   * @param namingMapProvider Style renaming map provider, if applicable.
    +161   * @throws IllegalStateException If an attempt is made to construct context outside of a server-side HTTP flow.
    +162   */
    +163  PageContextManager(@Nonnull AssetManager assetManager,
    +164                     @Nonnull Optional<SoyNamingMapProvider> namingMapProvider) {
    +165    if (logging.isDebugEnabled()) logging.debug("Initializing `PageContextManager`.");
    +166    //noinspection SimplifyOptionalCallChains
    +167    if (!ServerRequestContext.currentRequest().isPresent())
    +168      throw new IllegalStateException("Cannot construct `PageContext` outside of a server-side HTTP flow.");
    +169    this.request = ServerRequestContext.currentRequest().get();
    +170    this.context = interpretRequest(request);
    +171    this.hints = this.context.getHintsBuilder().build();
    +172    this.props = new ConcurrentSkipListMap<>();
    +173    this.injected = new ConcurrentSkipListMap<>();
    +174    this.varySegments = new ConcurrentSkipListSet<>();
    +175    this.namingMapProvider = namingMapProvider;
    +176    this.assetManager = assetManager;
    +177
    +178    // inject live-reload state
    +179    this.liveReload = "enabled".equals(System.getProperty("LIVE_RELOAD"));
    +180    this.injected.put(LIVE_RELOAD_SWITCH_PROP, this.liveReload);
    +181    if (this.liveReload) {
    +182      logging.info("Live-reload is currently ENABLED.");
    +183      this.injected.put(LIVE_RELOAD_TARGET_PROP, UnsafeSanitizedContentOrdainer.ordainAsSafe(
    +184        LIVE_RELOAD_JS,
    +185        SanitizedContent.ContentKind.TRUSTED_RESOURCE_URI)
    +186        .toTrustedResourceUrlProto());
    +187    }
    +188  }
    +189
    +190  /**
    +191   * Interpret the provided HTTP request, reading any properties and hints that are specified. For instance, this is
    +192   * where we can load/interpret <i>Client Hints</i> provided by the browser, as applicable.
    +193   *
    +194   * @param request HTTP request to interpret/read.
    +195   * @return Context builder to use, factoring in the provided request.
    +196   */
    +197  @VisibleForTesting
    +198  @SuppressWarnings({"WeakerAccess", "rawtypes"})
    +199  static @Nonnull Context.Builder interpretRequest(@Nonnull HttpRequest request) {
    +200    Context.Builder builder = Context.newBuilder();
    +201    interpretHints(request, builder);
    +202    return builder;
    +203  }
    +204
    +205  /**
    +206   * Interpret <i>Client Hints</i> headers from the provided HTTP request, and apply them to the provided context.
    +207   *
    +208   * @param request HTTP request to interpret hints from.
    +209   * @param builder Builder to apply the hints to, if found.
    +210   */
    +211  @VisibleForTesting
    +212  @SuppressWarnings({"WeakerAccess", "rawtypes"})
    +213  static void interpretHints(@Nonnull HttpRequest request, @Nonnull Context.Builder builder) {
    +214    HttpHeaders headers = request.getHeaders();
    +215    Context.ClientHints.Builder hints = builder.getHintsBuilder();
    +216
    +217    checkHint(ClientHint.DPR, headers, hints, (value) -> hints.setDevicePixelRatio(Integer.parseUnsignedInt(value)));
    +218    checkHint(ClientHint.ECT, headers, hints, (value) ->
    +219      hints.setEffectiveConnectionType(connectionHintForECT(value).orElse(DEFAULT_ECT)));
    +220    checkHint(ClientHint.RTT, headers, hints, (value) -> hints.setRoundTripTime(Integer.parseUnsignedInt(value)));
    +221    checkHint(ClientHint.DOWNLINK, headers, hints, (value) -> hints.setDownlink(Float.parseFloat(value)));
    +222    checkHint(ClientHint.DEVICE_MEMORY, headers, hints, (value) -> hints.setDeviceMemory(Float.parseFloat(value)));
    +223    checkHint(ClientHint.SAVE_DATA, headers, hints, (value) -> hints.setSaveData(true));
    +224    checkHint(ClientHint.WIDTH, headers, hints, (value) -> hints.setWidth(Integer.parseUnsignedInt(value)));
    +225    checkHint(ClientHint.VIEWPORT_WIDTH, headers, hints,
    +226      (value) -> hints.setViewportWidth(Integer.parseUnsignedInt(value)));
    +227  }
    +228
    +229  /**
    +230   * Check the provided set of HTTP headers for the specified client hint. If a value is found, dispatch the provided
    +231   * function with the hint, and the discovered header value.
    +232   *
    +233   * @param hint Client hint to check for in the specified headers.
    +234   * @param headers HTTP request headers to check.
    +235   * @param callable Callable to dispatch if the header is found.
    +236   */
    +237  @VisibleForTesting
    +238  @SuppressWarnings("WeakerAccess")
    +239  static void checkHint(@Nonnull ClientHint hint,
    +240                        @Nonnull HttpHeaders headers,
    +241                        @Nonnull ClientHints.Builder hints,
    +242                        Consumer<String> callable) {
    +243    String headerName = clientHintForEnum(hint);
    +244    if (headers.contains(headerName)) {
    +245      String headerValue = headers.get(headerName);
    +246      if (headerValue != null && !headerValue.isEmpty()) {
    +247        try {
    +248          hints.addIndicated(hint);
    +249          callable.accept(headerValue);
    +250        } catch (IllegalArgumentException iae) {
    +251          logging.warn(format("Failed to parse client hint '%s': %s.", hint.name(), iae.getMessage()));
    +252        }
    +253      }
    +254    }
    +255  }
    +256
    +257  /**
    +258   * Resolve an enumerated connection hint for the provided connection hint name, which was presumably found in a
    +259   * <i>Client Hints</i> Effective Connection Type header value.
    +260   *
    +261   * @param value Value to interpret.
    +262   * @return Connection hint type, resolved.
    +263   */
    +264  @VisibleForTesting
    +265  @SuppressWarnings("WeakerAccess")
    +266  static @Nonnull Optional<ConnectionHint> connectionHintForECT(@Nonnull String value) {
    +267    switch (value.toLowerCase().trim()) {
    +268      case "slow_2g": return Optional.of(ConnectionHint.SLOW_TWO);
    +269      case "2g": return Optional.of(ConnectionHint.SLOW);
    +270      case "3g": return Optional.of(ConnectionHint.TYPICAL);
    +271      case "4g": return Optional.of(ConnectionHint.FAST);
    +272      default: return Optional.empty();
    +273    }
    +274  }
    +275
    +276  /**
    +277   * Produce a string token for the provided client hint.
    +278   *
    +279   * @param hint Enumerated hint type.
    +280   * @return String token matching the hint.
    +281   */
    +282  @VisibleForTesting
    +283  @SuppressWarnings("WeakerAccess")
    +284  static @Nonnull String clientHintForEnum(@Nonnull ClientHint hint) {
    +285    switch (hint) {
    +286      case DPR: return "DPR";
    +287      case ECT: return "ECT";
    +288      case RTT: return "RTT";
    +289      case DOWNLINK: return "Downlink";
    +290      case DEVICE_MEMORY: return "Device-Memory";
    +291      case SAVE_DATA: return "Save-Data";
    +292      case WIDTH: return "Width";
    +293      case VIEWPORT_WIDTH: return "Viewport-Width";
    +294      default:
    +295        throw new IllegalStateException(format("Unrecognized client hint type: '%s'.", hint.name()));
    +296    }
    +297  }
    +298
    +299  /**
    +300   * Produce a token for the specified {@code Referrer-Policy} selection.
    +301   *
    +302   * @param policy Policy to produce a token for.
    +303   * @return String token.
    +304   */
    +305  @VisibleForTesting
    +306  @SuppressWarnings("WeakerAccess")
    +307  static @Nonnull Optional<String> tokenForReferrerPolicy(@Nonnull ReferrerPolicy policy) {
    +308    switch (policy) {
    +309      case NO_REFERRER: return Optional.of("no-referrer");
    +310      case NO_REFERRER_WHEN_DOWNGRADE: return Optional.of("no-referrer-when-downgrade");
    +311      case ORIGIN: return Optional.of("origin");
    +312      case ORIGIN_WHEN_CROSS_ORIGIN: return Optional.of("origin-when-cross-origin");
    +313      case SAME: return Optional.of("same-origin");
    +314      case STRICT_ORIGIN: return Optional.of("strict-origin");
    +315      case STRICT_ORIGIN_WHEN_CROSS_ORIGIN: return Optional.of("strict-origin-when-cross-origin");
    +316      case UNSAFE_URL: return Optional.of("unsafe-url");
    +317      default: return Optional.empty();
    +318    }
    +319  }
    +320
    +321  /**
    +322   * Produce a token for the specified {@code X-Frame-Options} policy.
    +323   *
    +324   * @param policy Policy to produce a token for.
    +325   * @return String token.
    +326   */
    +327  @VisibleForTesting
    +328  @SuppressWarnings("WeakerAccess")
    +329  static @Nonnull Optional<String> tokenForFramingPolicy(@Nonnull FramingPolicy policy) {
    +330    switch (policy) {
    +331      case SAMEORIGIN: return Optional.of("SAMEORIGIN");
    +332      case DENY: return Optional.of("DENY");
    +333      default: return Optional.empty();
    +334    }
    +335  }
    +336
    +337  /**
    +338   * Format a hostname for DNS pre-fetching by the browser.
    +339   *
    +340   * @param hostname Hostname to pre-fetch.
    +341   * @param relevance Indicates the type of link being specified.
    +342   * @return Formatted {@code Link} header value.
    +343   */
    +344  @VisibleForTesting
    +345  @SuppressWarnings("WeakerAccess")
    +346  static @Nonnull String formatLinkHeader(@Nonnull String hostname, @Nonnull String relevance) {
    +347    return "<" + hostname + ">; rel=" + relevance;
    +348  }
    +349
    +350  /** @return The current page context builder. */
    +351  public @Nonnull Context.Builder getContext() {
    +352    if (this.closed.get())
    +353      throw new IllegalStateException("Cannot access mutable context after closing page manager state.");
    +354    return context;
    +355  }
    +356
    +357  /** {@inheritDoc} */
    +358  @Override
    +359  public @Nonnull <T> MutableHttpResponse<T> finalizeResponse(@Nonnull HttpRequest<?> request,
    +360                                                              @Nonnull MutableHttpResponse<T> soyResponse,
    +361                                                              @Nonnull T body,
    +362                                                              @Nullable MessageDigest digester) {
    +363    MutableHttpResponse<T> response = soyResponse.body(body);
    +364    Optional<Context> pageContext = response.getAttribute("context", Context.class);
    +365    if (pageContext.isPresent()) {
    +366      if (logging.isDebugEnabled())
    +367        logging.debug("Found request context, finalizing headers.");
    +368      @Nonnull Context ctx = pageContext.get();
    +369
    +370      //noinspection ConstantConditions
    +371      if (this.contentTypeValue != null
    +372          && !this.contentTypeValue.isEmpty()
    +373          && !this.contentTypeValue.isBlank()) {
    +374        // set the content type
    +375        soyResponse.contentType(this.contentTypeValue);
    +376      }
    +377
    +378      // process `ETag` first, because if `If-None-Match` processing is enabled, this may kick is out of this response
    +379      // flow entirely.
    +380      if (digester != null) {
    +381        if (ctx.getEtag().hasPreimage()) {
    +382          digester.update(ctx.getEtag().getPreimage().getFingerprint().asReadOnlyByteBuffer().array());
    +383        }
    +384        String contentDigest = Hex.bytesToHex(digester.digest(), ETAG_LENGTH);
    +385        if (!Objects.requireNonNull(contentDigest).isEmpty()) {
    +386          if (request.getHeaders().contains(HttpHeaders.IF_NONE_MATCH)) {
    +387            // we have a potential conditional match
    +388            if (contentDigest.equals(Objects.requireNonNull(request.getHeaders()
    +389                  .get(HttpHeaders.IF_NONE_MATCH))
    +390                .replace("\"", "")
    +391                .replace("W/", ""))) {
    +392              if (logging.isDebugEnabled()) {
    +393                logging.debug(format(
    +394                  "Response matched `If-None-Match` etag value '%s'. Sending 304.", ("W/" + contentDigest)));
    +395              }
    +396
    +397              // drop the body - explicitly truncate so we don't get caught by chunked TE - and reset the status to
    +398              // indicate a conditional request match
    +399              response.body(null);
    +400              response.contentLength(0);
    +401              response.status(HttpStatus.NOT_MODIFIED.getCode());
    +402              return response;
    +403            }
    +404          }
    +405          response.getHeaders().add(HttpHeaders.ETAG, "W/" + "\"" + contentDigest + "\"");
    +406        }
    +407      } else if (logging.isTraceEnabled()) {
    +408        logging.trace("Dynamic ETags are disabled.");
    +409      }
    +410
    +411      // `Accept-CH`
    +412      if (ctx.hasHints() &&  // we must support hints to emit this header
    +413          ctx.getHints().getSupportedCount() > 0 &&  // there must be hint types to send
    +414          (ctx.getHints().getIndicatedCount() == 0 ||  // we should only send if there are no hints from the client, or
    +415           ctx.getHints().getIndicatedList() != ctx.getHints().getSupportedList() ||  // the lists differ in length, or
    +416          !(Sets.difference(  // the provided set of hints from the client doesn't match with the supported set
    +417             ImmutableSortedSet.copyOf(ctx.getHints().getIndicatedList()),
    +418             ImmutableSortedSet.copyOf(ctx.getHints().getSupportedList()))
    +419            .isEmpty()))) {
    +420        SortedSet<String> tokens = ctx.getHints().getSupportedList().stream()
    +421          .map(PageContextManager::clientHintForEnum)
    +422          .collect(Collectors.toCollection(TreeSet::new));
    +423
    +424        String renderedHints = Joiner.on(", ").join(tokens);
    +425        if (logging.isDebugEnabled())
    +426          logging.debug(format("Indicating `Accept-CH`: '%s'.", renderedHints.toLowerCase()));
    +427        response.getHeaders().add(ACCEPT_CH_HEADER, renderedHints.toLowerCase());
    +428
    +429        // since we've appended `Accept-CH`, check for a lifetime
    +430        long lifetime = ctx.getHints().getLifetime();
    +431        if (lifetime > 0) {
    +432          response.getHeaders().add(ACCEPT_CH_LIFETIME_HEADER, String.valueOf(lifetime));
    +433        }
    +434      } else if (logging.isTraceEnabled()) {
    +435        logging.trace("`Accept-CH` not configured for response.");
    +436      }
    +437
    +438      // `Content-Language`
    +439      if (ctx.getLanguage() != null && !ctx.getLanguage().isEmpty())
    +440        response.getHeaders().add(HttpHeaders.CONTENT_LANGUAGE, ctx.getLanguage());
    +441      else if (logging.isTraceEnabled())
    +442        logging.trace("`Content-Language` not configured for response.");
    +443
    +444      // `Vary`
    +445      if (ctx.getVaryCount() > 0)
    +446        response.getHeaders().add(
    +447          HttpHeaders.VARY,
    +448          Joiner.on(", ").join(new TreeSet<>(ctx.getVaryList())));
    +449      else if (logging.isTraceEnabled())
    +450        logging.trace("`Vary` not configured for response.");
    +451
    +452      // `Feature-Policy`
    +453      if (ctx.getFeaturePolicyCount() > 0) {
    +454        // gather policies
    +455        SortedSet<String> segments = new TreeSet<>(ctx.getFeaturePolicyList());
    +456        if (!segments.isEmpty()) {
    +457          String renderedPolicy = Joiner.on(" ").join(segments);
    +458          response.getHeaders().add(FEATURE_POLICY_HEADER, renderedPolicy);
    +459        }
    +460      } else if (logging.isTraceEnabled()) {
    +461        logging.trace("`Feature-Policy` not configured for response.");
    +462      }
    +463
    +464      // `Referrer-Policy`
    +465      Optional<String> referrerPolicyToken = tokenForReferrerPolicy(ctx.getReferrerPolicy());
    +466      if (referrerPolicyToken.isPresent()) {
    +467        if (logging.isDebugEnabled())
    +468          logging.debug(format("Indicating `Referrer-Policy`: '%s'.", referrerPolicyToken.get()));
    +469        response.getHeaders().add(
    +470          REFERRER_POLICY_HEADER,
    +471          referrerPolicyToken.get());
    +472      } else if (logging.isTraceEnabled()) {
    +473        logging.trace("`Referrer-Policy` not configured for response.");
    +474      }
    +475
    +476      // `X-Frame-Options`
    +477      Optional<String> framingToken = tokenForFramingPolicy(ctx.getFramingPolicy());
    +478      if (framingToken.isPresent()) {
    +479        if (logging.isDebugEnabled())
    +480          logging.debug(format("Indicating `X-Frame-Options`: '%s'.", ctx.getFramingPolicy().name()));
    +481        response.getHeaders().add(
    +482          X_FRAME_OPTIONS_HEADER,
    +483          framingToken.get());
    +484
    +485      } else if (logging.isTraceEnabled()) {
    +486        logging.trace("No `X-Frame-Options` configured for this response.");
    +487      }
    +488
    +489      // `X-Content-Type-Options`
    +490      if (ctx.getContentTypeNosniff()) {
    +491        if (logging.isDebugEnabled())
    +492          logging.debug("Indicating `X-Content-Type-Options`: 'nosniff'.");
    +493        response.getHeaders().add(
    +494          X_CONTENT_TYPE_OPTIONS_HEADER,
    +495          "nosniff");
    +496      } else if (logging.isTraceEnabled()) {
    +497        logging.trace("No `X-Content-Type-Options` configured for this response.");
    +498      }
    +499
    +500      // `Link` (domain pre-connection)
    +501      if (ctx.getPreconnectCount() > 0) {
    +502        SortedSet<String> preconnectList = new TreeSet<>(ctx.getPreconnectList());
    +503        preconnectList.forEach((preconnect) ->
    +504          response.getHeaders().add(LINK_HEADER, formatLinkHeader(preconnect, LINK_PRECONNECT_TOKEN)));
    +505
    +506        if (logging.isDebugEnabled()) {
    +507          logging.debug(format("Indicated (via `Link`) %s domains for pre-connection.", ctx.getPreconnectCount()));
    +508        }
    +509        if (logging.isTraceEnabled()) {
    +510          logging.trace(format("Domains for pre-connection: %s.", Joiner.on(", ").join(preconnectList)));
    +511        }
    +512      } else if (logging.isTraceEnabled()) {
    +513        logging.trace("No domains to apply to pre-connect hints.");
    +514      }
    +515
    +516      // `Link` (DNS pre-fetching)
    +517      if (ctx.getDnsPrefetchCount() > 0) {
    +518        SortedSet<String> dnsPrefetches = new TreeSet<>(ctx.getDnsPrefetchList());
    +519        dnsPrefetches.forEach((prefetch) ->
    +520          response.getHeaders().add(LINK_HEADER, formatLinkHeader(prefetch, LINK_DNS_PREFETCH_TOKEN)));
    +521
    +522        if (logging.isDebugEnabled()) {
    +523          logging.debug(format("Indicated (via `Link`) %s domains for DNS prefetching.", ctx.getDnsPrefetchCount()));
    +524        }
    +525        if (logging.isTraceEnabled()) {
    +526          logging.trace(format("DNS domains prefetched: %s.", Joiner.on(", ").join(dnsPrefetches)));
    +527        }
    +528      } else if (logging.isTraceEnabled()) {
    +529        logging.trace("No domains to apply to DNS prefetch hints.");
    +530      }
    +531
    +532      // `X-XSS-Protection`
    +533      if (ctx.getXssProtection() != null && !ctx.getXssProtection().isEmpty()) {
    +534        if (logging.isDebugEnabled())
    +535          logging.debug(format("Applying `X-XSS-Protection` policy: '%s'.", ctx.getXssProtection()));
    +536        response.getHeaders().add(X_XSS_PROTECTION_HEADER, ctx.getXssProtection());
    +537      } else if (logging.isTraceEnabled()) {
    +538        logging.trace("No `X-XSS-Protection` policy configured for this response cycle.");
    +539      }
    +540
    +541      // additional headers
    +542      if (ctx.getHeaderCount() > 0)
    +543        ctx.getHeaderList().forEach(
    +544          (header) -> response.getHeaders().add(header.getName(), header.getValue()));
    +545      else if (logging.isTraceEnabled())
    +546        logging.trace("No additional headers to apply.");
    +547    } else {
    +548      logging.warn("Failed to find HTTP cycle context: cannot finalize headers.");
    +549    }
    +550    return response;
    +551  }
    +552
    +553  /** @return Built context. After calling this method the first time, context may no longer be mutated. */
    +554  public @Nonnull PageContext render() {
    +555    if (this.closed.get()) {
    +556      assert this.builtContext != null;
    +557    } else {
    +558      this.closed.compareAndSet(false, true);
    +559      this.builtContext = PageContext.fromProto(
    +560        this.context.build(),
    +561        this.props,
    +562        this.injected,
    +563        this.namingMapProvider.orElse(null),
    +564        new SoyContext.SoyI18NContext(
    +565          this.messageBundle,
    +566          this.translationsFile,
    +567          this.translationsResource
    +568        ),
    +569        this.delegatePredicate.orElse(null));
    +570      if (logging.isDebugEnabled()) {
    +571        logging.debug(format("Exported page context with: %s props, %s injecteds, and proto follows \n%s",
    +572          this.props.size(),
    +573          this.injected.size(),
    +574          this.builtContext.getPageContext()));
    +575      }
    +576    }
    +577    return this.builtContext;
    +578  }
    +579
    +580  // -- Builder Interface (Context) -- //
    +581
    +582  /**
    +583   * Returns the currently-set content type for this response render flow. This value generally defaults to
    +584   * {@link MediaType#TEXT_HTML}.
    +585   *
    +586   * @return Content type set to serve for this render flow.
    +587   */
    +588  public @Nonnull String contentType() {
    +589    return this.contentTypeValue;
    +590  }
    +591
    +592  /**
    +593   * Sets the content type to return for the current render response flow. This value should typically be set from one
    +594   * of the options on {@link MediaType}, or as a raw {@code Content-Type} header value.
    +595   *
    +596   * @param type Content type to set.
    +597   * @return Current page context manager (for call chain-ability).
    +598   */
    +599  public @Nonnull PageContextManager contentType(@Nonnull String type) {
    +600    //noinspection ConstantConditions
    +601    if (type == null) throw new IllegalArgumentException("Cannot pass `null` for content type.");
    +602    this.contentTypeValue = type;
    +603    return this;
    +604  }
    +605
    +606  /**
    +607   * Add a `Feature-Policy` entry for the current response render flow. This value will be appended to whatever current
    +608   * values are set for the `Feature-Policy` header.
    +609   *
    +610   * @param policies Policies to add for the current page. Do not pass `null`.
    +611   * @return Current page context manager (for call chain-ability).
    +612   * @throws IllegalArgumentException If `null` is passed for the provided policies.
    +613   */
    +614  @CanIgnoreReturnValue
    +615  public @Nonnull PageContextManager addFeaturePolicy(@Nonnull String... policies) {
    +616    //noinspection ConstantConditions
    +617    if (policies == null) throw new IllegalArgumentException("Cannot pass `null` for feature policies.");
    +618    for (String policy : policies) {
    +619      this.context.addFeaturePolicy(policy);
    +620    }
    +621    return this;
    +622  }
    +623
    +624  /**
    +625   * Clear the current set of `Feature-Policy` entries. If the app makes use of the framework's built-in page frame and
    +626   * response cycle, the value will automatically be used.
    +627   *
    +628   * @return Current page context manager (for call chain-ability).
    +629   */
    +630  @CanIgnoreReturnValue
    +631  public @Nonnull PageContextManager clearFeaturePolicy() {
    +632    this.context.clearFeaturePolicy();
    +633    return this;
    +634  }
    +635
    +636  /**
    +637   * Overwrite the set of `Feature-Policy` entries for the current render flow. If the app makes use of the framework's
    +638   * built-in page frame and response cycle, the value will automatically be used.
    +639   *
    +640   * @param policies Policies to set for the current page. Do not pass `null`.
    +641   * @return Current page context manager (for call chain-ability).
    +642   * @throws IllegalArgumentException If `null` is passed for the policy collection to apply.
    +643   */
    +644  @CanIgnoreReturnValue
    +645  public @Nonnull PageContextManager setFeaturePolicy(@Nonnull Collection<String> policies) {
    +646    //noinspection ConstantConditions
    +647    if (policies == null) throw new IllegalArgumentException("Cannot pass `null` for `Feature-Policy` set.");
    +648    this.clearFeaturePolicy();
    +649    if (!policies.isEmpty()) this.context.addAllFeaturePolicy(policies);
    +650    return this;
    +651  }
    +652
    +653  /**
    +654   * Retrieve the current value for the page title, set in the builder. If there is no value, {@link Optional#empty()}
    +655   * is supplied as the return value.
    +656   *
    +657   * @return Current page title, wrapped in an optional value.
    +658   */
    +659  public @Nonnull Optional<String> title() {
    +660    final String val = this.context.getMetaBuilder().getTitle();
    +661    return "".equals(val) ? Optional.empty() : Optional.of(val);
    +662  }
    +663
    +664  /**
    +665   * Set the page title for the current render flow. If the app makes use of the framework's built-in page frame, the
    +666   * title will automatically be used.
    +667   *
    +668   * @param title Title to set for the current page. Do not pass `null`.
    +669   * @return Current page context manager (for call chain-ability).
    +670   * @throws IllegalArgumentException If `null` is passed for the title.
    +671   */
    +672  @CanIgnoreReturnValue
    +673  public @Nonnull PageContextManager title(@Nonnull String title) {
    +674    //noinspection ConstantConditions
    +675    if (title == null) throw new IllegalArgumentException("Cannot pass `null` for page title.");
    +676    this.context.getMetaBuilder().setTitle(title);
    +677    return this;
    +678  }
    +679
    +680  /**
    +681   * Retrieve the current value for the page description, set in the builder. If there is no value set,
    +682   * {@link Optional#empty()} is supplied as the return value.
    +683   *
    +684   * @return Current page description, wrapped in an optional value.
    +685   */
    +686  public @Nonnull Optional<String> description() {
    +687    final String val = this.context.getMetaBuilder().getDescription();
    +688    return "".equals(val) ? Optional.empty() : Optional.of(val);
    +689  }
    +690
    +691  /**
    +692   * Set the page description for the current render flow. If the app makes use of the framework's built-in page frame,
    +693   * the value will automatically be used.
    +694   *
    +695   * @param description Description to set for the current page. Do not pass `null`.
    +696   * @return Current page context manager (for call chain-ability).
    +697   * @throws IllegalArgumentException If `null` is passed for the description.
    +698   */
    +699  @CanIgnoreReturnValue
    +700  public @Nonnull PageContextManager description(@Nonnull String description) {
    +701    //noinspection ConstantConditions
    +702    if (description == null) throw new IllegalArgumentException("Cannot pass `null` for page description.");
    +703    this.context.getMetaBuilder().setDescription(description);
    +704    return this;
    +705  }
    +706
    +707  /**
    +708   * Retrieve the current value for the page keywords, set in the builder. If there is no value set,
    +709   * {@link Optional#empty()} is supplied as the return value.
    +710   *
    +711   * @return Current page keywords, wrapped in an optional value.
    +712   */
    +713  public @Nonnull Optional<List<String>> keywords() {
    +714    final ArrayList<String> val = this.context.getMetaBuilder().getKeywordList().asByteStringList()
    +715            .stream()
    +716            .map(ByteString::toString)
    +717            .collect(Collectors.toCollection(() -> new ArrayList<>(this.context.getMetaBuilder().getKeywordCount())));
    +718    return val.isEmpty() ? Optional.empty() : Optional.of(val);
    +719  }
    +720
    +721  /**
    +722   * Add the provided page keywords for the current render flow. If the app makes use of the framework's built-in page
    +723   * frame, the value will automatically be used.
    +724   *
    +725   * @param keywords Keywords to set for the current page. Do not pass `null`.
    +726   * @return Current page context manager (for call chain-ability).
    +727   * @throws IllegalArgumentException If `null` is passed for the keywords.
    +728   */
    +729  @CanIgnoreReturnValue
    +730  public @Nonnull PageContextManager addKeyword(@Nonnull String... keywords) {
    +731    //noinspection ConstantConditions
    +732    if (keywords == null) throw new IllegalArgumentException("Cannot pass `null` for page keywords.");
    +733    for (String keyword : keywords) {
    +734      this.context.getMetaBuilder().addKeyword(keyword);
    +735    }
    +736    return this;
    +737  }
    +738
    +739  /**
    +740   * Clear the current set of page keywords for the current render flow. If the app makes use of the framework's
    +741   * built-in page frame, the value will automatically be used.
    +742   *
    +743   * @return Current page context manager (for call chain-ability).
    +744   */
    +745  @CanIgnoreReturnValue
    +746  public @Nonnull PageContextManager clearKeywords() {
    +747    this.context.getMetaBuilder().clearKeyword();
    +748    return this;
    +749  }
    +750
    +751  /**
    +752   * Overwrite the current set of page keywords for the current render flow. If the app makes use of the framework's
    +753   * built-in page frame, the value will automatically be used.
    +754   *
    +755   * @param keywords Keywords to set for the current page. Do not pass `null`.
    +756   * @return Current page context manager (for call chain-ability).
    +757   * @throws IllegalArgumentException If `null` is passed for the keywords.
    +758   */
    +759  @CanIgnoreReturnValue
    +760  public @Nonnull PageContextManager setKeywords(@Nonnull Collection<String> keywords) {
    +761    //noinspection ConstantConditions
    +762    if (keywords == null) throw new IllegalArgumentException("Cannot pass `null` for page keywords.");
    +763    this.clearKeywords();
    +764    if (!keywords.isEmpty()) this.context.getMetaBuilder().addAllKeyword(keywords);
    +765    return this;
    +766  }
    +767
    +768  /**
    +769   * Retrieve the full set of regular HTML metadata links attached to the current render flow. If the app makes use of
    +770   * the framework's built-in age frame, these links will automatically be applied.
    +771   *
    +772   * @return Current set of links that will be listed in page metadata.
    +773   */
    +774  public @Nonnull Optional<List<Context.PageLink>> links() {
    +775    final List<Context.PageLink> links = this.context.getMetaBuilder().getLinkList();
    +776    return links.isEmpty() ? Optional.empty() : Optional.of(links);
    +777  }
    +778
    +779  /**
    +780   * Add a regular HTML metadata link to the current render flow, specified by a {@link Context.PageLink} proto record.
    +781   * Adding via this method is sufficient for the link to make it into the rendered page, so long as the framework's
    +782   * page frame is invoked.
    +783   *
    +784   * @param link HTML metadata link to add to the page.
    +785   * @return Current page context manager (for call chain-ability).
    +786   * @throws IllegalArgumentException If `null` is passed for the link.
    +787   */
    +788  @CanIgnoreReturnValue
    +789  public @Nonnull PageContextManager addLink(@Nonnull Context.PageLink.Builder link) {
    +790    //noinspection ConstantConditions
    +791    if (link == null) throw new IllegalArgumentException("Cannot pass `null` for page link spec.");
    +792    this.context.getMetaBuilder().addLink(link);
    +793    return this;
    +794  }
    +795
    +796  /**
    +797   * Add a regular HTML metadata link to the current render flow, specified by the provided method parameters. Each
    +798   * parameter maps to an attribute specified for the <pre>link</pre> HTML element.
    +799   *
    +800   * @param relevance HTML "rel" attribute.
    +801   * @param href HTML "href" attribute.
    +802   * @param type HTML "type" attribute. Wrapped in an optional.
    +803   * @return Current page context manager (for call chain-ability).
    +804   * @throws IllegalArgumentException If `null` is passed for any parameter.
    +805   */
    +806  @CanIgnoreReturnValue
    +807  @SuppressWarnings({"ConstantConditions", "OptionalAssignedToNull"})
    +808  public @Nonnull PageContextManager addLink(@Nonnull String relevance,
    +809                                             @Nonnull URI href,
    +810                                             @Nonnull Optional<String> type) {
    +811    if (relevance == null) throw new IllegalArgumentException("Cannot pass `null` for page link relevance.");
    +812    if (href == null) throw new IllegalArgumentException("Cannot pass `null` for page link href.");
    +813    if (type == null) throw new IllegalArgumentException("Cannot pass `null` for page link type.");
    +814    final Context.PageLink.Builder builder = Context.PageLink.newBuilder()
    +815      .setRelevance(relevance)
    +816      .setHref(this.trustedResource(href));
    +817    type.ifPresent(builder::setType);
    +818    this.context.getMetaBuilder().addLink(builder);
    +819    return this;
    +820  }
    +821
    +822  /**
    +823   * Clear the set of HTML metadata links assigned to the current render flow.
    +824   *
    +825   * @return Current page context manager (for call chain-ability).
    +826   */
    +827  @CanIgnoreReturnValue
    +828  public @Nonnull PageContextManager clearLinks() {
    +829    this.context.getMetaBuilder().clearLink();
    +830    return this;
    +831  }
    +832
    +833  /**
    +834   * Overwrite the set of page metadata links for the current render flow. If the app makes use of the framework's
    +835   * built-in page frame, the value will automatically be used.
    +836   *
    +837   * @param links Link directives to set for the current page. Do not pass `null`.
    +838   * @return Current page context manager (for call chain-ability).
    +839   * @throws IllegalArgumentException If `null` is passed for the provided links.
    +840   */
    +841  @CanIgnoreReturnValue
    +842  @SuppressWarnings("ConstantConditions")
    +843  public @Nonnull PageContextManager setLinks(@Nonnull Collection<Context.PageLink.Builder> links) {
    +844    if (links == null) throw new IllegalArgumentException("Cannot pass `null` for page links.");
    +845    this.clearLinks();
    +846    links.forEach(this::addLink);
    +847    return this;
    +848  }
    +849
    +850  /**
    +851   * Overwrite the value specified for the "robots" metadata key in the current render flow. If the app makes use of the
    +852   * framework's built-in page frame, the value will automatically be used.
    +853   *
    +854   * @param value Robots metadata value to use.
    +855   * @return Current page context manager (for call chain-ability).
    +856   * @throws IllegalArgumentException If `null` is passed for the provided value.
    +857   */
    +858  @CanIgnoreReturnValue
    +859  @SuppressWarnings("ConstantConditions")
    +860  public @Nonnull PageContextManager setRobots(@Nonnull String value) {
    +861    if (value == null) throw new IllegalArgumentException("Cannot pass `null` for robots value.");
    +862    this.context.getMetaBuilder().setRobots(value);
    +863    return this;
    +864  }
    +865
    +866  /**
    +867   * Overwrite the value specified for the "googlebot" metadata key in the current render flow. If the app makes use of
    +868   * the framework's built-in page frame, the value will automatically be used.
    +869   *
    +870   * @param value Googlebot metadata value to use.
    +871   * @return Current page context manager (for call chain-ability).
    +872   * @throws IllegalArgumentException If `null` is passed for the provided value.
    +873   */
    +874  @CanIgnoreReturnValue
    +875  @SuppressWarnings("ConstantConditions")
    +876  public @Nonnull PageContextManager setGooglebot(@Nonnull String value) {
    +877    if (value == null) throw new IllegalArgumentException("Cannot pass `null` for googlebot value.");
    +878    this.context.getMetaBuilder().setGooglebot(value);
    +879    return this;
    +880  }
    +881
    +882  /**
    +883   * Clear the current value, if any, set for <pre>robots</pre> in the current render flow metadata.
    +884   *
    +885   * @return Current page context manager (for call chain-ability).
    +886   */
    +887  @CanIgnoreReturnValue
    +888  public @Nonnull PageContextManager clearRobots() {
    +889    this.context.getMetaBuilder().clearRobots();
    +890    return this;
    +891  }
    +892
    +893  /**
    +894   * Clear the current value, if any, set for <pre>googlebot</pre> in the current render flow metadata.
    +895   *
    +896   * @return Current page context manager (for call chain-ability).
    +897   */
    +898  @CanIgnoreReturnValue
    +899  public @Nonnull PageContextManager clearGooglebot() {
    +900    this.context.getMetaBuilder().clearGooglebot();
    +901    return this;
    +902  }
    +903
    +904  /**
    +905   * Retrieve OpenGraph settings specified in the current page context. This method always returns a builder, to avoid
    +906   * re-builds of protocol buffers during page context construction. If no OpenGraph settings are available, an empty
    +907   * {@link Optional} is returned instead.
    +908   *
    +909   * @return {@link Optional}-wrapped OpenGraph settings, or {@link Optional#empty()}.
    +910   */
    +911  public @Nonnull Optional<Context.Metadata.OpenGraph.Builder> getOpenGraph() {
    +912    return this.context.getMetaBuilder().hasOpenGraph() ?
    +913      Optional.of(this.context.getMetaBuilder().getOpenGraphBuilder()) :
    +914      Optional.empty();
    +915  }
    +916
    +917  /**
    +918   * Overwrite the OpenGraph metadata configuration for the current render flow, with the provided OpenGraph metadata
    +919   * configuration. If the rendered page uses the framework's page template, the values will be serialized and rendered
    +920   * into the page head.
    +921   *
    +922   * @param content OpenGraph content to render.
    +923   * @return Current page context manager (for call chain-ability).
    +924   * @throws IllegalArgumentException If `null` is passed for the provided content.
    +925   */
    +926  @CanIgnoreReturnValue
    +927  @SuppressWarnings("ConstantConditions")
    +928  public @Nonnull PageContextManager setOpenGraph(@Nonnull Context.Metadata.OpenGraph.Builder content) {
    +929    if (content == null) throw new IllegalArgumentException("Cannot pass `null` for OpenGraph content.");
    +930    this.clearOpenGraph();
    +931    this.context.getMetaBuilder().setOpenGraph(content);
    +932    return this;
    +933  }
    +934
    +935  /**
    +936   * Apply the provided OpenGraph metadata configuration to the <i>current</i> OpenGraph metadata configuration, if any.
    +937   * If no OpenGraph metadata configuration is set, this method effectively overwrites it.
    +938   *
    +939   * @param content OpenGraph content to merge and apply.
    +940   * @return Current page context manager (for call chain-ability).
    +941   * @throws IllegalArgumentException If `null` is passed for the provided content.
    +942   */
    +943  @CanIgnoreReturnValue
    +944  @SuppressWarnings("ConstantConditions")
    +945  public @Nonnull PageContextManager applyOpenGraph(@Nonnull Context.Metadata.OpenGraph.Builder content) {
    +946    if (content == null) throw new IllegalArgumentException("Cannot pass `null` for OpenGraph content.");
    +947    Context.Metadata.OpenGraph.Builder ogContent = this.context.getMetaBuilder().getOpenGraphBuilder();
    +948    this.setOpenGraph(ogContent.mergeFrom(content.build()));
    +949    return this;
    +950  }
    +951
    +952  /**
    +953   * Clear any OpenGraph metadata configuration attached to the current render flow. If there is no such configuration,
    +954   * this method is a no-op.
    +955   *
    +956   * @return Current page context manager (for call chain-ability).
    +957   */
    +958  @CanIgnoreReturnValue
    +959  public @Nonnull PageContextManager clearOpenGraph() {
    +960    this.context.getMetaBuilder().clearOpenGraph();
    +961    return this;
    +962  }
    +963
    +964  /**
    +965   * Return the set of RDFa prefixes affixed to the current render flow, if any. If none are found,
    +966   * {@link Optional#empty()} is returned.
    +967   *
    +968   * @return Set of prefixes available on the current render flow, if any.
    +969   */
    +970  public @Nonnull Optional<List<Context.RDFPrefix>> prefixes() {
    +971    if (this.context.getMetaBuilder().getPrefixCount() > 0) {
    +972      return Optional.of(this.context.getMetaBuilder().getPrefixList());
    +973    }
    +974    return Optional.empty();
    +975  }
    +976
    +977  /**
    +978   * Overwrite the full set of RDFa prefixes for the current render flow. If the rendered page uses the framework's page
    +979   * template, the values will be serialized and rendered into the page head.
    +980   *
    +981   * @return Current page context manager (for call chain-ability).
    +982   */
    +983  public @Nonnull PageContextManager setPrefixes(@Nonnull Optional<List<Context.RDFPrefix>> prefixes) {
    +984    if (prefixes.isPresent()) {
    +985      List<Context.RDFPrefix> prefixList = prefixes.get();
    +986      prefixList.forEach((prefix) -> {
    +987        this.context.getMetaBuilder().addPrefix(Context.RDFPrefix.newBuilder()
    +988          .setPrefix(prefix.getPrefix())
    +989          .setTarget(prefix.getTarget()));
    +990      });
    +991      return this;
    +992    }
    +993    this.context.getMetaBuilder().clearPrefix();
    +994    return this;
    +995  }
    +996
    +997  /**
    +998   * Include the specified JavaScript resource in the rendered page, according to the specified settings. The module is
    +999   * expected to exist and be included in the application's asset bundle.
    +1000   *
    +1001   * @param name Name of the script module to load into the page.
    +1002   * @return Current page context manager (for call chain-ability).
    +1003   * @throws IllegalArgumentException If `null` is passed for the module name, or it cannot be located, or is invalid.
    +1004   */
    +1005  @CanIgnoreReturnValue
    +1006  public @Nonnull PageContextManager script(@Nonnull String name) {
    +1007    // sensible defaults for script embedding
    +1008    return this.script(
    +1009      name, null, true, false, false, false, false, false);
    +1010  }
    +1011
    +1012  /**
    +1013   * Include the specified JavaScript resource in the rendered page, according to the specified settings. The module is
    +1014   * expected to exist and be included in the application's asset bundle. This variant allows specification of the most
    +1015   * frequent attributes used with scripts.
    +1016   *
    +1017   * @param name Name of the script module to load into the page.
    +1018   * @param defer Whether to add the {@code defer} attribute to the script tag.
    +1019   * @param async Whether to add the {@code async} attribute to the script tag.
    +1020   * @return Current page context manager (for call chain-ability).
    +1021   * @throws IllegalArgumentException If `null` is passed for the module name, or it cannot be located, or is invalid.
    +1022   */
    +1023  @CanIgnoreReturnValue
    +1024  public @Nonnull PageContextManager script(@Nonnull String name, @Nonnull Boolean defer, @Nonnull Boolean async) {
    +1025    return this.script(
    +1026      name, null, defer, async, false, false, false, false);
    +1027  }
    +1028
    +1029  /**
    +1030   * Include the specified JavaScript resource in the rendered page, according to the specified settings. The module is
    +1031   * expected to exist and be included in the application's asset bundle.
    +1032   *
    +1033   * <p><b>Behavior:</b> Script assets included in this manner are always loaded in the document head, so be judicious
    +1034   * with {@code defer} if you are loading a significant amount of JavaScript. There are no default script assets.
    +1035   * Scripts are emitted in the order in which they are attached to the page context (i.e. via this method).</p>
    +1036   *
    +1037   * <p><b>Optimization:</b> Activating the {@code preload} flag causes the script asset to be mentioned in a
    +1038   * {@code Link} header, which makes supporting browsers aware of it before loading the DOM. For more aggressive
    +1039   * circumstances, the {@code push} flag proactively pushes the asset to the browser (where supported), unless the
    +1040   * framework knows the client has seen the asset already. Where HTTP/2 is not supported, special triggering
    +1041   * {@code Link} headers may be used.</p>
    +1042   *
    +1043   * @param name Name of the script module to load into the page.
    +1044   * @param id ID to assign the script block in the DOM, so it may be located dynamically.
    +1045   * @param defer Whether to add the {@code defer} attribute to the script tag.
    +1046   * @param async Whether to add the {@code async} attribute to the script tag.
    +1047   * @param module Whether to add the {@code module} attribute to the script tag.
    +1048   * @param nomodule Whether to add the {@code nomodule} attribute to the script tag.
    +1049   * @param preload Whether to link/hint about the asset in response headers.
    +1050   * @param push Whether to pro-actively push the asset, if we think the client doesn't have it.
    +1051   * @return Current page context manager (for call chain-ability).
    +1052   * @throws IllegalArgumentException If `null` is passed for the module name, or it cannot be located, or is invalid.
    +1053   */
    +1054  @CanIgnoreReturnValue
    +1055  public @Nonnull PageContextManager script(@Nonnull String name,
    +1056                                            @Nullable String id,
    +1057                                            @Nonnull Boolean defer,
    +1058                                            @Nonnull Boolean async,
    +1059                                            @Nonnull Boolean module,
    +1060                                            @Nonnull Boolean nomodule,
    +1061                                            @Nonnull Boolean preload,
    +1062                                            @Nonnull Boolean push) {
    +1063    Optional<ManagedAsset<ScriptAsset>> maybeAsset = (
    +1064      this.assetManager.assetMetadataByModule(Objects.requireNonNull(name)));
    +1065
    +1066    // fail if not present
    +1067    if (maybeAsset.isEmpty())
    +1068      throw new IllegalArgumentException(format("Failed to locate script module '%s'.", name));
    +1069    ManagedAsset<ScriptAsset> asset = maybeAsset.get();
    +1070
    +1071    if (!asset.getType().equals(AssetManager.ModuleType.JS))
    +1072      throw new IllegalArgumentException(format("Cannot include asset '%s' as %s, it is of type JS.",
    +1073        name,
    +1074        asset.getType().name()));
    +1075
    +1076    // resolve constituent scripts
    +1077    for (ScriptAsset script : asset.getAssets()) {
    +1078      // pre-filled URI js record
    +1079      JavaScript.Builder js = JavaScript.newBuilder(script.getScript())
    +1080        .setDefer(defer)
    +1081        .setAsync(async)
    +1082        .setModule(module)
    +1083        .setNoModule(nomodule)
    +1084        .setPreload(preload)
    +1085        .setPush(push);
    +1086
    +1087      if (id != null) js.setId(id);
    +1088      this.script(js);
    +1089    }
    +1090    return this;
    +1091  }
    +1092
    +1093  /**
    +1094   * Include the specified JavaScript resource in the rendered page, according to enclosed settings (i.e. respecting
    +1095   * {@code defer}, {@code async}, and other attributes). If the script asset has an ID, it will <b>not</b> be
    +1096   * passed through ID rewriting before being rendered.
    +1097   *
    +1098   * <p><b>Behavior:</b> Script assets included in this manner are always loaded in the document head, so be judicious
    +1099   * with {@code defer} if you are loading a significant amount of JavaScript. There are no default script assets.
    +1100   * Scripts are emitted in the order in which they are attached to the page context (i.e. via this method).</p>
    +1101   *
    +1102   * @param script Script asset to load in the rendered page output. Do not pass `null`.
    +1103   * @return Current page context manager (for call chain-ability).
    +1104   * @throws IllegalArgumentException If `null` is passed for the module name, or it cannot be located, or is invalid.
    +1105   */
    +1106  @CanIgnoreReturnValue
    +1107  public @Nonnull PageContextManager script(@Nonnull Context.Scripts.JavaScript.Builder script) {
    +1108    //noinspection ConstantConditions
    +1109    if (script == null) throw new IllegalArgumentException("Cannot pass `null` for script.");
    +1110    this.context.getScriptsBuilder().addLink(script);
    +1111    return this;
    +1112  }
    +1113
    +1114  /**
    +1115   * Include the specified CSS stylesheet in the rendered page with default settings. The module is expected to exist
    +1116   * and be included in the application's asset bundle.
    +1117   *
    +1118   * @param name Name of the module to load.
    +1119   * @return Current page context manager (for call chain-ability).
    +1120   * @throws IllegalArgumentException If `null` is passed for the module name, or it cannot be located, or is invalid.
    +1121   */
    +1122  @CanIgnoreReturnValue
    +1123  public @Nonnull PageContextManager stylesheet(@Nonnull String name) {
    +1124    return stylesheet(name, null);
    +1125  }
    +1126
    +1127  /**
    +1128   * Include the specified CSS stylesheet in the rendered page, along with the specified media setting. The module is
    +1129   * expected to exist and be included in the application's asset bundle.
    +1130   *
    +1131   * @param name Name of the module to load.
    +1132   * @param media Media assignment for the stylesheet.
    +1133   * @return Current page context manager (for call chain-ability).
    +1134   * @throws IllegalArgumentException If `null` is passed for the module name, or the module cannot be located.
    +1135   */
    +1136  @CanIgnoreReturnValue
    +1137  public @Nonnull PageContextManager stylesheet(@Nonnull String name, @Nullable String media) {
    +1138    return stylesheet(name, null, null, false, false);
    +1139  }
    +1140
    +1141  /**
    +1142   * Include the specified CSS stylesheet in the rendered page, according to the specified settings. The module is
    +1143   * expected to exist and be included in the application's asset bundle.
    +1144   *
    +1145   * <p><b>Optimization:</b> Activating the {@code preload} flag causes the style asset to be mentioned in a
    +1146   * {@code Link} header, which makes supporting browsers aware of it before loading the DOM. For more aggressive
    +1147   * circumstances, the {@code push} flag proactively pushes the asset to the browser (where supported), unless the
    +1148   * framework knows the client has seen the asset already. Where HTTP/2 is not supported, special triggering
    +1149   * {@code Link} headers may be used.</p>
    +1150   *
    +1151   * @param name Name of the module to load.
    +1152   * @param id ID to assign the link tag in the DOM.
    +1153   * @param media Media assignment for the stylesheet.
    +1154   * @param preload Whether to link/hint about the asset in response headers.
    +1155   * @param push Whether to pro-actively push the asset, if we think the client doesn't have it.
    +1156   * @return Current page context manager (for call chain-ability).
    +1157   * @throws IllegalArgumentException If `null` is passed for the module name, or it cannot be located, or is invalid.
    +1158   */
    +1159  @CanIgnoreReturnValue
    +1160  public @Nonnull PageContextManager stylesheet(@Nonnull String name,
    +1161                                                @Nullable String id,
    +1162                                                @Nullable String media,
    +1163                                                @Nonnull Boolean preload,
    +1164                                                @Nonnull Boolean push) {
    +1165    Optional<ManagedAsset<StyleAsset>> maybeAsset = (
    +1166      this.assetManager.assetMetadataByModule(Objects.requireNonNull(name)));
    +1167
    +1168    // fail if not present
    +1169    if (maybeAsset.isEmpty())
    +1170      throw new IllegalArgumentException(format("Failed to locate style module '%s'.", name));
    +1171    ManagedAsset<StyleAsset> asset = maybeAsset.get();
    +1172
    +1173    if (!asset.getType().equals(AssetManager.ModuleType.CSS))
    +1174      throw new IllegalArgumentException(format("Cannot include asset '%s' as %s, it is of type CSS.",
    +1175        name,
    +1176        asset.getType().name()));
    +1177
    +1178    // resolve constituent stylesheets
    +1179    for (StyleAsset styleBundle : asset.getAssets()) {
    +1180      // pre-filled URI js record
    +1181      Stylesheet.Builder styles = Stylesheet.newBuilder(styleBundle.getStylesheet())
    +1182        .setPush(push)
    +1183        .setPreload(preload);
    +1184
    +1185      if (id != null) styles.setId(id);
    +1186      if (media != null) styles.setMedia(media);
    +1187      this.stylesheet(styles);
    +1188    }
    +1189    return this;
    +1190  }
    +1191
    +1192  /**
    +1193   * Include the specified CSS stylesheet resource in the rendered page, according to the enclosed settings (i.e.
    +1194   * respecting properties like <pre>media</pre>). If the stylesheet has an ID, it will <b>not</b> be passed through ID
    +1195   * rewriting before being rendered.
    +1196   *
    +1197   * <p>Stylesheets included in this manner are always loaded in the head, via a link tag. If you want to defer loading
    +1198   * of styles, you'll have to do so from JS. Stylesheet links are emitted in the order in which they are attached to
    +1199   * the page context (i.e. via this method).</p>
    +1200   *
    +1201   * @param stylesheet Stylesheet asset to load in the rendered page output. Do not pass `null`.
    +1202   * @return Current page context manager (for call chain-ability).
    +1203   * @throws IllegalArgumentException If `null` is passed for the stylesheet.
    +1204   */
    +1205  @CanIgnoreReturnValue
    +1206  public @Nonnull PageContextManager stylesheet(@Nonnull Context.Styles.Stylesheet.Builder stylesheet) {
    +1207    //noinspection ConstantConditions
    +1208    if (stylesheet == null) throw new IllegalArgumentException("Cannot pass `null` for stylesheet.");
    +1209    this.context.getStylesBuilder().addLink(stylesheet);
    +1210    return this;
    +1211  }
    +1212
    +1213  // -- Map-like Interface (Props) -- //
    +1214
    +1215  /**
    +1216   * Install a regular context value, at the named key provided. This will make the value available in any bound Soy
    +1217   * render flow via a <pre>@param</pre> declaration on the subject template to be rendered.
    +1218   *
    +1219   * @param key Key at which to make this available as a param.
    +1220   * @param value Value to provide for the parameter.
    +1221   * @return Current page context manager (for call chain-ability).
    +1222   * @throws IllegalArgumentException If the provided <pre>key</pre> is <pre>null</pre>, or a disallowed value, like
    +1223   *         <pre>context</pre> (which cannot be overridden).
    +1224   */
    +1225  @CanIgnoreReturnValue
    +1226  public @Nonnull PageContextManager put(@Nonnull String key, @Nonnull Object value) {
    +1227    //noinspection ConstantConditions
    +1228    if (key == null)
    +1229      throw new IllegalArgumentException("Must provide a key to put a Soy context property. Got `null` for key.");
    +1230    this.props.put(key, value);
    +1231    return this;
    +1232  }
    +1233
    +1234  /**
    +1235   * Safely retrieve a value from the current render context properties. If no property is found at the provided key,
    +1236   * an empty {@link Optional} is returned. Otherwise, an {@link Optional} is returned wrapping whatever value was
    +1237   * found.
    +1238   *
    +1239   * @param key Key at which to retrieve the desired render context property.
    +1240   * @return Optional-wrapped context property value.
    +1241   */
    +1242  public @Nonnull Optional<Object> get(@Nonnull String key) {
    +1243    return this.get(key, false);
    +1244  }
    +1245
    +1246  /**
    +1247   * Safely retrieve a value from either the current render context properties, or the current injected values. If no
    +1248   * value is found in whatever context we're looking in, an empty {@link Optional} is returned. Otherwise, an
    +1249   * {@link Optional} is returned wrapping whatever value was found.
    +1250   *
    +1251   * @param key Key at which to retrieve the desired render property or injected value.
    +1252   * @param injected Whether to look in the injected values, or regular property values.
    +1253   * @return Empty optional if nothing was found, otherwise, the found value wrapped in an optional.
    +1254   */
    +1255  public @Nonnull Optional<Object> get(@Nonnull String key, boolean injected) {
    +1256    final ConcurrentMap<String, Object> base = injected ? this.injected : this.props;
    +1257    if (base.containsKey(key))
    +1258      return Optional.of(base.get(key));
    +1259    return Optional.empty();
    +1260  }
    +1261
    +1262  /**
    +1263   * Install an injected context value, at the named key provided. This will make the value available in any bound Soy
    +1264   * render flow via the <pre>@inject</pre> declaration, on any template in the render flow.
    +1265   *
    +1266   * @param key Key at which to make this available as an injected value.
    +1267   * @param value Value to provide for the parameter.
    +1268   * @return Current page context manager (for call chain-ability).
    +1269   * @throws IllegalArgumentException If the provided <pre>key</pre> is <pre>null</pre>, or a disallowed value, like
    +1270   *         <pre>context</pre> (which cannot be overridden).
    +1271   */
    +1272  @CanIgnoreReturnValue
    +1273  public @Nonnull PageContextManager inject(@Nonnull String key, @Nonnull Object value) {
    +1274    //noinspection ConstantConditions
    +1275    if (key == null)
    +1276      throw new IllegalArgumentException("Must provide a key to put a Soy context property. Got `null` for key.");
    +1277    if ("context".equals(key.toLowerCase()))
    +1278      throw new IllegalArgumentException("Cannot use key 'context' for injected property.");
    +1279    this.injected.put(key, value);
    +1280    return this;
    +1281  }
    +1282
    +1283  /**
    +1284   * Install, or uninstall, the request-scoped renaming map provider. This object will be used to lookup style classes
    +1285   * and IDs for render-time rewriting. To disable an existing naming map provider override, simply pass an empty
    +1286   * {@link Optional}.
    +1287   *
    +1288   * <p>If no renaming map provider is set here, but a global one is, and renaming is enabled, the global map will be
    +1289   * used. If a renaming map is set here, but renaming is <i>not</i> enabled, no renaming takes place.</p>
    +1290   *
    +1291   * @param namingMapProvider Renaming map provider to install, or {@link Optional#empty()} to uninstall any existing
    +1292   *                          overriding renaming map provider.
    +1293   * @return Current page context manager (for call chain-ability).
    +1294   */
    +1295  @CanIgnoreReturnValue
    +1296  public @Nonnull PageContextManager rewrite(@Nonnull Optional<SoyNamingMapProvider> namingMapProvider) {
    +1297    this.namingMapProvider = namingMapProvider;
    +1298    return this;
    +1299  }
    +1300
    +1301  /**
    +1302   * Indicate whether message translation is enabled for the rendering layer. This checks the presence of either a
    +1303   * translations file, or a translations URL resource. Additionally, a check is performed for any pre-fabricated Soy
    +1304   * messages bundle. If any of those are present, `true` is returned.
    +1305   *
    +1306   * @return Whether translations will be enabled during render.
    +1307   */
    +1308  @Override
    +1309  public boolean translate() {
    +1310    return (
    +1311      this.translationsFile.isPresent() ||
    +1312      this.translationsResource.isPresent() ||
    +1313      this.messageBundle.isPresent()
    +1314    );
    +1315  }
    +1316
    +1317  /**
    +1318   * Mount a loaded XLIFF file for use during render. Messages mentioned in the XLIFF file and in the corresponding
    +1319   * template are replaced as the renderer proceeds through the template. Passing {@link Optional#empty()} clears any
    +1320   * active translation file.
    +1321   *
    +1322   * @param xliffFile Translations file to apply.
    +1323   * @return Current page context manager (for call chain-ability).
    +1324   */
    +1325  public @Nonnull PageContextManager messagesFile(@Nonnull Optional<File> xliffFile) {
    +1326    this.translationsFile = xliffFile;
    +1327    return this;
    +1328  }
    +1329
    +1330  /**
    +1331   * Return the current translation file that will be applied during the next render routine, if available.
    +1332   *
    +1333   * @return Translation file, or {@link Optional#empty()}.
    +1334   */
    +1335  @Override
    +1336  public @Nonnull Optional<File> messagesFile() {
    +1337    return this.translationsFile;
    +1338  }
    +1339
    +1340  /**
    +1341   * Load and mount a referenced XLIFF resource for use during render. Messages mentioned in the XLIFF resource and in
    +1342   * the corresponding template are replaced as the renderer proceeds through the template.
    +1343   *
    +1344   * @param xliffData Translations data to apply.
    +1345   * @return Current page context manager (for call chain-ability).
    +1346   */
    +1347  public @Nonnull PageContextManager messagesResource(@Nonnull Optional<URL> xliffData) {
    +1348    this.translationsResource = xliffData;
    +1349    return this;
    +1350  }
    +1351
    +1352  /**
    +1353   * Return the current translation resource that will be applied during the next render routine, if available.
    +1354   *
    +1355   * @return Translation resource, or {@link Optional#empty()}.
    +1356   */
    +1357  @Override
    +1358  public @Nonnull Optional<URL> messagesResource() {
    +1359    return this.translationsResource;
    +1360  }
    +1361
    +1362  /**
    +1363   * Mount a pre-fabricated Soy message bundle for translation use during render. Messages mentioned in the bundle and
    +1364   * in the corresponding template are replaced as the renderer proceeds through the template.
    +1365   *
    +1366   * @param soyMsgBundle Soy message bundle to apply.
    +1367   * @return Current page context manager (for call chain-ability).
    +1368   */
    +1369  public @Nonnull PageContextManager messageBundle(@Nonnull Optional<SoyMsgBundle> soyMsgBundle) {
    +1370    this.messageBundle = soyMsgBundle;
    +1371    return this;
    +1372  }
    +1373
    +1374  /**
    +1375   * Return the current pre-fabricated Soy message bundle that will be applied during the next render routine, if
    +1376   * available.
    +1377   *
    +1378   * @return Soy message bundle, or {@link Optional#empty()}.
    +1379   */
    +1380  @Override
    +1381  public @Nonnull Optional<SoyMsgBundle> messageBundle() throws IOException {
    +1382    if (this.messageBundle.isPresent()) {
    +1383      return this.messageBundle;
    +1384    }
    +1385    return PageRender.super.messageBundle();
    +1386  }
    +1387
    +1388  // -- Delegate Rendering -- //
    +1389
    +1390  /**
    +1391   * Fetch the active delegate package, or return {@link Optional#empty()}. Either a {@link Predicate} is returned which
    +1392   * is invoked to match the delegate package, or {@link Optional#empty()} indicates that no package should be available
    +1393   * (or that defaults should be used, where available).
    +1394   *
    +1395   * @return Returns the active predicate, as applicable.
    +1396   */
    +1397  @Override
    +1398  public @Nonnull Optional<Predicate<String>> delegatePackage() {
    +1399    return this.delegatePredicate;
    +1400  }
    +1401
    +1402  /**
    +1403   * Set the active delegate package name, wrapped in an implied predicate which filters explicitly against the provided
    +1404   * name value. If the referenced Soy package is included in the build, it should be selected explicitly as the active
    +1405   * package during render.
    +1406   *
    +1407   * <p>Calling this method overwrites any current delegate package.</p>
    +1408   *
    +1409   * @param packageName Package name to match for delegation.
    +1410   * @return Current page context manager (for call chain-ability).
    +1411   */
    +1412  public @Nonnull PageContextManager delegatePackage(@Nonnull String packageName) {
    +1413    this.delegatePredicate = Optional.of(packageName::equals);
    +1414    return this;
    +1415  }
    +1416
    +1417  /**
    +1418   * Set the active delegate package predicate directly. This predicate is invoked to match a delegate package at
    +1419   * runtime, if and when one is needed.
    +1420   *
    +1421   * <p>Calling this method overwrites any current delegate package.</p>
    +1422   *
    +1423   * @param packagePredicate Predicate to match packages.
    +1424   * @return Current page context manager (for call chain-ability).
    +1425   */
    +1426  public @Nonnull PageContextManager delegatePackage(@Nonnull Predicate<String> packagePredicate) {
    +1427    this.delegatePredicate = Optional.of(packagePredicate);
    +1428    return this;
    +1429  }
    +1430
    +1431  /**
    +1432   * Clear any current delegate package.
    +1433   *
    +1434   * @return Current page context manager (for call chain-ability).
    +1435   */
    +1436  public @Nonnull PageContextManager clearDelegatePackage() {
    +1437    this.delegatePredicate = Optional.empty();
    +1438    return this;
    +1439  }
    +1440
    +1441  // -- Builder Interface (Response) -- //
    +1442
    +1443  /**
    +1444   * Affix an arbitrary HTTP header to the response eventually produced by this page context, assuming no errors occur.
    +1445   * If an error occurs while rendering, an error page is served <b>without</b> the additional header (unless
    +1446   * {@code force} is passed via the companion method to this one).
    +1447   *
    +1448   * @param name Name of the header value to affix to the response.
    +1449   * @param value Value of the header to affix to the response, at {@code name}.
    +1450   * @return Current page context manager (for call chain-ability).
    +1451   */
    +1452  @CanIgnoreReturnValue
    +1453  public @Nonnull PageContextManager header(@Nonnull String name, @Nonnull String value) {
    +1454    return this.header(name, value, false);
    +1455  }
    +1456
    +1457  /**
    +1458   * Affix an arbitrary HTTP header to the response eventually produced by this page context, assuming no errors occur.
    +1459   * If an error occurs while rendering, an error page is served <b>without</b> the additional header (unless
    +1460   * {@code force} is passed via the companion method to this one).
    +1461   *
    +1462   * @param name Name of the header value to affix to the response.
    +1463   * @param value Value of the header to affix to the response, at {@code name}.
    +1464   * @param force Whether to force the header to be applied, even when an error occurs.
    +1465   * @return Current page context manager (for call chain-ability).
    +1466   */
    +1467  @CanIgnoreReturnValue
    +1468  public @Nonnull PageContextManager header(@Nonnull String name, @Nonnull String value, @Nonnull Boolean force) {
    +1469    if (HttpHeaders.CONTENT_LANGUAGE.equals(name))
    +1470      throw new IllegalArgumentException("Please use `language()` instead of setting a `Content-Language` header.");
    +1471    if (HttpHeaders.VARY.equals(name))
    +1472      throw new IllegalArgumentException("Please use `vary()` instead of setting a `Vary` header.");
    +1473    if (HttpHeaders.ETAG.equals(name))
    +1474      throw new IllegalArgumentException("Please use `enableETags()` instead of setting an `ETag` header.");
    +1475    if (ACCEPT_CH_HEADER.equals(name))
    +1476      throw new IllegalArgumentException("Please use `clientHints()` instead of setting an `Accept-CH` header.");
    +1477    this.context.addHeader(Context.ResponseHeader.newBuilder()
    +1478      .setName(name)
    +1479      .setValue(value)
    +1480      .setForce(force));
    +1481    return this;
    +1482  }
    +1483
    +1484  /**
    +1485   * Enable a dynamic {@code ETag} header value, which is computed from the rendered content produced by this page
    +1486   * context record.
    +1487   *
    +1488   * @param enableETags Whether to enable the {@code ETag} header.
    +1489   * @return Current page context manager (for call chain-ability).
    +1490   */
    +1491  @CanIgnoreReturnValue
    +1492  @SuppressWarnings("WeakerAccess")
    +1493  public @Nonnull PageContextManager enableETags(@Nonnull Boolean enableETags) {
    +1494    this.context.setEtag(Context.DynamicETag.newBuilder()
    +1495      .setEnabled(enableETags)
    +1496      .setStrong(true));
    +1497    return this;
    +1498  }
    +1499
    +1500  /**
    +1501   * Enable the provided set of server-indicated client hint types. If the client supports any of the indicated types,
    +1502   * it will enclose matching client-hints accordingly, on subsequent requests for resources. If an empty optional
    +1503   * ({@link Optional#empty()}) is passed, the current set of client hints are cleared. This method is additive.
    +1504   *
    +1505   * @param hints Client hints to indicate as supported by the server.
    +1506   * @return Current page context manager (for call chain-ability).
    +1507   */
    +1508  @CanIgnoreReturnValue
    +1509  @SuppressWarnings("WeakerAccess")
    +1510  public @Nonnull PageContextManager supportedClientHints(Optional<Iterable<ClientHint>> hints,
    +1511                                                          @Nonnull Optional<Long> ttl) {
    +1512    if (hints.isPresent()) {
    +1513      // add hints to indicated set
    +1514      this.context.getHintsBuilder().addAllSupported(hints.get());
    +1515      ttl.ifPresent(aLong -> this.context.getHintsBuilder().setLifetime(aLong));
    +1516    } else {
    +1517      // if an empty optional is passed, clear the current set
    +1518      this.context.getHintsBuilder().clearIndicated();
    +1519    }
    +1520    return this;
    +1521  }
    +1522
    +1523  /**
    +1524   * Enable the provided set of server-indicated client hint types. This method is additive. If the client supports any
    +1525   * of the indicated types, it will enclose matching client-hints accordingly, on subsequent requests for resources.
    +1526   *
    +1527   * @param hints Client hints to indicate as supported by the server.
    +1528   * @return Current page context manager (for call chain-ability).
    +1529   */
    +1530  @CanIgnoreReturnValue
    +1531  public @Nonnull PageContextManager supportedClientHints(ClientHint... hints) {
    +1532    return supportedClientHints(Optional.of(Arrays.asList(hints)), Optional.empty());
    +1533  }
    +1534
    +1535  /**
    +1536   * Set the value to send back in this response's {@code Content-Language} header. If an {@link Optional#empty()}
    +1537   * instance is passed, no header is sent.
    +1538   *
    +1539   * @param language Language, or empty value, to send.
    +1540   * @return Current page context manager (for call chain-ability).
    +1541   */
    +1542  @CanIgnoreReturnValue
    +1543  public @Nonnull PageContextManager language(@Nonnull Optional<String> language) {
    +1544    if (language.isPresent()) {
    +1545      this.context.setLanguage(language.get());
    +1546    } else {
    +1547      this.context.clearLanguage();
    +1548    }
    +1549    return this;
    +1550  }
    +1551
    +1552  /**
    +1553   * Return the language value set for the current render routine - i.e. bound to the current request cycle. This is
    +1554   * often driven by the user's browser settings.
    +1555   *
    +1556   * @return Current language for this request cycle.
    +1557   */
    +1558  public @Nonnull Optional<String> language() {
    +1559    if (this.context.getLanguage().length() > 0) {
    +1560      return Optional.of(this.context.getLanguage());
    +1561    }
    +1562    return Optional.empty();
    +1563  }
    +1564
    +1565  /**
    +1566   * Append an HTTP request header considered as part of the {@code Vary} header in the response. These values are de-
    +1567   * duplicated before joining and affixing.
    +1568   *
    +1569   * @param variance HTTP request header which causes the associated response to vary.
    +1570   * @return Current page context manager (for call chain-ability).
    +1571   */
    +1572  @CanIgnoreReturnValue
    +1573  public @Nonnull PageContextManager vary(@Nonnull String variance) {
    +1574    return this.vary(Collections.singleton(variance));
    +1575  }
    +1576
    +1577  /**
    +1578   * Append an HTTP request header considered as part of the {@code Vary} header in the response. These values are de-
    +1579   * duplicated before joining and affixing.
    +1580   *
    +1581   * @param variance HTTP request header which causes the associated response to vary.
    +1582   * @return Current page context manager (for call chain-ability).
    +1583   */
    +1584  @CanIgnoreReturnValue
    +1585  public @Nonnull PageContextManager vary(@Nonnull String... variance) {
    +1586    return this.vary(Arrays.asList(variance));
    +1587  }
    +1588
    +1589  /**
    +1590   * Append an HTTP request header considered as part of the {@code Vary} header in the response. These values are de-
    +1591   * duplicated before joining and affixing.
    +1592   *
    +1593   * @param variance HTTP request header which causes the associated response to vary.
    +1594   * @return Current page context manager (for call chain-ability).
    +1595   */
    +1596  @CanIgnoreReturnValue
    +1597  @SuppressWarnings("WeakerAccess")
    +1598  public @Nonnull PageContextManager vary(@Nonnull Iterable<String> variance) {
    +1599    variance.forEach((segment) -> {
    +1600      if (this.varySegments.add(segment))
    +1601        this.context.addVary(segment);
    +1602    });
    +1603    return this;
    +1604  }
    +1605
    +1606  /**
    +1607   * Set the specified {@code prefix} as the Content Distribution Network hostname prefix to use when rendering asset
    +1608   * links for this HTTP cycle. <b>Do not use a user-provided value for this.</b> If no prefix is set, or one is cleared
    +1609   * by passing {@link Optional#empty()}, configuration will be read and used instead.
    +1610   *
    +1611   * @param prefix Prefix to apply as a CDN hostname for static assets.
    +1612   * @return Current page context manager (for call chain-ability).
    +1613   */
    +1614  @CanIgnoreReturnValue
    +1615  @SuppressWarnings("WeakerAccess")
    +1616  public @Nonnull PageContextManager cdnPrefix(@Nonnull Optional<String> prefix) {
    +1617    this.cdnPrefix = prefix;
    +1618    if (prefix.isPresent()) {
    +1619      TrustedResourceUrlProto proto = this.trustedResource(URI.create(prefix.get()));
    +1620      this.context.setCdnPrefix(proto);
    +1621      this.injected.put(CDN_PREFIX_IJ_PROP, proto);
    +1622    } else {
    +1623      this.context.clearCdnPrefix();
    +1624      this.injected.remove(CDN_PREFIX_IJ_PROP);
    +1625    }
    +1626    return this;
    +1627  }
    +1628
    +1629  /**
    +1630   * Retrieve the currently-configured CDN prefix value, if one exists. If none can be located, return an empty optional
    +1631   * via {@link Optional#empty()}.
    +1632   *
    +1633   * @return Current CDN prefix value.
    +1634   */
    +1635  @SuppressWarnings("WeakerAccess")
    +1636  public @Nonnull Optional<String> getCdnPrefix() {
    +1637    return this.cdnPrefix;
    +1638  }
    +1639
    +1640  /**
    +1641   * Inject the specified list of hosts as DNS records to prefetch from the browser. There is no guarantee made by the
    +1642   * browser that the records will be fetched, it's just a performance hint.
    +1643   *
    +1644   * @param hosts Hosts to add to the DNS prefetch list.
    +1645   * @return Current page context manager (for call chain-ability).
    +1646   */
    +1647  @CanIgnoreReturnValue
    +1648  @SuppressWarnings("WeakerAccess")
    +1649  public @Nonnull PageContextManager dnsPrefetch(Iterable<String> hosts) {
    +1650    hosts.forEach(this.context::addDnsPrefetch);
    +1651    return this;
    +1652  }
    +1653
    +1654  /**
    +1655   * Inject the specified DNS hostname(s) as records to prefetch from the browser. There is no guarantee made by the
    +1656   * browser that the records will be fetched, it's just a performance hint.
    +1657   *
    +1658   * @param hosts Hosts to add to the DNS prefetch list.
    +1659   * @return Current page context manager (for call chain-ability).
    +1660   */
    +1661  @CanIgnoreReturnValue
    +1662  @SuppressWarnings("WeakerAccess")
    +1663  public @Nonnull PageContextManager dnsPrefetch(String... hosts) {
    +1664    return dnsPrefetch(Arrays.asList(hosts));
    +1665  }
    +1666
    +1667  /**
    +1668   * Inject the specified list of hosts as pre-connect hints for the browser. There is no guarantee made by the browser
    +1669   * that the connections will be established, it's just a performance hint.
    +1670   *
    +1671   * @param hosts Hosts to add to the server pre-connection list.
    +1672   * @return Current page context manager (for call chain-ability).
    +1673   */
    +1674  @CanIgnoreReturnValue
    +1675  @SuppressWarnings("WeakerAccess")
    +1676  public @Nonnull PageContextManager preconnect(Iterable<String> hosts) {
    +1677    hosts.forEach(this.context::addPreconnect);
    +1678    return this;
    +1679  }
    +1680
    +1681  /**
    +1682   * Inject the specified list of hosts as pre-connect hints for the browser. There is no guarantee made by the browser
    +1683   * that the connections will be established, it's just a performance hint.
    +1684   *
    +1685   * @param hosts Hosts to add to the server pre-connection list.
    +1686   * @return Current page context manager (for call chain-ability).
    +1687   */
    +1688  @CanIgnoreReturnValue
    +1689  @SuppressWarnings("WeakerAccess")
    +1690  public @Nonnull PageContextManager preconnect(String... hosts) {
    +1691    return preconnect(Arrays.asList(hosts));
    +1692  }
    +1693
    +1694  // -- API: Trusted Resources -- //
    +1695
    +1696  /**
    +1697   * Generate a trusted resource URL for the provided Java URL.
    +1698   *
    +1699   * @param url Pre-ordained trusted resource URL.
    +1700   * @return Trusted resource URL specification proto.
    +1701   */
    +1702  @SuppressWarnings("WeakerAccess")
    +1703  public @Nonnull TrustedResourceUrlProto trustedResource(@Nonnull URL url) {
    +1704    return UnsafeSanitizedContentOrdainer.ordainAsSafe(
    +1705      url.toString(),
    +1706      SanitizedContent.ContentKind.TRUSTED_RESOURCE_URI)
    +1707      .toTrustedResourceUrlProto();
    +1708  }
    +1709
    +1710  /**
    +1711   * Generate a trusted resource URL for the provided Java URI.
    +1712   *
    +1713   * @param uri Pre-ordained trusted resource URI.
    +1714   * @return Trusted resource URL specification proto.
    +1715   */
    +1716  @SuppressWarnings("WeakerAccess")
    +1717  public @Nonnull TrustedResourceUrlProto trustedResource(@Nonnull URI uri) {
    +1718    return UnsafeSanitizedContentOrdainer.ordainAsSafe(
    +1719      uri.toString(),
    +1720      SanitizedContent.ContentKind.TRUSTED_RESOURCE_URI)
    +1721      .toTrustedResourceUrlProto();
    +1722  }
    +1723
    +1724  // -- Interface: Client Hints -- //
    +1725
    +1726  /**
    +1727   * Attempt to retrieve an interpreted <i>Client Hints</i> client-indicated value from the current HTTP request. If no
    +1728   * value can be found, or no valid value can be decoded for the hint type specified, {@link Optional#empty()} is
    +1729   * returned.
    +1730   *
    +1731   * @param hint Hint type to look for on the current request.
    +1732   * @param <V> Value type to return for this hint. If incorrect, a warning is logged and an empty value returned.
    +1733   * @return Optional-wrapped found value, or {@link Optional#empty()} if no value could be located.
    +1734   */
    +1735  @SuppressWarnings("unchecked")
    +1736  public @Nonnull <V> Optional<V> hint(@Nonnull ClientHint hint) {
    +1737    if (this.hints.getIndicatedCount() > 0 && this.hints.getIndicatedList().contains(hint)) {
    +1738      // we found the hint - try to decode the value
    +1739      try {
    +1740        final V value;
    +1741        switch (hint) {
    +1742          case DPR: value = (V)(Integer.valueOf(this.hints.getDevicePixelRatio())); break;
    +1743          case ECT: value = (V)(this.hints.getEffectiveConnectionType()); break;
    +1744          case RTT: value = (V)(Integer.valueOf(this.hints.getRoundTripTime())); break;
    +1745          case DOWNLINK: value = (V)(Float.valueOf(this.hints.getDownlink())); break;
    +1746          case DEVICE_MEMORY: value = (V)(Float.valueOf(this.hints.getDevicePixelRatio())); break;
    +1747          case SAVE_DATA: value = (V)(Boolean.valueOf(this.hints.getSaveData())); break;
    +1748          case WIDTH: value = (V)(Integer.valueOf(this.hints.getWidth())); break;
    +1749          case VIEWPORT_WIDTH: value = (V)(Integer.valueOf(this.hints.getViewportWidth())); break;
    +1750          default:
    +1751            logging.warn(format("Unrecognized client hint: '%s'.", hint.name()));
    +1752            return Optional.empty();
    +1753        }
    +1754        return Optional.of(value);
    +1755
    +1756      } catch (ClassCastException cce) {
    +1757        logging.warn(format("Failed to cast client hint value '%s'.", hint.name()));
    +1758      }
    +1759    }
    +1760    return Optional.empty();
    +1761  }
    +1762
    +1763  // -- Interface: HTTP Request -- //
    +1764
    +1765  /**
    +1766   * Return the currently-active HTTP request object, to which the current render/controller flow is bound.
    +1767   *
    +1768   * @return Active HTTP request object.
    +1769   */
    +1770  @SuppressWarnings("rawtypes")
    +1771  public @Nonnull HttpRequest getRequest() {
    +1772    return this.request;
    +1773  }
    +1774
    +1775  /**
    +1776   * Return the set of interpreted <i>Client Hints</i> headers for the current request.
    +1777   *
    +1778   * @return Client hints configuration.
    +1779   */
    +1780  public @Nonnull ClientHints getHints() {
    +1781    return this.hints;
    +1782  }
    +1783
    +1784  // -- Interface: HTTP `ETag`s -- //
    +1785
    +1786  /** {@inheritDoc} */
    +1787  @Override
    +1788  public boolean enableETags() {
    +1789    return this.builtContext != null ?
    +1790      this.builtContext.getPageContext().getEtag().getEnabled() :
    +1791      this.context.getEtag().getEnabled();
    +1792  }
    +1793
    +1794  /** {@inheritDoc} */
    +1795  @Override
    +1796  public boolean strongETags() {
    +1797    return this.builtContext != null ?
    +1798      this.builtContext.getPageContext().getEtag().getStrong() :
    +1799      this.context.getEtag().getStrong();
    +1800  }
    +1801
    +1802  // -- Interface: Closeable -- //
    +1803
    +1804  /**
    +1805   * Closes this stream and releases any system resources associated with it. If the stream is already closed then
    +1806   * invoking this method has no effect.
    +1807   *
    +1808   * <p>As noted in {@link AutoCloseable#close()}, cases where the close may fail require careful attention. It is
    +1809   * strongly advised to relinquish the underlying resources and to internally <em>mark</em> the {@code Closeable} as
    +1810   * closed, prior to throwing the {@code IOException}.
    +1811   */
    +1812  @Override
    +1813  public void close() {
    +1814    if (!this.closed.get()) {
    +1815      this.render();
    +1816    }
    +1817  }
    +1818
    +1819  // -- Interface: Delegated Context -- //
    +1820
    +1821  /**
    +1822   * Retrieve serializable server-side-rendered page context, which should be assigned to the render flow bound to this
    +1823   * context mediator.
    +1824   *
    +1825   * @return Server-side rendered page context.
    +1826   */
    +1827  @Override
    +1828  public @Nonnull Context getPageContext() {
    +1829    this.close();
    +1830    if (this.builtContext == null) throw new IllegalStateException("Unable to read page context.");
    +1831    return this.builtContext.getPageContext();
    +1832  }
    +1833
    +1834  /**
    +1835   * Retrieve properties which should be made available via regular, declared `@param` statements.
    +1836   *
    +1837   * @return Map of regular template properties.
    +1838   */
    +1839  @Nonnull
    +1840  @Override
    +1841  public Map<String, Object> getProperties() {
    +1842    this.close();
    +1843    if (this.builtContext == null) throw new IllegalStateException("Unable to read page context.");
    +1844    return this.builtContext.getProperties();
    +1845  }
    +1846
    +1847  /**
    +1848   * Retrieve properties and values that should be made available via `@inject`.
    +1849   *
    +1850   * @param framework Framework-injected properties.
    +1851   * @return Map of injected properties and their values.
    +1852   */
    +1853  @Nonnull
    +1854  @Override
    +1855  public Map<String, Object> getInjectedProperties(@Nonnull Map<String, Object> framework) {
    +1856    this.close();
    +1857    if (this.builtContext == null) throw new IllegalStateException("Unable to read page context.");
    +1858    return this.builtContext.getInjectedProperties(framework);
    +1859  }
    +1860
    +1861  /**
    +1862   * Specify a Soy renaming map which overrides the globally-installed map, if any. Renaming must still be activated via
    +1863   * config, or manually, for the return value of this method to have any effect.
    +1864   *
    +1865   * @return {@link SoyNamingMapProvider} that should be used for this render routine.
    +1866   */
    +1867  @Nonnull
    +1868  @Override
    +1869  public Optional<SoyNamingMapProvider> overrideNamingMap() {
    +1870    this.close();
    +1871    if (this.builtContext == null) throw new IllegalStateException("Unable to read page context.");
    +1872    return this.builtContext.overrideNamingMap();
    +1873  }
    +1874
    +1875  /**
    +1876   * Indicate whether live-reload mode is enabled or not, which is governed by the built toolchain (i.e. a Bazel
    +1877   * condition, activated by the Makefile, which injects a JDK system property). Live-reload features additionally
    +1878   * require dev mode to be active.
    +1879   *
    +1880   * @return Whether live-reload is enabled.
    +1881   */
    +1882  public boolean isLiveReload() {
    +1883    return liveReload;
    +1884  }
    +1885}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/PageRender.html b/docs/java/src-html/gust/backend/PageRender.html new file mode 100644 index 000000000..f74f2d12b --- /dev/null +++ b/docs/java/src-html/gust/backend/PageRender.html @@ -0,0 +1,114 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015import io.micronaut.views.soy.SoyContextMediator;
    +016import tools.elide.page.Context;
    +017
    +018import javax.annotation.Nonnull;
    +019
    +020
    +021/**
    +022 * Interface by which protobuf-driven Soy render context can be managed and orchestrated by a custom {@link PageContext}
    +023 * object. Provides the ability to specify variables for <pre>@param</pre> and <pre>@inject</pre> Soy declarations, and
    +024 * the ability to override objects like the {@link io.micronaut.views.soy.SoyNamingMapProvider}.
    +025 *
    +026 * @author Sam Gammon (sam@momentum.io)
    +027 * @see PageContext Default implementation of this interface
    +028 */
    +029public interface PageRender extends SoyContextMediator {
    +030  /** Name at which proto-context is injected. */
    +031  String PAGE_CONTEXT_IJ_NAME = "page";
    +032
    +033  /**
    +034   * Retrieve serializable server-side-rendered page context, which should be assigned to the render flow bound to this
    +035   * context mediator.
    +036   *
    +037   * @return Server-side rendered page context.
    +038   */
    +039  @Nonnull Context getPageContext();
    +040}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/TemplateProvider.html b/docs/java/src-html/gust/backend/TemplateProvider.html new file mode 100644 index 000000000..2dd748aa4 --- /dev/null +++ b/docs/java/src-html/gust/backend/TemplateProvider.html @@ -0,0 +1,195 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend;
    +014
    +015import com.google.template.soy.SoyFileSet;
    +016import com.google.template.soy.jbcsrc.api.SoySauce;
    +017import com.google.template.soy.jbcsrc.api.SoySauceBuilder;
    +018import com.google.template.soy.shared.SoyCssRenamingMap;
    +019import com.google.template.soy.shared.SoyIdRenamingMap;
    +020import io.micronaut.views.soy.SoyFileSetProvider;
    +021import io.micronaut.views.soy.SoyNamingMapProvider;
    +022
    +023import javax.annotation.Nonnull;
    +024import javax.annotation.Nullable;
    +025import javax.inject.Singleton;
    +026
    +027
    +028/** Default Soy template provider. */
    +029@Singleton
    +030public final class TemplateProvider implements SoyFileSetProvider, SoyNamingMapProvider {
    +031  /** Default set of templates, detected from the classpath. */
    +032  private static final @Nonnull SoySauce defaultCompiledTemplates;
    +033
    +034  /** Templates explicitly provided by the developer. */
    +035  private @Nullable SoySauce overrideTemplates = null;
    +036
    +037  /** CSS class renaming map to apply during render. */
    +038  private @Nullable SoyIdRenamingMap idRenamingMap = null;
    +039
    +040  /** CSS ID renaming map to apply during render. */
    +041  private @Nullable SoyCssRenamingMap cssRenamingMap = null;
    +042
    +043  static {
    +044    defaultCompiledTemplates = new SoySauceBuilder().build();
    +045  }
    +046
    +047  // -- Public API: Installation -- //
    +048
    +049  /**
    +050   * Install a set of compiled templates manually into the local template provider context. These templates will be used
    +051   * instead of any detected templates on the classpath (via the {@link #defaultCompiledTemplates}).
    +052   *
    +053   * @param compiled Compiled templates to install.
    +054   * @return Template provider, for method chaining.
    +055   */
    +056  public TemplateProvider installTemplates(@Nonnull SoySauce compiled) {
    +057    this.overrideTemplates = compiled;
    +058    return this;
    +059  }
    +060
    +061  /**
    +062   * Install a Soy-compatible style renaming map for CSS classes, and optionally one for CSS IDs as well. These maps are
    +063   * installed locally and provided to Soy during template render operations. Any templates rendered subsequent to this
    +064   * call should have the rewritten styles applied in the DOM, and with any served assets associated with the page.
    +065   *
    +066   * If an existing ID rewriting map is mounted, a call to unseat it with `null` will be ignored.
    +067   *
    +068   * @param cssMap CSS renaming map to apply during render.
    +069   * @param idMap Optional ID renaming map to apply during render.
    +070   * @return Template provider, for method chaining.
    +071   */
    +072  public TemplateProvider installRenamingMaps(@Nonnull SoyCssRenamingMap cssMap, @Nullable SoyIdRenamingMap idMap) {
    +073    this.cssRenamingMap = cssMap;
    +074    if (this.idRenamingMap == null || idMap != null)
    +075      this.idRenamingMap = idMap;
    +076    return this;
    +077  }
    +078
    +079  // -- Public API: Consumption -- //
    +080
    +081  /**
    +082   * Provide a Soy file set for the embedded templates.
    +083   *
    +084   * @return Prepared Soy file set.
    +085   * @deprecated Soy file sets are slow due to runtime template interpretation. Please use compiled templates via the
    +086   *     {@link SoySauce} class instead. See the see-also listings for this method for more information.
    +087   * @see #provideCompiledTemplates() to acquire compiled template instances.
    +088   */
    +089  @Deprecated @Nullable @Override public SoyFileSet provideSoyFileSet() {
    +090    return null;
    +091  }
    +092
    +093  /**
    +094   * Provide the compiled Soy file set for embedded templates.
    +095   *
    +096   * @return Pre-compiled Soy templates.
    +097   */
    +098  @Nonnull @Override public SoySauce provideCompiledTemplates() {
    +099    if (overrideTemplates != null)
    +100      return overrideTemplates;
    +101    return defaultCompiledTemplates;
    +102  }
    +103
    +104  /**
    +105   * By default, return `null` for the Soy CSS renaming map.
    +106   *
    +107   * @return `null`, by default.
    +108   */
    +109  @Nullable @Override public SoyCssRenamingMap cssRenamingMap() {
    +110    return cssRenamingMap;
    +111  }
    +112
    +113  /**
    +114   * By default, return `null` for the Soy ID renaming map.
    +115   *
    +116   * @return `null`, by default.
    +117   */
    +118  @Nullable @Override public SoyIdRenamingMap idRenamingMap() {
    +119    return idRenamingMap;
    +120  }
    +121}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/annotations/Js.html b/docs/java/src-html/gust/backend/annotations/Js.html new file mode 100644 index 000000000..c7e990817 --- /dev/null +++ b/docs/java/src-html/gust/backend/annotations/Js.html @@ -0,0 +1,138 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.annotations;
    +014
    +015import io.micronaut.aop.Introduction;
    +016
    +017import javax.annotation.Nonnull;
    +018import java.lang.annotation.*;
    +019
    +020
    +021/**
    +022 * Links a script module to a given controller endpoint, such that it will automatically be included and loaded in the
    +023 * head of the page with the specified settings.
    +024 */
    +025@Documented
    +026@Introduction
    +027@Target(ElementType.METHOD)
    +028@Retention(RetentionPolicy.RUNTIME)
    +029public @interface Js {
    +030  /**
    +031   * Module name for the script module. This is generally the Closure module path (for example, `app.entrypoint`).
    +032   *
    +033   * @return Module name for this script module.
    +034   */
    +035  @Nonnull String value();
    +036
    +037  /**
    +038   * Whether to defer loading this script until the body DOM has initialized.
    +039   *
    +040   * @return Defaults to {@code true}.
    +041   */
    +042  boolean defer() default true;
    +043
    +044  /**
    +045   * Whether to completely unlink this script's loading flow from the DOM (i.e. async mode).
    +046   *
    +047   * @return Defaults to {@code false}.
    +048   */
    +049  boolean async() default false;
    +050
    +051  /**
    +052   * Whether to treat this script entry as a module.
    +053   *
    +054   * @return Defaults to {@code false}.
    +055   */
    +056  boolean module() default false;
    +057
    +058  /**
    +059   * Whether to treat this script entry as a module fallback.
    +060   *
    +061   * @return Defaults to {@code false}.
    +062   */
    +063  boolean noModule() default false;
    +064}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/annotations/Page.html b/docs/java/src-html/gust/backend/annotations/Page.html new file mode 100644 index 000000000..b1127f066 --- /dev/null +++ b/docs/java/src-html/gust/backend/annotations/Page.html @@ -0,0 +1,194 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.annotations;
    +014
    +015import io.micronaut.aop.Introduction;
    +016import static tools.elide.backend.builtin.Sitemap.ChangeFrequency;
    +017
    +018import javax.annotation.Nonnull;
    +019import java.lang.annotation.*;
    +020
    +021
    +022/**
    +023 * Specifies settings for a given page method, that may later be applied via outputs like the application's site-map, or
    +024 * other metadata assets.
    +025 */
    +026@Documented
    +027@Introduction
    +028@Target(ElementType.METHOD)
    +029@Retention(RetentionPolicy.RUNTIME)
    +030public @interface Page {
    +031    /** Property name for robots enable/disable. */
    +032    String ROBOTS_ENABLE = "enableIndexing";
    +033
    +034    /** Property name for indexing robot settings. */
    +035    String ROBOTS_SETTINGS = "robotsSettings";
    +036
    +037    /** Property for Googlebot-specific settings. */
    +038    String GOOGLEBOT_SETTINGS = "googlebotSettings";
    +039
    +040    /** Property name for sitemap enable/disable. */
    +041    String SITEMAP = "sitemap";
    +042
    +043    /** Property name for the canonical URL. */
    +044    String CANONICAL = "canonicalUrl";
    +045
    +046    /** Property name for the page's last-modified date. */
    +047    String LAST_MODIFIED = "lastModified";
    +048
    +049    /** Property name for the page's change-frequency value. */
    +050    String CHANGE_FREQUENCY = "changeFrequency";
    +051
    +052    /** Property name for the page's priority value. */
    +053    String PRIORITY = "priority";
    +054
    +055    /** Sentinel for no-value strings. */
    +056    String NO_VALUE = "NO_VALUE";
    +057
    +058    /**
    +059     * Simple name / tag for the page. This can be used to generate URLs or route requests.
    +060     *
    +061     * @return Module name for this script module.
    +062     */
    +063    @Nonnull String value();
    +064
    +065    /**
    +066     * Whether to allow robots on the selected page.
    +067     *
    +068     * @return Defaults to {@code true}.
    +069     */
    +070    boolean enableIndexing() default true;
    +071
    +072    /**
    +073     * Generic settings for indexing robots.
    +074     *
    +075     * @return Indexing settings, if present, or {@code null}.
    +076     */
    +077    String robotsSettings() default NO_VALUE;
    +078
    +079    /**
    +080     * Settings for Googlebot on the selected page.
    +081     *
    +082     * @return Googlebot-specific settings, if present, or {@code null}.
    +083     */
    +084    String googlebotSettings() default NO_VALUE;
    +085
    +086    /**
    +087     * Whether to list this page in the site map.
    +088     *
    +089     * @return Defaults to {@code true}.
    +090     */
    +091    boolean sitemap() default true;
    +092
    +093    /**
    +094     * Specifies the canonical URL to use in sitemaps and robots.txt listings, etc, for this page, if applicable.
    +095     *
    +096     * @return The canonical URL, or {@code null}.
    +097     */
    +098    String canonicalUrl() default NO_VALUE;
    +099
    +100    /**
    +101     * Retrieve the last-modified string for this page. For now, this value is static.
    +102     *
    +103     * @return Last-modified value, or {@code null}.
    +104     */
    +105    String lastModified() default NO_VALUE;
    +106
    +107    /**
    +108     * Retrieve the change frequency set for this page, if any.
    +109     *
    +110     * @return Change frequency value, or {@code null}.
    +111     */
    +112    ChangeFrequency changeFrequency() default ChangeFrequency.UNSPECIFIED_CHANGE_FREQUENCY;
    +113
    +114    /**
    +115     * Priority value for the page.
    +116     *
    +117     * @return Priority value, or {@code null}.
    +118     */
    +119    String priority() default NO_VALUE;
    +120}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/annotations/Style.MediaType.html b/docs/java/src-html/gust/backend/annotations/Style.MediaType.html new file mode 100644 index 000000000..411a9efbd --- /dev/null +++ b/docs/java/src-html/gust/backend/annotations/Style.MediaType.html @@ -0,0 +1,127 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.annotations;
    +014
    +015import io.micronaut.aop.Introduction;
    +016
    +017import javax.annotation.Nonnull;
    +018import javax.annotation.Nullable;
    +019import java.lang.annotation.*;
    +020
    +021
    +022/**
    +023 * Links a style module to a given controller endpoint, such that it will automatically be included and loaded in the
    +024 * head of the page, applying any applicable rewrite settings (if enabled).
    +025 */
    +026@Documented
    +027@Introduction
    +028@Target(ElementType.METHOD)
    +029@Retention(RetentionPolicy.RUNTIME)
    +030public @interface Style {
    +031  /** Applicable media types. */
    +032  enum MediaType {
    +033    /** For screen display. */
    +034    SCREEN,
    +035
    +036    /** For print display. */
    +037    PRINT
    +038  }
    +039
    +040  /**
    +041   * Module name for the style module. This is generally the GSS/style module path (for example, `app.styles`).
    +042   *
    +043   * @return Module name for this style module.
    +044   */
    +045  @Nonnull String value();
    +046
    +047  /**
    +048   * Media type to apply to the injected spreadsheet, if applicable.
    +049   *
    +050   * @return Defaults to {@code SCREEN}.
    +051   */
    +052  @Nullable MediaType media() default MediaType.SCREEN;
    +053}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/annotations/Style.html b/docs/java/src-html/gust/backend/annotations/Style.html new file mode 100644 index 000000000..411a9efbd --- /dev/null +++ b/docs/java/src-html/gust/backend/annotations/Style.html @@ -0,0 +1,127 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.annotations;
    +014
    +015import io.micronaut.aop.Introduction;
    +016
    +017import javax.annotation.Nonnull;
    +018import javax.annotation.Nullable;
    +019import java.lang.annotation.*;
    +020
    +021
    +022/**
    +023 * Links a style module to a given controller endpoint, such that it will automatically be included and loaded in the
    +024 * head of the page, applying any applicable rewrite settings (if enabled).
    +025 */
    +026@Documented
    +027@Introduction
    +028@Target(ElementType.METHOD)
    +029@Retention(RetentionPolicy.RUNTIME)
    +030public @interface Style {
    +031  /** Applicable media types. */
    +032  enum MediaType {
    +033    /** For screen display. */
    +034    SCREEN,
    +035
    +036    /** For print display. */
    +037    PRINT
    +038  }
    +039
    +040  /**
    +041   * Module name for the style module. This is generally the GSS/style module path (for example, `app.styles`).
    +042   *
    +043   * @return Module name for this style module.
    +044   */
    +045  @Nonnull String value();
    +046
    +047  /**
    +048   * Media type to apply to the injected spreadsheet, if applicable.
    +049   *
    +050   * @return Defaults to {@code SCREEN}.
    +051   */
    +052  @Nullable MediaType media() default MediaType.SCREEN;
    +053}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/firestore/FirestoreAdapter.html b/docs/java/src-html/gust/backend/driver/firestore/FirestoreAdapter.html new file mode 100644 index 000000000..5e7ac82ce --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/firestore/FirestoreAdapter.html @@ -0,0 +1,350 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.firestore;
    +014
    +015import com.google.api.gax.core.CredentialsProvider;
    +016import com.google.api.gax.rpc.TransportChannelProvider;
    +017import com.google.cloud.firestore.DocumentSnapshot;
    +018import com.google.cloud.firestore.FirestoreOptions;
    +019import com.google.cloud.firestore.v1.stub.FirestoreStubSettings;
    +020import com.google.cloud.grpc.GrpcTransportOptions;
    +021import com.google.common.util.concurrent.ListeningScheduledExecutorService;
    +022import com.google.protobuf.Message;
    +023import gust.backend.model.*;
    +024import gust.backend.runtime.Logging;
    +025import gust.backend.transport.GoogleAPIChannel;
    +026import gust.backend.transport.GoogleService;
    +027import io.micronaut.context.annotation.Context;
    +028import io.micronaut.context.annotation.Factory;
    +029import io.micronaut.runtime.context.scope.Refreshable;
    +030import org.slf4j.Logger;
    +031
    +032import javax.annotation.Nonnull;
    +033import javax.annotation.concurrent.Immutable;
    +034import javax.annotation.concurrent.ThreadSafe;
    +035import java.util.Optional;
    +036
    +037
    +038/**
    +039 * Defines a built-in database adapter for interacting with Google Cloud Firestore, using business-data models defined
    +040 * through Protobuf annotated with framework-provided metadata.
    +041 *
    +042 * <p>This adapter makes use of a specialized {@link DatabaseDriver} ({@link FirestoreDriver}), and supports a custom
    +043 * configuration class ({@link FirestoreTransportConfig}) which is loaded from application config during service channel
    +044 * initialization. Connections are pooled and cached against a caching executor by the active transport manager.</p>
    +045 *
    +046 * <p>Instantiation is disallowed to facilitate restriction of the active {@link ModelCodec} to Firestore's own model
    +047 * codec, which uses {@link gust.backend.model.ObjectModelCodec} to produce generic collapsed messages during
    +048 * serialization, and to translate from proto-maps during deserialization. Optionally, a compliant instance of
    +049 * {@link CacheDriver} may be provided at construction time, which enables caching against that driver for calls that
    +050 * are eligible (according, again, to settings from {@link FirestoreTransportConfig}).</p>
    +051 *
    +052 * @param <Model> Model type which this adapter adapts to Firestore.
    +053 * @see FirestoreDriver Driver for speaking to Firestore.
    +054 * @see FirestoreTransportConfig Transport configuration for Firestore.
    +055 */
    +056@Immutable
    +057@ThreadSafe
    +058@SuppressWarnings({"WeakerAccess", "unused", "UnstableApiUsage"})
    +059public final class FirestoreAdapter<Key extends Message, Model extends Message>
    +060  implements DatabaseAdapter<Key, Model, DocumentSnapshot, CollapsedMessage> {
    +061  /** Private log pipe. */
    +062  private static final Logger logging = Logging.logger(FirestoreAdapter.class);
    +063
    +064  /** Firestore database driver. */
    +065  private final @Nonnull FirestoreDriver<Key, Model> driver;
    +066
    +067  /** Serializer and deserializer for this model. */
    +068  private final @Nonnull ModelCodec<Model, CollapsedMessage, DocumentSnapshot> codec;
    +069
    +070  /** Cache to use for model interactions through this adapter (optional). */
    +071  private final @Nonnull Optional<CacheDriver<Key, Model>> cache;
    +072
    +073  /**
    +074   * Setup a new Firestore adapter from scratch. Generally instances of this class are acquired through injection, or
    +075   * static factory methods also listed on this class.
    +076   *
    +077   * @param driver Database driver to use when speaking to Firestore.
    +078   * @param codec Serializer and deserializer to use.
    +079   * @param cache Cache to use when reading data from Firestore (optional).
    +080   */
    +081  private FirestoreAdapter(@Nonnull FirestoreDriver<Key, Model> driver,
    +082                           @Nonnull ModelCodec<Model, CollapsedMessage, DocumentSnapshot> codec,
    +083                           @Nonnull Optional<CacheDriver<Key, Model>> cache) {
    +084    this.driver = driver;
    +085    this.codec = codec;
    +086    this.cache = cache;
    +087  }
    +088
    +089  /**
    +090   * Create or otherwise resolve a {@link FirestoreAdapter} for the provided model type and builder. This additionally
    +091   * resolves a model codec, driver, and optionally a caching engine as well (although one may be provided explicitly at
    +092   * the invoking developer's discretion - see {@link #forModel(Message.Builder, FirestoreDriver, Optional)}).
    +093   *
    +094   * <p>The resulting adapter may not be created fresh for the task at hand, but it is threadsafe and shares no direct
    +095   * state with any other operation.</p>
    +096   *
    +097   * @see #forModel(Message.Builder, FirestoreDriver, Optional) To provide an explicit cache driver for this type.
    +098   * @param <M> Model type for which we are requesting a Firestore adapter instance.
    +099   * @param builder Model builder instance, which the engine will clone for each retrieve operation.
    +100   * @param driver Driver which we should use when handling instances of <code>M</code>.
    +101   * @return Pre-fabricated (or otherwise resolved) Firestore adapter for the requested model.
    +102   */
    +103  public static @Nonnull <K extends Message, M extends Message> FirestoreAdapter<K, M> forModel(
    +104    @Nonnull Message.Builder builder,
    +105    @Nonnull FirestoreDriver<K, M> driver) {
    +106    return forModel(builder, driver, Optional.empty());
    +107  }
    +108
    +109  /**
    +110   * Create or otherwise resolve a {@link FirestoreAdapter} for the provided model type and builder. This additionally
    +111   * resolves a model codec, driver, and optionally a caching engine as well.
    +112   *
    +113   * <p>The resulting adapter may not be created fresh for the task at hand, but it is threadsafe and shares no direct
    +114   * state with any other operation.</p>
    +115   *
    +116   * @param <M> Model type for which we are requesting a Firestore adapter instance.
    +117   * @param driver Driver which we should use when handling instances of <code>M</code>.
    +118   * @param builder Model builder instance, which the engine will clone for each retrieve operation.
    +119   * @return Pre-fabricated (or otherwise resolved) Firestore adapter for the requested model.
    +120   */
    +121  public static @Nonnull <K extends Message, M extends Message> FirestoreAdapter<K, M> forModel(
    +122    @Nonnull Message.Builder builder,
    +123    @Nonnull FirestoreDriver<K, M> driver,
    +124    @Nonnull Optional<CacheDriver<K, M>> cacheDriver) {
    +125    return new FirestoreAdapter<>(
    +126      driver,
    +127      driver.codec(),
    +128      cacheDriver);
    +129  }
    +130
    +131  /** Factory responsible for creating {@link FirestoreAdapter} instances from injected dependencies. */
    +132  @Factory
    +133  final static class FirestoreAdapterFactory {
    +134    /**
    +135     * Acquire a new instance of the Firestore adapter, using the specified component objects to facilitate model
    +136     * serialization/deserialization, and transport communication with Firestore.
    +137     *
    +138     * @param messageBuilder Builder for the instance in question.
    +139     * @param driver Driver with which we should talk to Firestore.
    +140     * @param cache Driver with which we should cache eligible data.
    +141     * @return Firestore driver instance.
    +142     */
    +143    @Context
    +144    @Refreshable
    +145    public static @Nonnull <K extends Message, M extends Message> FirestoreAdapter<K, M> acquire(
    +146      @Nonnull Message.Builder messageBuilder,
    +147      @Nonnull FirestoreDriver<K, M> driver,
    +148      @Nonnull Optional<CacheDriver<K, M>> cache) {
    +149      // resolve model builder from type
    +150      return FirestoreAdapter.forModel(
    +151        messageBuilder,
    +152        driver,
    +153        cache);
    +154    }
    +155  }
    +156
    +157  /**
    +158   * Acquire an instance of the {@link FirestoreAdapter} and {@link FirestoreDriver}, customized for the provided
    +159   * `modelInstance` and `keyInstance`. This method variant makes use of a default object for the gRPC transport
    +160   * provider and Google credential provider.
    +161   *
    +162   * @param keyInstance Key type instance for the record in question.
    +163   * @param messageInstance Message type instance for the record in question.
    +164   * @param executorService Background executor service for Firestore operations.
    +165   * @param <K> Key type.
    +166   * @param <M> Message type.
    +167   * @return Instance of the {@link FirestoreAdapter} and {@link FirestoreDriver}, customized as described.
    +168   */
    +169  public static @Nonnull <K extends Message, M extends Message> FirestoreAdapter<K, M> acquire(
    +170      @Nonnull K keyInstance,
    +171      @Nonnull M messageInstance,
    +172      @Nonnull ListeningScheduledExecutorService executorService) {
    +173    return acquire(
    +174        FirestoreOptions.newBuilder(),
    +175        FirestoreStubSettings.defaultTransportChannelProvider(),
    +176        FirestoreStubSettings.defaultCredentialsProviderBuilder().build(),
    +177        GrpcTransportOptions.newBuilder().build(),
    +178        executorService,
    +179        keyInstance,
    +180        messageInstance
    +181    );
    +182  }
    +183
    +184  /**
    +185   * Acquire an instance of the {@link FirestoreAdapter} and {@link FirestoreDriver}, customized for the provided
    +186   * `modelInstance` and `keyInstance`. This method variant makes use of a default object for the gRPC transport
    +187   * provider and Google credential provider, but allows specifying custom {@link FirestoreOptions}.
    +188   *
    +189   * @param baseOptions Base options to apply to the Firestore driver.
    +190   * @param keyInstance Key type instance for the record in question.
    +191   * @param messageInstance Message type instance for the record in question.
    +192   * @param executorService Background executor service for Firestore operations.
    +193   * @param <K> Key type.
    +194   * @param <M> Message type.
    +195   * @return Instance of the {@link FirestoreAdapter} and {@link FirestoreDriver}, customized as described.
    +196   */
    +197  public static @Nonnull <K extends Message, M extends Message> FirestoreAdapter<K, M> acquire(
    +198    @Nonnull K keyInstance,
    +199    @Nonnull M messageInstance,
    +200    @Nonnull FirestoreOptions.Builder baseOptions,
    +201    @Nonnull ListeningScheduledExecutorService executorService) {
    +202    return acquire(
    +203      baseOptions,
    +204      FirestoreStubSettings.defaultTransportChannelProvider(),
    +205      FirestoreStubSettings.defaultCredentialsProviderBuilder().build(),
    +206      GrpcTransportOptions.newBuilder().build(),
    +207      executorService,
    +208      keyInstance,
    +209      messageInstance
    +210    );
    +211  }
    +212
    +213  /**
    +214   * Acquire an instance of the {@link FirestoreAdapter} and {@link FirestoreDriver}, customized for the provided
    +215   * `modelInstance` and `keyInstance`. This method variant allows specification of the full set of objects which
    +216   * govern the connection and interaction with Firestore.
    +217   *
    +218   * @param baseOptions Base options to apply to the Firestore driver.
    +219   * @param firestoreChannel Transport provider for Firestore communication channels via gRPC.
    +220   * @param credentialsProvider Provider for transport/call credentials, when interacting with Firestore.
    +221   * @param transportOptions gRPC transport options, to apply when instantiating channels for Firestore communications.
    +222   * @param executorService Background executor service for Firestore operations.
    +223   * @param keyInstance Key type instance for the record in question.
    +224   * @param messageInstance Message type instance for the record in question.
    +225   * @param <K> Key type.
    +226   * @param <M> Message type.
    +227   * @return Instance of the {@link FirestoreAdapter} and {@link FirestoreDriver}, customized as described.
    +228   */
    +229  public static @Nonnull <K extends Message, M extends Message> FirestoreAdapter<K, M> acquire(
    +230      @Nonnull FirestoreOptions.Builder baseOptions,
    +231      @Nonnull @GoogleAPIChannel(service = GoogleService.FIRESTORE) TransportChannelProvider firestoreChannel,
    +232      @Nonnull CredentialsProvider credentialsProvider,
    +233      @Nonnull GrpcTransportOptions transportOptions,
    +234      @Nonnull ListeningScheduledExecutorService executorService,
    +235      @Nonnull K keyInstance,
    +236      @Nonnull M messageInstance) {
    +237    Message.Builder builder = messageInstance.newBuilderForType();
    +238    return FirestoreAdapter.FirestoreAdapterFactory.acquire(
    +239        builder,
    +240        FirestoreDriver.FirestoreDriverFactory.acquireDriver(
    +241            baseOptions,
    +242            firestoreChannel,
    +243            credentialsProvider,
    +244            transportOptions,
    +245            executorService,
    +246            messageInstance
    +247        ),
    +248        Optional.empty()
    +249    );
    +250  }
    +251
    +252  // -- Components -- //
    +253  /** {@inheritDoc} */
    +254  @Override
    +255  public @Nonnull ModelCodec<Model, CollapsedMessage, DocumentSnapshot> codec() {
    +256    return this.codec;
    +257  }
    +258
    +259  /** {@inheritDoc} */
    +260  @Override
    +261  public @Nonnull Optional<CacheDriver<Key, Model>> cache() {
    +262    return this.cache;
    +263  }
    +264
    +265  /** {@inheritDoc} */
    +266  @Override
    +267  public @Nonnull DatabaseDriver<Key, Model, DocumentSnapshot, CollapsedMessage> engine() {
    +268    return this.driver;
    +269  }
    +270
    +271  /** {@inheritDoc} */
    +272  @Override
    +273  public @Nonnull ListeningScheduledExecutorService executorService() {
    +274    return driver.executorService();
    +275  }
    +276}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/firestore/FirestoreDriver.html b/docs/java/src-html/gust/backend/driver/firestore/FirestoreDriver.html new file mode 100644 index 000000000..e5ee9489a --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/firestore/FirestoreDriver.html @@ -0,0 +1,580 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.firestore;
    +014
    +015import com.google.api.gax.core.CredentialsProvider;
    +016import com.google.api.gax.rpc.TransportChannelProvider;
    +017import com.google.cloud.Timestamp;
    +018import com.google.cloud.firestore.*;
    +019import com.google.cloud.grpc.GrpcTransportOptions;
    +020import com.google.common.base.Function;
    +021import com.google.common.collect.ImmutableMap;
    +022import com.google.common.util.concurrent.Futures;
    +023import com.google.common.util.concurrent.ListeningScheduledExecutorService;
    +024import com.google.protobuf.FieldMask;
    +025import com.google.protobuf.Message;
    +026import com.google.protobuf.ProtocolStringList;
    +027import gust.backend.model.*;
    +028import gust.backend.runtime.Logging;
    +029import gust.backend.runtime.ReactiveFuture;
    +030import gust.backend.transport.GoogleAPIChannel;
    +031import gust.backend.transport.GoogleService;
    +032import io.micronaut.context.annotation.Context;
    +033import io.micronaut.context.annotation.Factory;
    +034import io.micronaut.runtime.context.scope.Refreshable;
    +035import org.slf4j.Logger;
    +036import tools.elide.core.Datamodel;
    +037import tools.elide.core.DatapointType;
    +038import tools.elide.core.FieldType;
    +039
    +040import javax.annotation.Nonnull;
    +041import javax.annotation.Nullable;
    +042import javax.annotation.concurrent.Immutable;
    +043import javax.annotation.concurrent.ThreadSafe;
    +044import java.io.IOException;
    +045import java.io.PrintWriter;
    +046import java.io.StringWriter;
    +047import java.util.*;
    +048import java.util.concurrent.ExecutorService;
    +049
    +050import static gust.backend.model.ModelMetadata.enforceRole;
    +051import static gust.backend.model.ModelMetadata.modelAnnotation;
    +052import static gust.backend.model.ModelMetadata.idField;
    +053import static gust.backend.model.ModelMetadata.keyField;
    +054import static gust.backend.model.ModelMetadata.spliceBuilder;
    +055import static gust.backend.model.ModelMetadata.annotatedField;
    +056import static gust.backend.model.ModelMetadata.id;
    +057
    +058
    +059/**
    +060 * Defines a built-in framework {@link DatabaseDriver} for interacting seamlessly with Google Cloud Firestore. This
    +061 * enables Firestore-based persistence for any {@link Message}-derived (schema-driven) business model in a given Gust
    +062 * app's ecosystem.
    +063 *
    +064 * <p>Model storage can be deeply customized on a per-model basis, thanks to the built-in proto annotations available
    +065 * in <code>gust.core</code>. The Firestore adapter supports basic persistence (i.e. as a regular
    +066 * <pre>PersistenceDriver</pre>), but also supports generic, object index-style queries.</p>
    +067 *
    +068 * <p><b>Caching</b> may be facilitated by any compliant cache driver, via the main Firestore adapter.</p>
    +069 *
    +070 * @see FirestoreAdapter main adapter interface for Firestore.
    +071 * @see FirestoreManager logic and connection manager for Firestore.
    +072 * @see FirestoreTransportConfig configuration class for Firestore access.
    +073 */
    +074@Immutable
    +075@ThreadSafe
    +076@SuppressWarnings({"UnstableApiUsage", "OptionalUsedAsFieldOrParameterType"})
    +077public final class FirestoreDriver<Key extends Message, Model extends Message>
    +078  implements DatabaseDriver<Key, Model, DocumentSnapshot, CollapsedMessage> {
    +079  /** Private log pipe. */
    +080  private static final Logger logging = Logging.logger(FirestoreDriver.class);
    +081
    +082  /** Whether to run operations in a transactional by default. */
    +083  private static final Boolean defaultTransactional = true;
    +084
    +085  /** Executor service to use for async calls. */
    +086  private final ListeningScheduledExecutorService executorService;
    +087
    +088  /** Codec to use for serializing/de-serializing models. */
    +089  private final ModelCodec<Model, CollapsedMessage, DocumentSnapshot> codec;
    +090
    +091  /** Firestore client engine. */
    +092  private final Firestore engine;
    +093
    +094  /** Deserializes Firestore {@link DocumentSnapshot} instances to {@link Message} instances. */
    +095  final static class DocumentSnapshotDeserializer<M extends Message> implements ModelDeserializer<DocumentSnapshot, M> {
    +096    /** Encapsulated object deserializer. */
    +097    private final ObjectModelDeserializer<M> objectDeserializer;
    +098
    +099    /**
    +100     * Private constructor.
    +101     *
    +102     * @param instance Model instance to deserialize.
    +103     */
    +104    private DocumentSnapshotDeserializer(@Nonnull M instance) {
    +105      this.objectDeserializer = ObjectModelDeserializer.defaultInstance(instance);
    +106    }
    +107
    +108    /**
    +109     * Construct a {@link DocumentSnapshot} deserializer for the provided <b>instance</b>.
    +110     *
    +111     * @param instance Model instance to acquire a snapshot deserializer for.
    +112     * @param <M> Model type to deserialize.
    +113     * @return Snapshot deserializer instance.
    +114     */
    +115    static <M extends Message> DocumentSnapshotDeserializer<M> forModel(@Nonnull M instance) {
    +116      return new DocumentSnapshotDeserializer<>(instance);
    +117    }
    +118
    +119    /** @inheritDoc */
    +120    @Override
    +121    public @Nonnull M inflate(@Nonnull DocumentSnapshot documentSnapshot) throws ModelInflateException {
    +122      return objectDeserializer.inflate(Objects.requireNonNull(documentSnapshot.getData()));
    +123    }
    +124  }
    +125
    +126  /** Factory responsible for creating {@link FirestoreDriver} instances from injected dependencies. */
    +127  @Factory
    +128  final static class FirestoreDriverFactory {
    +129    /**
    +130     * Acquire a new instance of the Firestore driver, using the specified configuration settings, and the specified
    +131     * injected channel.
    +132     *
    +133     * @param baseOptions Base options to apply to the Firestore driver.
    +134     * @param firestoreChannel Managed gRPC channel provider.
    +135     * @param credentialsProvider Transport credentials provider. Generally calls into ADC.
    +136     * @param transportOptions Options to apply to the Firestore channel.
    +137     * @param executorService Executor service to use when executing calls.
    +138     * @return Firestore driver instance.
    +139     */
    +140    @Context
    +141    @Refreshable
    +142    public static @Nonnull <K extends Message, M extends Message> FirestoreDriver<K, M> acquireDriver(
    +143      @Nonnull FirestoreOptions.Builder baseOptions,
    +144      @Nonnull @GoogleAPIChannel(service = GoogleService.FIRESTORE) TransportChannelProvider firestoreChannel,
    +145      @Nonnull CredentialsProvider credentialsProvider,
    +146      @Nonnull GrpcTransportOptions transportOptions,
    +147      @Nonnull ListeningScheduledExecutorService executorService,
    +148      @Nonnull M instance) {
    +149      return new FirestoreDriver<>(
    +150        baseOptions,
    +151        firestoreChannel,
    +152        credentialsProvider,
    +153        transportOptions,
    +154        executorService,
    +155        CollapsedMessageCodec.forModel(instance, DocumentSnapshotDeserializer.forModel(instance)));
    +156    }
    +157  }
    +158
    +159  /**
    +160   * Construct a new Firestore driver from scratch.
    +161   *
    +162   * @param baseOptions Base options to apply to the Firestore driver.
    +163   * @param channelProvider Managed gRPC channel to use for Firestore RPCAPI interactions.
    +164   * @param credentialsProvider Transport credentials provider.
    +165   * @param transportOptions Options to apply to the transport layer.
    +166   * @param executorService Executor service to use when executing calls.
    +167   * @param codec Model codec to use with this driver.
    +168   */
    +169  private FirestoreDriver(@Nonnull FirestoreOptions.Builder baseOptions,
    +170                          @Nonnull TransportChannelProvider channelProvider,
    +171                          @Nonnull CredentialsProvider credentialsProvider,
    +172                          @Nonnull GrpcTransportOptions transportOptions,
    +173                          @Nonnull ListeningScheduledExecutorService executorService,
    +174                          @Nonnull ModelCodec<Model, CollapsedMessage, DocumentSnapshot> codec) {
    +175    this.codec = codec;
    +176    this.executorService = executorService;
    +177    FirestoreOptions firestoreOptions = baseOptions
    +178      .setChannelProvider(channelProvider)
    +179      .setCredentialsProvider(credentialsProvider)
    +180      .setTransportOptions(transportOptions)
    +181      .build();
    +182
    +183    if (logging.isDebugEnabled())
    +184      logging.debug(String.format("Initializing Firestore driver with options:\n%s", firestoreOptions));
    +185    this.engine = firestoreOptions.getService();
    +186  }
    +187
    +188  /**
    +189   * Deserialize the provided document snapshot, into an instance of the message we manage through this instance of the
    +190   * {@link FirestoreDriver}.
    +191   *
    +192   * @param snapshot Document snapshot to de-serialize.
    +193   * @return Inflated object record, or {@link Optional#empty()}.
    +194   */
    +195  private @Nonnull Model deserialize(@Nonnull DocumentSnapshot snapshot) {
    +196    try {
    +197      return this.codec.deserialize(snapshot);
    +198    } catch (IOException ioe) {
    +199      var buf = new StringWriter();
    +200      var printer = new PrintWriter(buf);
    +201      ioe.printStackTrace(printer);
    +202      logging.error("Failed to deserialize model: '" + ioe.getMessage() + "'.\n" + buf);
    +203      throw new RuntimeException(ioe);
    +204    }
    +205  }
    +206
    +207  // -- Getters -- //
    +208  /** {@inheritDoc} */
    +209  @Override
    +210  public @Nonnull ListeningScheduledExecutorService executorService() {
    +211    return this.executorService;
    +212  }
    +213
    +214  /** {@inheritDoc} */
    +215  @Nonnull
    +216  @Override
    +217  public ModelCodec<Model, CollapsedMessage, DocumentSnapshot> codec() {
    +218    return this.codec;
    +219  }
    +220
    +221  /**
    +222   * Convert a model `Key` into a Firestore {@link DocumentReference}.
    +223   *
    +224   * @param keyInstance Key instance to convert into a ref.
    +225   * @return Computed document reference.
    +226   */
    +227  private @Nonnull DocumentReference ref(@Nonnull Message keyInstance) {
    +228    if (logging.isDebugEnabled())
    +229      logging.debug("Creating Firestore ref from key instance '" + keyInstance.toString() + "'.");
    +230    enforceRole(keyInstance, DatapointType.OBJECT_KEY);
    +231    var keyDescriptor = keyInstance.getDescriptorForType();
    +232
    +233    // first: resolve the key's model path
    +234    String resolvedPath;
    +235    var explicitPath = modelAnnotation(keyDescriptor, Datamodel.db, false);
    +236
    +237    if (explicitPath.isPresent() && !explicitPath.get().getPath().isEmpty()) {
    +238      resolvedPath = explicitPath.get().getPath();
    +239      if (logging.isTraceEnabled())
    +240        logging.trace("Explicit path found for type '"
    +241            + keyDescriptor.getFullName() + "'. Using '" + resolvedPath + "'.");
    +242    } else {
    +243      // `PersonKey` -> `persons`
    +244      resolvedPath = keyInstance
    +245          .getDescriptorForType()
    +246          .getName()
    +247          .toLowerCase()
    +248          .replace("key", "")
    +249          + "s";
    +250
    +251      if (logging.isTraceEnabled())
    +252        logging.trace("No explicit path found for type '"
    +253            + keyDescriptor.getFullName() + "'. Resolved as '" + resolvedPath + "'.");
    +254    }
    +255
    +256    // second: resolve the key's ID
    +257    var resolvedId = id(keyInstance);
    +258    Optional<String> targetId = resolvedId.map(Object::toString);
    +259
    +260    // third: resolve the model's parent, if applicable
    +261    var parentField = annotatedField(
    +262        keyDescriptor,
    +263        Datamodel.field,
    +264        false,
    +265        Optional.of((field) -> field.getType() == FieldType.PARENT));
    +266
    +267    if (parentField.isPresent()) {
    +268      var parentInstance = ModelMetadata.pluck(keyInstance, parentField.get());
    +269      if (parentInstance.getValue().isPresent()) {
    +270        var parentKey = this.ref((Message)parentInstance.getValue().get());
    +271
    +272        DocumentReference ref = targetId
    +273            .map(s -> parentKey.collection(resolvedPath).document(s))
    +274            .orElseGet(() -> parentKey.collection(resolvedPath).document());
    +275
    +276        if (logging.isDebugEnabled())
    +277          logging.debug("Generated document reference with parent: '" + ref.toString() + "'.");
    +278        return ref;
    +279      } else {
    +280        // no parent present when one is required: fail
    +281        throw new IllegalStateException("Cannot persist key with missing parent when one is requred.");
    +282      }
    +283    } else {
    +284      // build a document reference with no parent
    +285      DocumentReference ref = targetId
    +286          .map(s -> engine.collection(resolvedPath).document(s))
    +287          .orElseGet(() -> engine.collection(resolvedPath).document());
    +288
    +289      if (logging.isDebugEnabled())
    +290        logging.debug("Generated document reference with no parent: '" + ref.toString() + "'.");
    +291      return ref;
    +292    }
    +293  }
    +294
    +295  /**
    +296   * Convert a path and prefix into a Firestore {@link DocumentReference}.
    +297   *
    +298   * @param path Path in Firestore for the document.
    +299   * @param prefix Global prefix to apply to the path.
    +300   * @return Computed document reference.
    +301   */
    +302  private @Nonnull DocumentReference ref(@Nonnull String path, @Nullable String prefix) {
    +303    if (prefix != null) {
    +304      return engine.document(prefix + path);
    +305    } else {
    +306      return engine.document(path);
    +307    }
    +308  }
    +309
    +310  // -- API: Key Generation -- //
    +311  /** {@inheritDoc} */
    +312  @Override
    +313  public @Nonnull Key generateKey(@Nonnull Message instance) {
    +314    var fieldPointer = keyField(instance);
    +315    if (fieldPointer.isEmpty())
    +316      throw new IllegalArgumentException("Failed to resolve key field for message '"
    +317          + instance.getDescriptorForType().getFullName() + "'.");
    +318
    +319    var keyBuilder = instance.toBuilder().getFieldBuilder(fieldPointer.get().getField());
    +320    var idPointer = idField(keyBuilder.getDescriptorForType());
    +321    if (idPointer.isEmpty())
    +322      throw new IllegalArgumentException("Failed to resolve key ID field for key message type '"
    +323          + keyBuilder.getDescriptorForType().getFullName() + "'.");
    +324
    +325    spliceBuilder(
    +326        keyBuilder,
    +327        idPointer.get(),
    +328        Optional.of(UUID.randomUUID().toString().toUpperCase()));
    +329
    +330    //noinspection unchecked
    +331    return (Key)keyBuilder.build();
    +332  }
    +333
    +334  /** @return Converted Firestore field mask from the provided Protobuf mask. */
    +335  private @Nullable com.google.cloud.firestore.FieldMask convertMask(@Nonnull FieldMask originalMask) {
    +336    ProtocolStringList paths = originalMask.getPathsList();
    +337    if (!paths.isEmpty()) {
    +338      if (logging.isDebugEnabled())
    +339        logging.debug("Applying field mask for Firestore operation: \n" + originalMask.toString());
    +340
    +341      ArrayList<String> pathsList = new ArrayList<>(paths.size());
    +342      var count = originalMask.getPathsCount();
    +343      for (int i = 0; i < count; i++) {
    +344        pathsList.add(originalMask.getPaths(i));
    +345      }
    +346
    +347      String[] pathsArr = new String[pathsList.size()];
    +348      pathsList.toArray(pathsArr);
    +349
    +350      return com.google.cloud.firestore.FieldMask.of(pathsArr);
    +351    }
    +352    return null;
    +353  }
    +354
    +355  /** @return Preconditions for the provided operational options. */
    +356  private @Nonnull Precondition generatePreconditions(@Nonnull OperationOptions options) {
    +357      var updatedTimestamp = options.updatedAtMicros()
    +358          .map(Timestamp::ofTimeMicroseconds)
    +359          .orElseGet(() -> options.updatedAtSeconds()
    +360              .map((secs) -> Timestamp.ofTimeSecondsAndNanos(secs, 0))
    +361              .orElse(null));
    +362
    +363      if (updatedTimestamp == null) {
    +364        return Precondition.NONE;
    +365      } else {
    +366        return Precondition.updatedAt(updatedTimestamp);
    +367      }
    +368  }
    +369
    +370  /** @return Fetched model, with the provided field mask enforced. */
    +371  private @Nonnull Model enforceMask(@Nonnull Model instance, @Nullable Optional<FieldMask> mask) {
    +372    // nothing from nothing
    +373    Objects.requireNonNull(instance, "model instance should not be null for mask enforcement");
    +374    // @TODO: field mask enforcement
    +375    return instance;
    +376  }
    +377
    +378  // -- API: Fetch -- //
    +379  /** {@inheritDoc} */
    +380  @Override
    +381  public @Nonnull ReactiveFuture<Optional<Model>> retrieve(@Nonnull Key key, @Nonnull FetchOptions opts) {
    +382    Objects.requireNonNull(key, "Cannot fetch model with `null` for key.");
    +383    Objects.requireNonNull(opts, "Cannot fetch model without `options`.");
    +384    enforceRole(key, DatapointType.OBJECT_KEY);
    +385    id(key).orElseThrow(() -> new IllegalArgumentException("Cannot fetch model with empty key."));
    +386
    +387    ExecutorService exec = opts.executorService().orElseGet(this::executorService);
    +388    DocumentReference[] refs = {ref(key)};
    +389    FieldMask mask = opts.fieldMask().orElse(null);
    +390    var that = this;
    +391
    +392    if (opts.transactional().orElse(defaultTransactional)) {
    +393      return ReactiveFuture.wrap(Futures.transform(ReactiveFuture.wrap(engine.runAsyncTransaction((txn) ->
    +394          txn.getAll(refs, mask != null ? this.convertMask(mask) : null),
    +395          TransactionOptions.createReadOnlyOptionsBuilder()
    +396              .setExecutor(exec)
    +397              .setReadTime(opts.snapshot()
    +398                  .map((secs) -> com.google.protobuf.Timestamp.newBuilder()
    +399                    .setSeconds(secs))
    +400                  .orElse(null))
    +401              .build())), documentSnapshots -> {
    +402        // check for results
    +403        if (documentSnapshots != null && !documentSnapshots.isEmpty() && documentSnapshots.get(0).exists()) {
    +404          return Optional.of(that.deserialize(documentSnapshots.get(0)));
    +405        } else {
    +406          // otherwise return an empty optional
    +407          return Optional.empty();
    +408        }
    +409      }, exec));
    +410
    +411    } else {
    +412      return ReactiveFuture.wrap(Futures.transform(ReactiveFuture.wrap(
    +413          engine.getAll(
    +414              refs,
    +415              mask != null ? this.convertMask(mask) : null
    +416          ),
    +417          exec), new Function<>() {
    +418        @Override
    +419        public @Nonnull Optional<Model> apply(@Nullable List<DocumentSnapshot> documentSnapshots) {
    +420          if (documentSnapshots == null || documentSnapshots.isEmpty() || (
    +421              documentSnapshots.size() == 1 && !documentSnapshots.get(0).exists())) {
    +422            return Optional.empty();
    +423
    +424          } else if (documentSnapshots.size() > 1) {
    +425            throw new IllegalStateException("Unexpectedly encountered more than 1 result.");
    +426
    +427          } else {
    +428            return Optional.of(that.enforceMask(deserialize(
    +429                Objects.requireNonNull(
    +430                    documentSnapshots.get(0),
    +431                    "Unexpected null `DocumentReference`.")), opts.fieldMask()));
    +432          }
    +433        }
    +434      }, exec), exec);
    +435    }
    +436  }
    +437
    +438  // -- API: Persist -- //
    +439  /** {@inheritDoc} */
    +440  @Override
    +441  public @Nonnull ReactiveFuture<Model> persist(@Nonnull Key key,
    +442                                                @Nonnull Model model,
    +443                                                @Nonnull WriteOptions options) {
    +444    Objects.requireNonNull(key, "Cannot write model with `null` for key.");
    +445    Objects.requireNonNull(model, "Cannot write model which is, itself, `null`.");
    +446    Objects.requireNonNull(options, "Cannot write model without `options`.");
    +447    enforceRole(key, DatapointType.OBJECT_KEY);
    +448    ExecutorService exec = options.executorService().orElseGet(this::executorService);
    +449
    +450    try {
    +451      // collapse the model
    +452      var serialized = codec.serialize(model);
    +453      var that = this;
    +454
    +455      return ReactiveFuture.wrap(engine.runTransaction(transaction -> {
    +456        serialized.persist(null, new WriteProxy<DocumentReference>() {
    +457          @Override
    +458          public @Nonnull DocumentReference ref(@Nonnull String path, @Nullable String prefix) {
    +459            return that.ref(path, prefix);
    +460          }
    +461
    +462          @Override
    +463          public void put(@Nonnull DocumentReference key, @Nonnull SerializedModel message) {
    +464            transaction.set(key, ImmutableMap.copyOf(message.getData()));
    +465          }
    +466
    +467          @Override
    +468          public void create(@Nonnull DocumentReference key, @Nonnull SerializedModel message) {
    +469            transaction.create(key, ImmutableMap.copyOf(message.getData()));
    +470          }
    +471
    +472          @Override
    +473          public void update(@Nonnull DocumentReference key, @Nonnull SerializedModel message) {
    +474            transaction.update(key, ImmutableMap.copyOf(message.getData()));
    +475          }
    +476        });
    +477        return model;
    +478      }, TransactionOptions.createReadWriteOptionsBuilder()
    +479          .setExecutor(exec)
    +480          .setNumberOfAttempts(options.retries().orElse(2))
    +481          .build()));
    +482
    +483    } catch (IOException ioe) {
    +484      throw new IllegalStateException(ioe);
    +485    }
    +486  }
    +487
    +488  // -- API: Delete -- //
    +489  /** {@inheritDoc} */
    +490  @Override
    +491  public @Nonnull ReactiveFuture<Key> delete(@Nonnull Key key, @Nonnull DeleteOptions options) {
    +492    Objects.requireNonNull(key, "Cannot delete model with `null` for key.");
    +493    Objects.requireNonNull(options, "Cannot delete model without `options`.");
    +494    enforceRole(key, DatapointType.OBJECT_KEY);
    +495    id(key).orElseThrow(() -> new IllegalArgumentException("Cannot delete model with empty key."));
    +496    ExecutorService exec = options.executorService().orElseGet(this::executorService);
    +497
    +498    return ReactiveFuture.wrap(engine.runTransaction(transaction -> {
    +499      transaction.delete(ref(key), generatePreconditions(options));
    +500      return key;
    +501    }, TransactionOptions.createReadWriteOptionsBuilder()
    +502      .setExecutor(exec)
    +503      .setNumberOfAttempts(options.retries().orElse(2))
    +504      .build()));
    +505  }
    +506}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/firestore/FirestoreManager.html b/docs/java/src-html/gust/backend/driver/firestore/FirestoreManager.html new file mode 100644 index 000000000..6b8a89a95 --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/firestore/FirestoreManager.html @@ -0,0 +1,98 @@ + + + +Source code + + + +
    + +
    + + diff --git a/docs/java/src-html/gust/backend/driver/firestore/FirestoreTransportConfig.html b/docs/java/src-html/gust/backend/driver/firestore/FirestoreTransportConfig.html new file mode 100644 index 000000000..94a7ceccd --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/firestore/FirestoreTransportConfig.html @@ -0,0 +1,199 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.firestore;
    +014
    +015import gust.backend.transport.GoogleTransportManager;
    +016import gust.backend.transport.GrpcTransportConfig;
    +017import io.micronaut.context.annotation.ConfigurationProperties;
    +018
    +019import java.time.Duration;
    +020import javax.annotation.Nonnull;
    +021import javax.validation.constraints.Min;
    +022import javax.validation.constraints.NotBlank;
    +023
    +024
    +025/**
    +026 * Specifies configuration property bindings for a managed transport channel interacting with Cloud Firestore. Also in
    +027 * charge of supplying a sensible set of defaults, if no config properties are specified.
    +028 *
    +029 * <p>Configurable aspects of the framework's connection to Firestore include keepalive timings, retries, connection
    +030 * pooling, connection refreshing, and more. All of these may be customized via a number of code paths:
    +031 * <ul>
    +032 *   <li><b>Environment:</b> By default, Micronaut will automatically merge config with environment vars.</li>
    +033 *   <li><b>Config:</b> You can configure things under the <pre>transport.google.firestore</pre> prefix.</li>
    +034 *   <li><b>Bean events:</b> You can watch for a bean event creating this object, and use the methods on it to change
    +035 *       configured values before they are used.</li>
    +036 * </ul></p>
    +037 */
    +038@SuppressWarnings({"WeakerAccess", "FieldCanBeLocal"})
    +039@ConfigurationProperties(FirestoreTransportConfig.CONFIG_PREFIX)  // `transport.google.firestore`
    +040public final class FirestoreTransportConfig implements GrpcTransportConfig {
    +041  // -- Paths -- //
    +042
    +043  /** Token under `transport.google` to look for Firestore config. */
    +044  private final static String CONFIG_TOKEN = "firestore";
    +045
    +046  /** Path prefix at which the Firestore transport layer may be configured. */
    +047  public final static String CONFIG_PREFIX = GoogleTransportManager.CONFIG_PREFIX + "." + CONFIG_TOKEN;
    +048
    +049  // -- Defaults -- //
    +050
    +051  /** Specifies the default number of connections to maintain to Firestore. */
    +052  public final static int DEFAULT_POOL_SIZE = 2;
    +053
    +054  /** Whether to enable keepalive features by default. */
    +055  public final static boolean DEFAULT_ENABLE_KEEPALIVE = true;
    +056
    +057  /** Length of time in between keepalive pings. */
    +058  public final static Duration DEFAULT_KEEPALIVE_TIME = Duration.ofMinutes(3);
    +059
    +060  /** Amount of time to wait before ending the keepalive stream. */
    +061  public final static Duration DEFAULT_KEEPALIVE_TIMEOUT = Duration.ofMinutes(15);
    +062
    +063  /** Whether to keep the connection alive, even if there is no activity. */
    +064  public final static boolean DEFAULT_KEEPALIVE_NO_ACTIVITY = true;
    +065
    +066  /** Default Firestore endpoint. */
    +067  public final static String DEFAULT_FIRESTORE_ENDPOINT = "firestore.googleapis.com";
    +068
    +069  // -- Configuration Properties -- //
    +070
    +071  /** Size of the connection pool for Firestore. */
    +072  private @Min(1) int poolSize = DEFAULT_POOL_SIZE;
    +073
    +074  /** Whether to enable keepalive features. */
    +075  private boolean keepaliveEnabled = DEFAULT_ENABLE_KEEPALIVE;
    +076
    +077  /** Length of time in between keepalive pings. */
    +078  private @Nonnull Duration keepaliveTime = DEFAULT_KEEPALIVE_TIME;
    +079
    +080  /** Amount of time to wait before ending the keepalive stream. */
    +081  private @Nonnull Duration keepaliveTimeout = DEFAULT_KEEPALIVE_TIMEOUT;
    +082
    +083  /** Whether to keep the connection alive, even if there is no activity. */
    +084  private boolean keepaliveNoActivity = DEFAULT_KEEPALIVE_NO_ACTIVITY;
    +085
    +086  /** Endpoint at which to connect to Firestore. */
    +087  private @NotBlank @Nonnull String firestoreEndpoint = DEFAULT_FIRESTORE_ENDPOINT;
    +088
    +089  // -- Interface Compliance -- //
    +090  /** @return gRPC endpoint at which to connect to the target service. */
    +091  @Override
    +092  public @Nonnull String endpoint() {
    +093    return firestoreEndpoint;
    +094  }
    +095
    +096  /** @return Retrieve the desired connection pool size. */
    +097  @Override
    +098  public @Nonnull Integer getPoolSize() {
    +099    return poolSize;
    +100  }
    +101
    +102  /** @return Whether to enable keepalive features. */
    +103  @Override
    +104  public @Nonnull Boolean getKeepaliveEnabled() {
    +105    return keepaliveEnabled;
    +106  }
    +107
    +108  /** @return Keep-alive time. */
    +109  @Override
    +110  public @Nonnull Duration getKeepaliveTime() {
    +111    return keepaliveTime;
    +112  }
    +113
    +114  /** @return Keep-alive timeout. */
    +115  @Override
    +116  public @Nonnull Duration getKeepaliveTimeout() {
    +117    return keepaliveTimeout;
    +118  }
    +119
    +120  /** @return Whether to keep-alive even when there is no activity. */
    +121  @Override
    +122  public @Nonnull Boolean getKeepAliveNoActivity() {
    +123    return keepaliveNoActivity;
    +124  }
    +125}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/inmemory/InMemoryAdapter.html b/docs/java/src-html/gust/backend/driver/inmemory/InMemoryAdapter.html new file mode 100644 index 000000000..9ed191301 --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/inmemory/InMemoryAdapter.html @@ -0,0 +1,211 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.inmemory;
    +014
    +015import com.google.common.util.concurrent.ListeningScheduledExecutorService;
    +016import com.google.protobuf.Message;
    +017import gust.backend.model.*;
    +018
    +019import javax.annotation.Nonnull;
    +020import java.util.Optional;
    +021import java.util.concurrent.ExecutorService;
    +022
    +023
    +024/**
    +025 * Reference implementation of a {@link ModelAdapter}. Stores persisted models in a static concurrent hash map. It is
    +026 * not a good idea to use this in production, under any circumstances (especially because there is no persistence across
    +027 * restarts or between hosts).
    +028 *
    +029 * <p>This adapter can use any model codec, and any cache driver, in front of its storage operations. The backing map
    +030 * stores entities as opaque blobs, so it doesn't care how they are serialized or inflated. Queries are not supported by
    +031 * this engine.</p>
    +032 */
    +033@SuppressWarnings("UnstableApiUsage")
    +034public final class InMemoryAdapter<Key extends Message, Model extends Message>
    +035  implements ModelAdapter<Key, Model, EncodedModel, EncodedModel> {
    +036  /** Specifies the format to use. One of `BINARY`, `JSON`, or `TEXT`. */
    +037  private static final EncodingMode FORMAT = EncodingMode.BINARY;
    +038
    +039  /** Driver for this in-memory adapter. */
    +040  private final @Nonnull InMemoryDriver<Key, Model> driver;
    +041
    +042  /** Codec in use for model serialization/de-serialization activities. */
    +043  private final @Nonnull ModelCodec<Model, EncodedModel, EncodedModel> codec;
    +044
    +045  /** Cache to use for model interactions through this adapter (optional). */
    +046  private final @Nonnull Optional<CacheDriver<Key, Model>> cache;
    +047
    +048  /**
    +049   * Private constructor - create an in-memory adapter from scratch.
    +050   *
    +051   * @param keyInstance Empty instance of the attached model's key.
    +052   * @param codec Model codec to use with this adapter (when serializing/de-serializing instances).
    +053   * @param cache Caching driver to use with this adapter (optional).
    +054   * @param executorService Executor service to use for storage operations.
    +055   */
    +056  @SuppressWarnings("unused")
    +057  private InMemoryAdapter(@Nonnull Key keyInstance,
    +058                          @Nonnull ModelCodec<Model, EncodedModel, EncodedModel> codec,
    +059                          @Nonnull Optional<CacheDriver<Key, Model>> cache,
    +060                          @Nonnull ListeningScheduledExecutorService executorService) {
    +061    this.cache = cache;
    +062    this.codec = codec;
    +063    this.driver = InMemoryDriver.acquire(codec, executorService);
    +064  }
    +065
    +066  /**
    +067   * Acquire an instance of the {@link InMemoryAdapter}, specialized for the provided empty model instance.
    +068   *
    +069   * <p>An empty instance can easily be acquired for any given model, via {@link Message#getDefaultInstanceForType()}.
    +070   * The instance is used only for builder-spawning and type information. The provided {@link ExecutorService} is used
    +071   * for model codec activities and callback dispatch.</p>
    +072   *
    +073   * @param keyInstance Empty instance of the key type for <pre>instance</pre>.
    +074   * @param instance Empty model instance with which to spawn new builders, and resolve type information.
    +075   * @param executorService Executor to use for callbacks and model codec activities.
    +076   * @param <M> Type of model for which an {@link InMemoryAdapter} is being requested.
    +077   * @return Instance of an in-memory data adapter for the provided model.
    +078   * @throws InvalidModelType If the specified model is not meant to be used for storage.
    +079   */
    +080  public static @Nonnull <K extends Message, M extends Message> InMemoryAdapter<K, M> acquire(
    +081    @Nonnull K keyInstance,
    +082    @Nonnull M instance,
    +083    @Nonnull ListeningScheduledExecutorService executorService) throws InvalidModelType {
    +084    return acquire(keyInstance, instance, Optional.empty(), executorService);
    +085  }
    +086
    +087  /**
    +088   * Acquire an instance of the {@link InMemoryAdapter}, specialized for the provided empty model instance, optionally
    +089   * specifying a {@link CacheDriver} to use.
    +090   *
    +091   * <p>An empty instance can easily be acquired for any given model, via {@link Message#getDefaultInstanceForType()}.
    +092   * The instance is used only for builder-spawning and type information. The provided {@link ExecutorService} is used
    +093   * for model codec activities and callback dispatch.</p>
    +094   *
    +095   * <p>If {@link Optional#empty()}</p> is passed as the {@code cache}, no caching will take place. If a valid
    +096   * {@link CacheDriver} instance is provided, it will be used only if {@code options} on a request allow for it
    +097   * (caching defaults to being active).</p>
    +098   *
    +099   * @param keyInstance Empty instance of the key type for <pre>instance</pre>.
    +100   * @param instance Empty model instance with which to spawn new builders, and resolve type information.
    +101   * @param cache Cache driver to use for read-path code in the adapter.
    +102   * @param executorService Executor to use for callbacks and model codec activities.
    +103   * @param <M> Type of model for which an {@link InMemoryAdapter} is being requested.
    +104   * @return Instance of an in-memory data adapter for the provided model.
    +105   * @throws InvalidModelType If the specified model is not meant to be used for storage.
    +106   */
    +107  public static @Nonnull <K extends Message, M extends Message> InMemoryAdapter<K, M> acquire(
    +108    @Nonnull K keyInstance,
    +109    @Nonnull M instance,
    +110    @Nonnull Optional<CacheDriver<K, M>> cache,
    +111    @Nonnull ListeningScheduledExecutorService executorService) throws InvalidModelType {
    +112    return new InMemoryAdapter<>(
    +113      keyInstance,
    +114      ProtoModelCodec.forModel(instance, FORMAT),
    +115      cache,
    +116      executorService);
    +117  }
    +118
    +119  // -- Components -- //
    +120  /** {@inheritDoc} */
    +121  @Override
    +122  public @Nonnull ModelCodec<Model, EncodedModel, EncodedModel> codec() {
    +123    return this.codec;
    +124  }
    +125
    +126  /** {@inheritDoc} */
    +127  @Override
    +128  public @Nonnull Optional<CacheDriver<Key, Model>> cache() {
    +129    return this.cache;
    +130  }
    +131
    +132  /** {@inheritDoc} */
    +133  @Override
    +134  public @Nonnull InMemoryDriver<Key, Model> engine() {
    +135    return this.driver;
    +136  }
    +137}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/inmemory/InMemoryCache.html b/docs/java/src-html/gust/backend/driver/inmemory/InMemoryCache.html new file mode 100644 index 000000000..ec77d2b93 --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/inmemory/InMemoryCache.html @@ -0,0 +1,200 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.inmemory;
    +014
    +015import com.google.common.cache.Cache;
    +016import com.google.common.cache.CacheBuilder;
    +017import com.google.common.util.concurrent.ListeningScheduledExecutorService;
    +018import com.google.protobuf.Message;
    +019import gust.backend.model.*;
    +020import gust.backend.runtime.ReactiveFuture;
    +021
    +022import javax.annotation.Nonnull;
    +023import javax.annotation.concurrent.ThreadSafe;
    +024import java.time.Duration;
    +025import java.util.Optional;
    +026
    +027
    +028/**
    +029 * Defines a {@link CacheDriver} backed by a Guava in-memory cache, which statically holds onto cached full model
    +030 * instances, potentially on behalf of some other persistence driver (via use with a {@link ModelAdapter}).
    +031 *
    +032 * <p>Cache options may be adjusted based on the operation being memoized, using the {@link CacheOptions} interface,
    +033 * which is supported by various other higher-order options interfaces (i.e. {@link FetchOptions}).</p>
    +034 *
    +035 * @param <K> Type of key used with the cache and model.
    +036 * @param <M> Type of model supported by this cache facade.
    +037 */
    +038@ThreadSafe
    +039@SuppressWarnings("UnstableApiUsage")
    +040public final class InMemoryCache<K extends Message, M extends Message> implements CacheDriver<K, M> {
    +041  /** Static in-memory instance cache. */
    +042  private final static @Nonnull InMemoryCaching CACHE = new InMemoryCaching();
    +043
    +044  /** Responsible for managing static cache access. */
    +045  private final static class InMemoryCaching {
    +046    /** Internal backing cache storage. */
    +047    private final @Nonnull Cache<String, Message> inMemoryCache;
    +048
    +049    /** Initialize in-memory caching from scratch. */
    +050    private InMemoryCaching() {
    +051      inMemoryCache = CacheBuilder.newBuilder()
    +052        .concurrencyLevel(2)
    +053        .maximumSize(50)
    +054        .expireAfterWrite(Duration.ofHours(1))
    +055        .weakKeys()
    +056        .recordStats()
    +057        .build();
    +058    }
    +059
    +060    /**
    +061     * Acquire the in-memory static cache, which holds onto model instances without requiring serialization. In-memory
    +062     * caching may be used transparently with any backing {@link PersistenceDriver}.
    +063     *
    +064     * @return In-memory static model cache.
    +065     */
    +066    @Nonnull Cache<String, Message> acquire() {
    +067      return inMemoryCache;
    +068    }
    +069  }
    +070
    +071  /**
    +072   * Acquire an instance of the in-memory caching driver, generalized to support the provided key type {@code K} and
    +073   * model instance type {@code M}.
    +074   *
    +075   * @param <K> Generic type for the key associated with model type {@code M}.
    +076   * @param <M> Generic model type managed by this cache.
    +077   * @return Instance of the acquired cache engine.
    +078   */
    +079  public static @Nonnull <K extends Message, M extends Message> InMemoryCache<K, M> acquire() {
    +080    return new InMemoryCache<>();
    +081  }
    +082
    +083  /** {@inheritDoc} */
    +084  @Override
    +085  public @Nonnull ReactiveFuture put(@Nonnull Message key,
    +086                                     @Nonnull Message model,
    +087                                     @Nonnull ListeningScheduledExecutorService executor) {
    +088    final String id = (
    +089      ModelMetadata.<String>id(key).orElseThrow(() -> new IllegalArgumentException("Cannot add to cache with empty key.")));
    +090    return ReactiveFuture.wrap(executor.submit(() -> CACHE.acquire().put(id, model)), executor);
    +091  }
    +092
    +093  /** {@inheritDoc} */
    +094  @Override
    +095  public @Nonnull ReactiveFuture<Optional<M>> fetch(@Nonnull K key,
    +096                                                    @Nonnull FetchOptions options,
    +097                                                    @Nonnull ListeningScheduledExecutorService executor) {
    +098    final String id = (
    +099      ModelMetadata.<String>id(key).orElseThrow(() -> new IllegalArgumentException("Cannot fetch empty key.")));
    +100
    +101    return ReactiveFuture.wrap(options.executorService().orElse(executor).submit(() -> {
    +102      Message cached = (CACHE.acquire().getIfPresent(id));
    +103
    +104      //noinspection unchecked
    +105      return cached == null ? Optional.empty() : Optional.of((M)cached);
    +106    }), executor);
    +107  }
    +108
    +109  /** {@inheritDoc} */
    +110  @Override
    +111  public @Nonnull ReactiveFuture evict(@Nonnull K key, @Nonnull ListeningScheduledExecutorService executor) {
    +112    final String id = (
    +113      ModelMetadata.<String>id(key).orElseThrow(() -> new IllegalArgumentException("Cannot expire with empty key.")));
    +114
    +115    return ReactiveFuture.wrap(executor.submit(() -> CACHE.acquire().invalidate(id)), executor);
    +116  }
    +117
    +118  /** {@inheritDoc} */
    +119  @Override
    +120  public @Nonnull ReactiveFuture flush(@Nonnull ListeningScheduledExecutorService executor) {
    +121    return ReactiveFuture.wrap(executor.submit(() -> {
    +122      CACHE.acquire().invalidateAll();
    +123      CACHE.acquire().cleanUp();
    +124    }), executor);
    +125  }
    +126}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/inmemory/InMemoryDriver.html b/docs/java/src-html/gust/backend/driver/inmemory/InMemoryDriver.html new file mode 100644 index 000000000..d7cc1ac2e --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/inmemory/InMemoryDriver.html @@ -0,0 +1,337 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.inmemory;
    +014
    +015import com.google.common.util.concurrent.ListeningScheduledExecutorService;
    +016import com.google.errorprone.annotations.CanIgnoreReturnValue;
    +017import com.google.protobuf.Message;
    +018import gust.backend.model.*;
    +019import gust.backend.runtime.Logging;
    +020import gust.backend.runtime.ReactiveFuture;
    +021import org.slf4j.Logger;
    +022import tools.elide.core.DatapointType;
    +023
    +024import javax.annotation.Nonnull;
    +025import javax.annotation.Nullable;
    +026import javax.annotation.concurrent.Immutable;
    +027import java.util.Objects;
    +028import java.util.Optional;
    +029import java.util.concurrent.ConcurrentMap;
    +030import java.util.concurrent.ConcurrentSkipListMap;
    +031
    +032import static java.lang.String.format;
    +033import static gust.backend.model.ModelMetadata.*;
    +034
    +035
    +036/**
    +037 * Proxies calls to a static concurrent map, held by a private singleton. This nicely supplies local entity storage for
    +038 * simple testing and mocking purposes. Please do not use this in production. The in-memory data engine does not support
    +039 * queries, persistence, or nearly anything except get/put/delete.
    +040 *
    +041 * @param <Model> Model/message type which we are storing with this driver.
    +042 */
    +043@SuppressWarnings("UnstableApiUsage")
    +044public final class InMemoryDriver<Key extends Message, Model extends Message>
    +045  implements PersistenceDriver<Key, Model, EncodedModel, EncodedModel> {
    +046  /** Private logging pipe. */
    +047  private static final Logger logging = Logging.logger(InMemoryStorage.class);
    +048
    +049  /** Codec to use for model serialization/de-serialization. */
    +050  private final @Nonnull ModelCodec<Model, EncodedModel, EncodedModel> codec;
    +051
    +052  /** Executor service to use for storage calls. */
    +053  private final @Nonnull ListeningScheduledExecutorService executorService;
    +054
    +055  /** Holds private "data storage" via in-memory concurrent map. */
    +056  @Immutable
    +057  private final static class InMemoryStorage {
    +058    /** Storage singleton instance. */
    +059    private final static InMemoryStorage INSTANCE;
    +060
    +061    /** Backing storage map. */
    +062    private final @Nonnull ConcurrentMap<Object, EncodedModel> storageMap;
    +063
    +064    static {
    +065      INSTANCE = new InMemoryStorage();
    +066    }
    +067
    +068    /** Private constructor. Acquire via {@link #acquire()}. */
    +069    private InMemoryStorage() {
    +070      storageMap = new ConcurrentSkipListMap<>();
    +071    }
    +072
    +073    /** @return In-memory storage window singleton. */
    +074    @CanIgnoreReturnValue
    +075    private static @Nonnull ConcurrentMap<Object, EncodedModel> acquire() {
    +076      return INSTANCE.storageMap;
    +077    }
    +078  }
    +079
    +080  /**
    +081   * Construct a new in-memory driver from scratch. This constructor is private to force use of static factory methods
    +082   * also defined on this class.
    +083   *
    +084   * @param codec Codec to use when serializing and de-serializing models with this driver.
    +085   * @param executorService Executor service to run against.
    +086   */
    +087  private InMemoryDriver(@Nonnull ModelCodec<Model, EncodedModel, EncodedModel> codec,
    +088                         @Nonnull ListeningScheduledExecutorService executorService) {
    +089    this.codec = codec;
    +090    this.executorService = executorService;
    +091  }
    +092
    +093  /**
    +094   * Acquire an in-memory driver instance for the provided model type and builder. Although the driver object itself is
    +095   * created for the purpose, it accesses a static concurrent map backing all in-memory driver instances to facilitate
    +096   * storage.
    +097   *
    +098   * <p>It is generally recommended to acquire an instance of this driver through the adapter instead. This can be
    +099   * accomplished via {@link InMemoryAdapter#acquire(Message, Message, Optional, ListeningScheduledExecutorService)},
    +100   * followed by {@link InMemoryAdapter#engine()}.</p>
    +101   *
    +102   * @see InMemoryAdapter#acquire(Message, Message, ListeningScheduledExecutorService) to acquire a full adapter.
    +103   * @param <K> Key type to specify for the attached model type.
    +104   * @param <M> Model/message type for which we should return an in-memory storage driver.
    +105   * @param codec Codec to use when serializing and de-serializing models with this driver.
    +106   * @param executorService Executor service to use for storage calls.
    +107   * @return In-memory driver instance created for the specified message type.
    +108   */
    +109  static @Nonnull <K extends Message, M extends Message> InMemoryDriver<K, M> acquire(
    +110    @Nonnull ModelCodec<M, EncodedModel, EncodedModel> codec,
    +111    @Nonnull ListeningScheduledExecutorService executorService) {
    +112    return new InMemoryDriver<>(codec, executorService);
    +113  }
    +114
    +115  // -- Getters -- //
    +116  /** {@inheritDoc} */
    +117  @Override
    +118  public @Nonnull ModelCodec<Model, EncodedModel, EncodedModel> codec() {
    +119    return this.codec;
    +120  }
    +121
    +122  /** {@inheritDoc} */
    +123  @Override
    +124  public @Nonnull ListeningScheduledExecutorService executorService() {
    +125    return this.executorService;
    +126  }
    +127
    +128  // -- API: Fetch -- //
    +129  /** {@inheritDoc} */
    +130  @Override
    +131  public @Nonnull ReactiveFuture<Optional<Model>> retrieve(final @Nonnull Key key,
    +132                                                           final @Nonnull FetchOptions options) {
    +133    Objects.requireNonNull(key, "Cannot fetch model with `null` for key.");
    +134    Objects.requireNonNull(options, "Cannot fetch model without `options`.");
    +135    enforceRole(key, DatapointType.OBJECT_KEY);
    +136    final var id = id(key).orElseThrow(() -> new IllegalArgumentException("Cannot fetch model with empty key."));
    +137
    +138    if (logging.isDebugEnabled())
    +139      logging.debug(format("Retrieving model at ID '%s' from in-memory storage.", id));
    +140
    +141    return ReactiveFuture.wrap(this.executorService.submit(() -> {
    +142      if (logging.isTraceEnabled())
    +143        logging.trace(format("Began async task to retrieve model at ID '%s' from in-memory storage.", id));
    +144
    +145      EncodedModel data = InMemoryStorage.acquire().get(id);
    +146      if (data != null) {
    +147        if (logging.isTraceEnabled())
    +148          logging.trace(format("Model found at ID '%s'. Sending to deserializer...", id));
    +149
    +150        var deserialized = this.codec.deserialize(data);
    +151        if (logging.isDebugEnabled())
    +152          logging.debug(format("Found and deserialized model at ID '%s'. Record follows:\n%s", id, deserialized));
    +153        if (logging.isInfoEnabled())
    +154          logging.info(format("Retrieved record at ID '%s' from in-memory storage.", id));
    +155
    +156        // we found encoded data at the provided key. inflate it with the codec.
    +157        return Optional.of(spliceKey(applyMask(deserialized, options), Optional.of(key)));
    +158      } else {
    +159        if (logging.isWarnEnabled())
    +160          logging.warn(format("Model not found at ID '%s'.", id));
    +161
    +162        // the model was not found.
    +163        return Optional.empty();
    +164      }
    +165    }), options.executorService().orElse(this.executorService));
    +166  }
    +167
    +168  // -- API: Persist -- //
    +169  /** {@inheritDoc} */
    +170  @Override
    +171  public @Nonnull ReactiveFuture<Model> persist(final @Nullable Key key,
    +172                                                final @Nonnull Model model,
    +173                                                final @Nonnull WriteOptions options) {
    +174    Objects.requireNonNull(model, "Cannot persist `null` model.");
    +175    Objects.requireNonNull(options, "Cannot persist model without `options`.");
    +176    if (key != null) enforceRole(key, DatapointType.OBJECT_KEY);
    +177
    +178    // resolve target key, and then write mode
    +179    final @Nonnull Key targetKey = key != null ? key : generateKey(model);
    +180    //noinspection OptionalGetWithoutIsPresent
    +181    final @Nonnull Object targetId = id(targetKey).get();
    +182
    +183    if (logging.isDebugEnabled())
    +184      logging.debug(format("Persisting model at ID '%s' using in-memory storage.", targetId));
    +185
    +186    return ReactiveFuture.wrap(this.executorService.submit(() -> {
    +187      WriteOptions.WriteDisposition writeMode = (
    +188        key == null ? WriteOptions.WriteDisposition.MUST_NOT_EXIST : options.writeMode()
    +189          .orElse(WriteOptions.WriteDisposition.BLIND));
    +190
    +191      if (logging.isTraceEnabled())
    +192        logging.trace(format(
    +193          "Began async task to write model at ID '%s' to in-memory storage. Write disposition: '%s'.",
    +194          targetId,
    +195          writeMode.name()));
    +196
    +197      // enforce write mode
    +198      boolean conflictFailure = false;
    +199      switch (writeMode) {
    +200        case MUST_NOT_EXIST: conflictFailure = InMemoryStorage.acquire().containsKey(targetId); break;
    +201        case MUST_EXIST: conflictFailure = !InMemoryStorage.acquire().containsKey(targetId); break;
    +202        case BLIND: break;
    +203      }
    +204      if (conflictFailure) {
    +205        logging.error(format("Encountered conflict failure: key collision at ID '%s'.", targetId));
    +206        throw new ModelWriteConflict(targetId, model, writeMode);
    +207      }
    +208
    +209      // if we make it this far, we're ready to write. serialize and put.
    +210      InMemoryStorage
    +211        .acquire()
    +212        .put(targetId, codec.serialize(model));
    +213
    +214      if (logging.isTraceEnabled())
    +215        logging.trace(format(
    +216          "No conflict failure encountered, model was written at ID '%s'.",
    +217          targetId));
    +218
    +219      var rval = ModelMetadata.<Model, Key>spliceKey(model, Optional.of(targetKey));
    +220      if (logging.isInfoEnabled())
    +221        logging.info(format(
    +222          "Wrote record to in-memory storage at ID '%s'.",
    +223          targetId));
    +224      if (logging.isDebugEnabled())
    +225        logging.debug(format(
    +226          "Returning written model at ID '%s' after write to in-memory storage. Record follows:\n%s",
    +227          targetId,
    +228          rval));
    +229
    +230      return rval;
    +231
    +232    }), options.executorService().orElse(this.executorService));
    +233  }
    +234
    +235  // -- API: Delete -- //
    +236  /** {@inheritDoc} */
    +237  @Override
    +238  public @Nonnull ReactiveFuture<Key> delete(@Nonnull Key key, @Nonnull DeleteOptions options) {
    +239    Objects.requireNonNull(key, "Cannot delete `null` key.");
    +240    Objects.requireNonNull(options, "Cannot delete model without `options`.");
    +241    ModelMetadata.enforceRole(key, DatapointType.OBJECT_KEY);
    +242
    +243    final @Nonnull Object targetId = id(key)
    +244      .orElseThrow(() -> new IllegalStateException("Cannot delete record with empty key/ID."));
    +245
    +246    if (logging.isDebugEnabled())
    +247      logging.debug(format("Deleting model at ID '%s' from in-memory storage.", targetId));
    +248
    +249    return ReactiveFuture.wrap(this.executorService.submit(() -> {
    +250      if (logging.isTraceEnabled())
    +251        logging.trace(format("Began async task to delete model at ID '%s' from in-memory storage.", targetId));
    +252
    +253      InMemoryStorage
    +254        .acquire()
    +255        .remove(targetId);
    +256
    +257      if (logging.isInfoEnabled())
    +258        logging.info(format("Model at ID '%s' deleted from in-memory storage.", targetId));
    +259
    +260      return key;
    +261    }));
    +262  }
    +263}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/inmemory/InMemoryManager.html b/docs/java/src-html/gust/backend/driver/inmemory/InMemoryManager.html new file mode 100644 index 000000000..f6654b6eb --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/inmemory/InMemoryManager.html @@ -0,0 +1,96 @@ + + + +Source code + + + +
    + +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerAdapter.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerAdapter.html new file mode 100644 index 000000000..465722914 --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerAdapter.html @@ -0,0 +1,526 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import com.google.api.gax.core.CredentialsProvider;
    +016import com.google.api.gax.rpc.TransportChannelProvider;
    +017import com.google.cloud.grpc.GrpcTransportOptions;
    +018import com.google.cloud.spanner.*;
    +019import com.google.cloud.spanner.v1.stub.SpannerStubSettings;
    +020import com.google.common.util.concurrent.ListeningScheduledExecutorService;
    +021import com.google.common.util.concurrent.MoreExecutors;
    +022import com.google.protobuf.Message;
    +023import gust.backend.model.*;
    +024import gust.backend.transport.GoogleAPIChannel;
    +025import gust.backend.transport.GoogleService;
    +026import io.micronaut.context.annotation.Context;
    +027import io.micronaut.context.annotation.Factory;
    +028import io.micronaut.runtime.context.scope.Refreshable;
    +029
    +030import javax.annotation.Nonnull;
    +031import javax.annotation.concurrent.Immutable;
    +032import javax.annotation.concurrent.ThreadSafe;
    +033import java.io.Closeable;
    +034import java.util.Optional;
    +035import java.util.concurrent.Executors;
    +036
    +037
    +038/**
    +039 * Implementation of a {@link DatabaseAdapter} backed by Google Cloud Spanner, capable of marshalling arbitrary
    +040 * schema-generated {@link Message} objects back and forth from structured columnar storage.
    +041 *
    +042 * <p>This adapter is implemented by the {@link SpannerDriver} and related codec classes ({@link SpannerCodec},
    +043 * {@link SpannerStructDeserializer} and {@link SpannerMutationSerializer}). Background execution and gRPC channels are
    +044 * hooked into driver acquisition and may be managed by the developer, or by the framework automatically. See below for
    +045 * a summary of application-level Spanner features supported by this engine:</p>
    +046 *
    +047 * <p><b>Caching</b> may be facilitated by any compliant model {@link CacheDriver}.</p>
    +048 *
    +049 * <p><b>Transactions</b> are supported under the hood, and can be controlled via Spanner-extended operation options
    +050 * interfaces (such as {@link SpannerDriver.SpannerWriteOptions} and {@link SpannerDriver.SpannerFetchOptions}).
    +051 * Invoking code may either opt-in to transactional protection automatically, or drive external transactions with this
    +052 * adapter/driver by specifying an input transaction for a given operation.</p>
    +053 *
    +054 * <p><b>Collections</b> are supported by this engine, with additional support for nested models encoded via JSON. In
    +055 * cases where model JSON is involved, {@link com.google.protobuf.util.JsonFormat} is used to produce and consume
    +056 * compliant Proto-JSON.</p>
    +057 *
    +058 * @param <Key> Typed {@link Message} which implements a concrete model key structure, as defined and annotated by the
    +059 *              core Gust annotations.
    +060 * @param <Model> Typed {@link Message} which implements a concrete model object structure, as defined and annotated by
    +061 *              the core Gust annotations.
    +062 * @see SpannerManager Adapter instance manager and factory.
    +063 * @see SpannerDriverSettings Driver-level settings specific to Spanner.
    +064 * @see gust.backend.driver.firestore.FirestoreAdapter Similar adapter implementation, built on top of Cloud Firestore,
    +065 *      which itself is implemented on top of Cloud Spanner.
    +066 */
    +067@Immutable @ThreadSafe
    +068@SuppressWarnings({"OptionalUsedAsFieldOrParameterType", "UnstableApiUsage"})
    +069public final class SpannerAdapter<Key extends Message, Model extends Message>
    +070        implements DatabaseAdapter<Key, Model, Struct, Mutation>, Closeable, AutoCloseable {
    +071    /** Spanner database driver. */
    +072    private final @Nonnull SpannerDriver<Key, Model> driver;
    +073
    +074    /** Serializer and deserializer for this model. */
    +075    private final @Nonnull ModelCodec<Model, Mutation, Struct> codec;
    +076
    +077    /** Cache to use for model interactions through this adapter (optional). */
    +078    private final @Nonnull Optional<CacheDriver<Key, Model>> cache;
    +079
    +080    /**
    +081     * Private constructor for a model-specialized Spanner adapter instance.
    +082     *
    +083     * @param driver Driver instance created to power this adapter.
    +084     * @param codec Codec to use when marshalling objects with this adapter.
    +085     * @param cache Optionally, cache to employ when interacting with the underlying driver.
    +086     */
    +087    private SpannerAdapter(@Nonnull SpannerDriver<Key, Model> driver,
    +088                           @Nonnull ModelCodec<Model, Mutation, Struct> codec,
    +089                           @Nonnull Optional<CacheDriver<Key, Model>> cache) {
    +090        this.driver = driver;
    +091        this.codec = codec;
    +092        this.cache = cache;
    +093    }
    +094
    +095    /**
    +096     * Create or resolve a {@link SpannerAdapter} for the pre-fabricated {@link SpannerDriver}.
    +097     *
    +098     * <p>Note: this method has no way to specify a cache. See below for alternatives.</p>
    +099     *
    +100     * @see #forModel(SpannerDriver, Optional) Variant of this method that allows invoking code to provide a
    +101     *      compliant {@link CacheDriver} instance.
    +102     * @param driver Pre-fabricated Spanner driver to wrap with an adapter instance.
    +103     * @param <K> Typed model key structure which the resulting adapter should be specialized to.
    +104     * @param <M> Typed model object structure which the resulting adapter should be specialized to.
    +105     * @return Adapter instance, wrapping the provided driver.
    +106     */
    +107    public static @Nonnull <K extends Message, M extends Message> SpannerAdapter<K, M> forModel(
    +108            @Nonnull SpannerDriver<K, M> driver) {
    +109        return forModel(driver, Optional.empty());
    +110    }
    +111
    +112    /**
    +113     * Create or resolve a {@link SpannerAdapter} for the pre-fabricated {@link SpannerDriver}, optionally using the
    +114     * provided {@link CacheDriver}, if present.
    +115     *
    +116     * @param driver Pre-fabricated Spanner driver to wrap with an adapter instance.
    +117     * @param cacheDriver Optional cache engine to employ when interacting with the provided driver.
    +118     * @param <K> Typed model key structure which the resulting adapter should be specialized to.
    +119     * @param <M> Typed model object structure which the resulting adapter should be specialized to.
    +120     * @return Adapter instance, wrapping the provided driver.
    +121     */
    +122    public static @Nonnull <K extends Message, M extends Message> SpannerAdapter<K, M> forModel(
    +123            @Nonnull SpannerDriver<K, M> driver,
    +124            @Nonnull Optional<CacheDriver<K, M>> cacheDriver) {
    +125        return new SpannerAdapter<>(
    +126            driver,
    +127            driver.codec(),
    +128            cacheDriver
    +129        );
    +130    }
    +131
    +132    /** Factory responsible for creating {@link SpannerAdapter} instances from injected dependencies. */
    +133    @Factory static final class SpannerAdapterFactory {
    +134        private SpannerAdapterFactory() { /* Disallow construction. */ }
    +135
    +136        /**
    +137         * Acquire a new instance of the Spanner adapter, using the specified component objects to facilitate model
    +138         * serialization/deserialization, and transport communication with Cloud Spanner.
    +139         *
    +140         * @param driver Driver with which we should talk to Spanner.
    +141         * @param cache Driver with which we should cache eligible data.
    +142         * @return Spanner driver instance.
    +143         */
    +144        @Context
    +145        @Refreshable
    +146        public static @Nonnull <K extends Message, M extends Message> SpannerAdapter<K, M> acquire(
    +147                @Nonnull SpannerDriver<K, M> driver,
    +148                @Nonnull Optional<CacheDriver<K, M>> cache) {
    +149            // resolve model builder from type
    +150            return SpannerAdapter.forModel(
    +151                driver,
    +152                cache
    +153            );
    +154        }
    +155    }
    +156
    +157    /**
    +158     * Create or acquire a {@link SpannerAdapter} and matching {@link SpannerDriver} for the provided generated model
    +159     * key and object structures, working against the provided Spanner {@link DatabaseId}.
    +160     *
    +161     * <p>This method variant is the simplest invocation option for acquiring an adapter. Variants of this method
    +162     * provide deeper control over interactions with the Spanner service. See below for alternatives if deeper control
    +163     * is necessary. Generally, it is best to let the framework manage transport and stub settings.</p>
    +164     *
    +165     * @see #acquire(Message, Message, DatabaseId, ListeningScheduledExecutorService) For an option which lets invoking
    +166     *      code specify a background executor for RPC transmission and followup.
    +167     * @see #acquire(Message, Message, DatabaseId, SpannerOptions.Builder, ListeningScheduledExecutorService) Variant of
    +168     *      this same method which offers control of the {@link SpannerOptions} used to spawn RPC clients.
    +169     * @see #acquire(SpannerOptions.Builder, DatabaseId, TransportChannelProvider, Optional, Optional,
    +170     *      GrpcTransportOptions, ListeningScheduledExecutorService, Message, Message, SpannerDriverSettings, Optional)
    +171     *      Full control over creation of the Spanner adapter and driver.
    +172     * @param keyInstance Generated key {@link Message} structure, for which the adapter should be specialized.
    +173     * @param messageInstance Generated object {@link Message} structure, for which the adapter should be specialized.
    +174     * @param defaultDatabase Default Spanner database to use when interacting with this adapter. This value may be
    +175     *                        overridden on an individual operation basis via specifying custom
    +176     *                        {@link SpannerDriver.SpannerOperationOptions} and descendents.
    +177     * @param <K> Model key structure for which the resulting adapter should be specialized.
    +178     * @param <M> Model object structure for which the resulting adapter should be specialized.
    +179     * @return Spanner adapter instance, specialized to the provided model and key {@link Message}s.
    +180     */
    +181    public static @Nonnull <K extends Message, M extends Message> SpannerAdapter<K, M> acquire(
    +182            @Nonnull K keyInstance,
    +183            @Nonnull M messageInstance,
    +184            @Nonnull DatabaseId defaultDatabase) {
    +185        return acquire(
    +186                SpannerOptions.newBuilder(),
    +187                defaultDatabase,
    +188                SpannerStubSettings.defaultTransportChannelProvider(),
    +189                Optional.of(SpannerStubSettings.defaultCredentialsProviderBuilder().build()),
    +190                Optional.empty(),
    +191                GrpcTransportOptions.newBuilder().build(),
    +192                MoreExecutors.listeningDecorator(
    +193                    Executors.newScheduledThreadPool(3)
    +194                ),
    +195                keyInstance,
    +196                messageInstance,
    +197                SpannerDriverSettings.DEFAULTS,
    +198                Optional.empty()
    +199        );
    +200    }
    +201
    +202    /**
    +203     * Create or acquire a {@link SpannerAdapter} and matching {@link SpannerDriver} for the provided generated model
    +204     * key and object structures, working against the provided Spanner {@link DatabaseId}.
    +205     *
    +206     * <p>This method variant additionally allows the developer to specify a custom
    +207     * {@link ListeningScheduledExecutorService} to use for background operation execution. This executor service is
    +208     * injected directly into the {@link SpannerDriver} and underlying Spanner RPC client, and is used for both RPC
    +209     * operational execution and async followup.</p>
    +210     *
    +211     * <p>Variants of this method provide deeper control over interactions with the Spanner service. See below for
    +212     * alternatives.</p>
    +213     *
    +214     * @see #acquire(Message, Message, DatabaseId) For a simpler version of this method which uses managed driver
    +215     *      settings and a sensible cached threadpool executor.
    +216     * @see #acquire(Message, Message, DatabaseId, SpannerOptions.Builder, ListeningScheduledExecutorService) Variant of
    +217     *      this same method which offers control of the {@link SpannerOptions} used to spawn RPC clients.
    +218     * @see #acquire(SpannerOptions.Builder, DatabaseId, TransportChannelProvider, Optional, Optional,
    +219     *      GrpcTransportOptions, ListeningScheduledExecutorService, Message, Message, SpannerDriverSettings, Optional)
    +220     *      Full control over creation of the Spanner adapter and driver.
    +221     * @param keyInstance Generated key {@link Message} structure, for which the adapter should be specialized.
    +222     * @param messageInstance Generated object {@link Message} structure, for which the adapter should be specialized.
    +223     * @param defaultDatabase Default Spanner database to use when interacting with this adapter. This value may be
    +224     *                        overridden on an individual operation basis via specifying custom
    +225     *                        {@link SpannerDriver.SpannerOperationOptions} and descendents.
    +226     * @param executorService Executor service to use for primary RPC execution and related followup.
    +227     * @param <K> Model key structure for which the resulting adapter should be specialized.
    +228     * @param <M> Model object structure for which the resulting adapter should be specialized.
    +229     * @return Spanner adapter instance, specialized to the provided model and key {@link Message}s.
    +230     */
    +231    public static @Nonnull <K extends Message, M extends Message> SpannerAdapter<K, M> acquire(
    +232            @Nonnull K keyInstance,
    +233            @Nonnull M messageInstance,
    +234            @Nonnull DatabaseId defaultDatabase,
    +235            @Nonnull ListeningScheduledExecutorService executorService) {
    +236        return acquire(
    +237            SpannerOptions.newBuilder(),
    +238            defaultDatabase,
    +239            SpannerStubSettings.defaultTransportChannelProvider(),
    +240            Optional.of(SpannerStubSettings.defaultCredentialsProviderBuilder().build()),
    +241            Optional.empty(),
    +242            GrpcTransportOptions.newBuilder().build(),
    +243            executorService,
    +244            keyInstance,
    +245            messageInstance,
    +246            SpannerDriverSettings.DEFAULTS,
    +247            Optional.empty()
    +248        );
    +249    }
    +250
    +251    /**
    +252     * Create or acquire a {@link SpannerAdapter} and matching {@link SpannerDriver} for the provided generated model
    +253     * key and object structures, working against the provided Spanner {@link DatabaseId}.
    +254     *
    +255     * <p>This method variant is a balanced invocation which allows invoking code to control <i>most</i> settings,
    +256     * without coupling too tightly to Google SDKs.</p>
    +257     *
    +258     * @see #acquire(Message, Message, DatabaseId, ListeningScheduledExecutorService) For an option which lets invoking
    +259     *      code specify a background executor for RPC transmission and followup.
    +260     * @see #acquire(Message, Message, DatabaseId, SpannerOptions.Builder, ListeningScheduledExecutorService) Variant of
    +261     *      this same method which offers control of the {@link SpannerOptions} used to spawn RPC clients.
    +262     * @see #acquire(SpannerOptions.Builder, DatabaseId, TransportChannelProvider, Optional, Optional,
    +263     *      GrpcTransportOptions, ListeningScheduledExecutorService, Message, Message, SpannerDriverSettings, Optional)
    +264     *      Full control over creation of the Spanner adapter and driver.
    +265     * @param keyInstance Generated key {@link Message} structure, for which the adapter should be specialized.
    +266     * @param messageInstance Generated object {@link Message} structure, for which the adapter should be specialized.
    +267     * @param defaultDatabase Default Spanner database to use when interacting with this adapter. This value may be
    +268     *                        overridden on an individual operation basis via specifying custom
    +269     *                        {@link SpannerDriver.SpannerOperationOptions} and descendents.
    +270     * @param executorService Executor service to use for primary RPC execution and related followup.
    +271     * @param driverSettings Custom driver settings to apply. {@link SpannerDriverSettings#DEFAULTS} is a good start.
    +272     * @param <K> Model key structure for which the resulting adapter should be specialized.
    +273     * @param <M> Model object structure for which the resulting adapter should be specialized.
    +274     * @return Spanner adapter instance, specialized to the provided model and key {@link Message}s.
    +275     */
    +276    public static @Nonnull <K extends Message, M extends Message> SpannerAdapter<K, M> acquire(
    +277            @Nonnull K keyInstance,
    +278            @Nonnull M messageInstance,
    +279            @Nonnull DatabaseId defaultDatabase,
    +280            @Nonnull Optional<ListeningScheduledExecutorService> executorService,
    +281            @Nonnull Optional<SpannerDriverSettings> driverSettings,
    +282            @Nonnull Optional<SpannerOptions.Builder> baseOptions,
    +283            @Nonnull Optional<CacheDriver<K, M>> cacheDriver) {
    +284        return acquire(
    +285            baseOptions.orElse(SpannerOptions.newBuilder()),
    +286            defaultDatabase,
    +287            SpannerStubSettings.defaultTransportChannelProvider(),
    +288            Optional.of(SpannerStubSettings.defaultCredentialsProviderBuilder().build()),
    +289            Optional.empty(),
    +290            GrpcTransportOptions.newBuilder().build(),
    +291            executorService.orElseGet(() -> MoreExecutors.listeningDecorator(
    +292                Executors.newScheduledThreadPool(3)
    +293            )),
    +294            keyInstance,
    +295            messageInstance,
    +296            driverSettings.orElse(SpannerDriverSettings.DEFAULTS),
    +297            cacheDriver
    +298        );
    +299    }
    +300
    +301    /**
    +302     * Create or acquire a {@link SpannerAdapter} and matching {@link SpannerDriver} for the provided generated model
    +303     * key and object structures, working against the provided Spanner {@link DatabaseId}.
    +304     *
    +305     * <p>This method variant additionally allows the developer to specify a custom
    +306     * {@link ListeningScheduledExecutorService} to use for background operation execution, and a set of
    +307     * {@link SpannerOptions} to use when spawning RPC clients. This executor service is injected directly into the
    +308     * {@link SpannerDriver} and underlying Spanner RPC clients, and is used for both RPC operational execution and
    +309     * async followup.</p>
    +310     *
    +311     * <p>Variants of this method provide either simpler invocation, or deeper control over interactions with the
    +312     * Spanner service. See below for alternatives.</p>
    +313     *
    +314     * @see #acquire(Message, Message, DatabaseId) For a simpler version of this method which uses managed driver
    +315     *      settings and a sensible cached threadpool executor.
    +316     * @see #acquire(Message, Message, DatabaseId, ListeningScheduledExecutorService) For a simpler version of this
    +317     *      method which uses managed driver settings.
    +318     * @see #acquire(SpannerOptions.Builder, DatabaseId, TransportChannelProvider, Optional, Optional,
    +319     *      GrpcTransportOptions, ListeningScheduledExecutorService, Message, Message, SpannerDriverSettings, Optional)
    +320     *      Full control over creation of the Spanner adapter and driver.
    +321     * @param keyInstance Generated key {@link Message} structure, for which the adapter should be specialized.
    +322     * @param messageInstance Generated object {@link Message} structure, for which the adapter should be specialized.
    +323     * @param defaultDatabase Default Spanner database to use when interacting with this adapter. This value may be
    +324     *                        overridden on an individual operation basis via specifying custom
    +325     *                        {@link SpannerDriver.SpannerOperationOptions} and descendents.
    +326     * @param baseOptions Spanner options to use when spawning RPC clients.
    +327     * @param executorService Executor service to use for primary RPC execution and related followup.
    +328     * @param <K> Model key structure for which the resulting adapter should be specialized.
    +329     * @param <M> Model object structure for which the resulting adapter should be specialized.
    +330     * @return Spanner adapter instance, specialized to the provided model and key {@link Message}s.
    +331     */
    +332    public static @Nonnull <K extends Message, M extends Message> SpannerAdapter<K, M> acquire(
    +333            @Nonnull K keyInstance,
    +334            @Nonnull M messageInstance,
    +335            @Nonnull DatabaseId defaultDatabase,
    +336            @Nonnull SpannerOptions.Builder baseOptions,
    +337            @Nonnull ListeningScheduledExecutorService executorService) {
    +338        return acquire(
    +339            baseOptions,
    +340            defaultDatabase,
    +341            SpannerStubSettings.defaultTransportChannelProvider(),
    +342            Optional.of(SpannerStubSettings.defaultCredentialsProviderBuilder().build()),
    +343            Optional.empty(),
    +344            GrpcTransportOptions.newBuilder().build(),
    +345            executorService,
    +346            keyInstance,
    +347            messageInstance,
    +348            SpannerDriverSettings.DEFAULTS,
    +349            Optional.empty()
    +350        );
    +351    }
    +352
    +353    /**
    +354     * Create or acquire a {@link SpannerAdapter} and matching {@link SpannerDriver} for the provided generated model
    +355     * key and object structures, working against the provided Spanner {@link DatabaseId}.
    +356     *
    +357     * <p>This method variant additionally allows the developer to specify all custom settings available for the Spanner
    +358     * driver and adapter.</p>
    +359     *
    +360     * <p>Variants of this method provide simpler invocation, for looser coupling with applications. See below to
    +361     * consider these alternatives for situations where deep control isn't necessary.</p>
    +362     *
    +363     * @param baseOptions Spanner options to use when spawning RPC clients.
    +364     * @param defaultDatabase Default Spanner database to use when interacting with this adapter. This value may be
    +365     *                        overridden on an individual operation basis via specifying custom
    +366     *                        {@link SpannerDriver.SpannerOperationOptions} and descendents.
    +367     * @param spannerChannel Transport channel provider to use when spawning RPC connections to Spanner.
    +368     * @param credentialsProvider Credentials provider to use when authorizing calls to Spanner.
    +369     * @param callCredentialProvider Call-level credentials provider to use when authorizing calls to Spanner. Optional.
    +370     * @param transportOptions Transport options to apply when interacting with Spanner services.
    +371     * @param executorService Executor service to use for primary RPC execution and related followup.
    +372     * @param keyInstance Generated key {@link Message} structure, for which the adapter should be specialized.
    +373     * @param messageInstance Generated object {@link Message} structure, for which the adapter should be specialized.
    +374     * @param driverSettings Settings to apply to the Spanner driver and adapter itself.
    +375     * @param cacheDriver Cache engine to use when interacting with the underlying driver.
    +376     * @param <K> Model key structure for which the resulting adapter should be specialized.
    +377     * @param <M> Model object structure for which the resulting adapter should be specialized.
    +378     * @return Spanner adapter instance, specialized to the provided model and key {@link Message}s.
    +379     */
    +380    public static @Nonnull <K extends Message, M extends Message> SpannerAdapter<K, M> acquire(
    +381            @Nonnull SpannerOptions.Builder baseOptions,
    +382            @Nonnull DatabaseId defaultDatabase,
    +383            @Nonnull @GoogleAPIChannel(service = GoogleService.SPANNER) TransportChannelProvider spannerChannel,
    +384            @Nonnull Optional<CredentialsProvider> credentialsProvider,
    +385            @Nonnull Optional<SpannerOptions.CallCredentialsProvider> callCredentialProvider,
    +386            @Nonnull GrpcTransportOptions transportOptions,
    +387            @Nonnull ListeningScheduledExecutorService executorService,
    +388            @Nonnull K keyInstance,
    +389            @Nonnull M messageInstance,
    +390            @Nonnull SpannerDriverSettings driverSettings,
    +391            @Nonnull Optional<CacheDriver<K, M>> cacheDriver) {
    +392        return SpannerAdapterFactory.acquire(
    +393            SpannerDriver.SpannerDriverFactory.acquireDriver(
    +394                baseOptions,
    +395                defaultDatabase,
    +396                spannerChannel,
    +397                credentialsProvider,
    +398                callCredentialProvider,
    +399                transportOptions,
    +400                executorService,
    +401                keyInstance,
    +402                messageInstance,
    +403                driverSettings
    +404            ),
    +405            cacheDriver
    +406        );
    +407    }
    +408
    +409    // -- API: Closeable -- //
    +410
    +411    @Override
    +412    public void close() {
    +413        // Not yet implemented.
    +414    }
    +415
    +416    // -- Components -- //
    +417
    +418    /** {@inheritDoc} */
    +419    @Override
    +420    public @Nonnull ModelCodec<Model, Mutation, Struct> codec() {
    +421        return this.codec;
    +422    }
    +423
    +424    /** {@inheritDoc} */
    +425    @Override
    +426    public @Nonnull Optional<CacheDriver<Key, Model>> cache() {
    +427        return this.cache;
    +428    }
    +429
    +430    /** {@inheritDoc} */
    +431    @Override
    +432    public @Nonnull DatabaseDriver<Key, Model, Struct, Mutation> engine() {
    +433        return this.driver;
    +434    }
    +435
    +436    /** {@inheritDoc} */
    +437    @Override
    +438    public @Nonnull ListeningScheduledExecutorService executorService() {
    +439        return driver.executorService();
    +440    }
    +441
    +442    // -- Spanner: Extended API -- //
    +443
    +444    /**
    +445     * Acquire the Spanner for Java client powering this adapter.
    +446     *
    +447     * @return Spanner client for Java.
    +448     */
    +449    public @Nonnull Spanner spanner() {
    +450        return ((SpannerDriver<Key, Model>)engine()).engine;
    +451    }
    +452}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerCodec.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerCodec.html new file mode 100644 index 000000000..6684839ea --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerCodec.html @@ -0,0 +1,236 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import com.google.cloud.spanner.Mutation;
    +016import com.google.cloud.spanner.Struct;
    +017import com.google.protobuf.Message;
    +018import gust.backend.model.*;
    +019import io.micronaut.context.annotation.Context;
    +020import io.micronaut.context.annotation.Factory;
    +021
    +022import javax.annotation.Nonnull;
    +023import javax.annotation.concurrent.Immutable;
    +024import javax.annotation.concurrent.ThreadSafe;
    +025import java.io.IOException;
    +026
    +027
    +028/**
    +029 * Implements a {@link ModelCodec} for Spanner types used as intermediaries during database operations. In particular,
    +030 * Spanner uses {@link Mutation} objects to express writes, and yields reads in the form of {@link Struct} objects which
    +031 * each identify a result row.
    +032 *
    +033 * <p>This codec is implemented with custom serialization via {@link SpannerMutationSerializer}, and de-serialization
    +034 * via {@link SpannerStructDeserializer}, which are similarly specialized at compile time to a given {@link Message}
    +035 * generated schema implementation.</p>
    +036 *
    +037 * @see SpannerMutationSerializer Specialized serialization from {@link Message} objects to Spanner {@link Mutation}s.
    +038 * @see SpannerStructDeserializer Specialized de-serialization from {@link Struct} objects to {@link Message}s.
    +039 * @param <Model> Typed {@link Message} which implements a concrete model object structure, as defined and annotated by
    +040 *                the core Gust annotations.
    +041 */
    +042@Factory
    +043@Immutable
    +044@ThreadSafe
    +045public final class SpannerCodec<Model extends Message> implements ModelCodec<Model, Mutation, Struct> {
    +046    /** Default model instance to build with. */
    +047    private final Model instance;
    +048
    +049    /** Serializer for the model, provided at construction time. */
    +050    private final ModelSerializer<Model, Mutation> serializer;
    +051
    +052    /** Deserializer for the model, provided at construction time. */
    +053    private final ModelDeserializer<Struct, Model> deserializer;
    +054
    +055    /**
    +056     * Construct a mutation codec from scratch.
    +057     *
    +058     * @param instance Model instance we intend to encode / decode with this codec.
    +059     * @param deserializer Deserializer for read-intermediate objects.
    +060     */
    +061    private SpannerCodec(@Nonnull Model instance,
    +062                         @Nonnull ModelSerializer<Model, Mutation> serializer,
    +063                         @Nonnull ModelDeserializer<Struct, Model> deserializer) {
    +064        this.instance = instance;
    +065        this.serializer = serializer;
    +066        this.deserializer = deserializer;
    +067    }
    +068
    +069    /**
    +070     * Create a Spanner message codec which adapts the provided builder to a Spanner {@link Mutation} and back with
    +071     * default codecs and settings.
    +072     *
    +073     * @param instance Default instance for the model we will be serializing/deserializing.
    +074     * @param <M> Model type for which we will construct or otherwise resolve a collapsed message codec.
    +075     * @return Mutation codec bound to the provided message type.
    +076     */
    +077    @Context
    +078    public static @Nonnull <M extends Message> SpannerCodec<M> forModel(@Nonnull M instance) {
    +079        return forModel(
    +080            instance,
    +081            SpannerDriverSettings.DEFAULTS
    +082        );
    +083    }
    +084
    +085    /**
    +086     * Create a Spanner message codec which adapts the provided builder to a Spanner {@link Mutation} and back with
    +087     * default codecs and custom settings.
    +088     *
    +089     * @param instance Default instance for the model we will be serializing/deserializing.
    +090     * @param driverSettings Settings for the Spanner driver itself.
    +091     * @param <M> Model type for which we will construct or otherwise resolve a collapsed message codec.
    +092     * @return Mutation codec bound to the provided message type.
    +093     */
    +094    @Context
    +095    public static @Nonnull <M extends Message> SpannerCodec<M> forModel(@Nonnull M instance,
    +096                                                                        @Nonnull SpannerDriverSettings driverSettings) {
    +097        return forModel(
    +098            instance,
    +099            SpannerMutationSerializer.forModel(
    +100                instance,
    +101                driverSettings
    +102            ),
    +103            SpannerStructDeserializer.forModel(
    +104                instance,
    +105                driverSettings
    +106            )
    +107        );
    +108    }
    +109
    +110    /**
    +111     * Create a Spanner message codec which adapts the provided builder to a Spanner {@link Mutation} and back.
    +112     *
    +113     * @param instance Default instance for the model we will be serializing/deserializing.
    +114     * @param serializer Custom serializer to use for this codec.
    +115     * @param deserializer Custom deserializer to use for this codec.
    +116     * @param <M> Model type for which we will construct or otherwise resolve a collapsed message codec.
    +117     * @return Mutation codec bound to the provided message type.
    +118     */
    +119    @Context
    +120    public static @Nonnull <M extends Message> SpannerCodec<M> forModel(
    +121            @Nonnull M instance,
    +122            @Nonnull ModelSerializer<M, Mutation> serializer,
    +123            @Nonnull ModelDeserializer<Struct, M> deserializer) {
    +124        return new SpannerCodec<>(instance, serializer, deserializer);
    +125    }
    +126
    +127    /**
    +128     * Specialized entrypoint for converting model instances into {@link Mutation} instances so they may be written to
    +129     * Spanner.
    +130     *
    +131     * @param initial Initial empty mutation to populate with the serialized message result.
    +132     * @param model Model which we intend to store in Spanner.
    +133     * @return Initialized and serialized mutation.
    +134     * @throws IOException If some serialization error occurs while processing the model.
    +135     */
    +136    public @Nonnull Mutation serialize(@Nonnull Mutation.WriteBuilder initial,
    +137                                       @Nonnull Model model) throws IOException {
    +138        return ((SpannerMutationSerializer<Model>) this.serializer).initializeMutation(
    +139            initial
    +140        ).deflate(model);
    +141    }
    +142
    +143    // -- Implementation: Codec API -- //
    +144
    +145    /** @inheritDoc */
    +146    @Override
    +147    public @Nonnull Model instance() {
    +148        return this.instance;
    +149    }
    +150
    +151    /** @inheritDoc */
    +152    @Override
    +153    public @Nonnull ModelSerializer<Model, Mutation> serializer() {
    +154        return this.serializer;
    +155    }
    +156
    +157    /** @inheritDoc */
    +158    @Override
    +159    public @Nonnull ModelDeserializer<Struct, Model> deserializer() {
    +160        return this.deserializer;
    +161    }
    +162}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerDriver.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerDriver.html new file mode 100644 index 000000000..7b88c2d31 --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerDriver.html @@ -0,0 +1,560 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import com.google.api.gax.core.CredentialsProvider;
    +016import com.google.api.gax.rpc.TransportChannelProvider;
    +017import com.google.cloud.grpc.GrpcTransportOptions;
    +018import com.google.cloud.spanner.*;
    +019import com.google.cloud.spanner.SpannerOptions;
    +020import com.google.common.util.concurrent.ListeningScheduledExecutorService;
    +021import com.google.protobuf.FieldMask;
    +022import com.google.protobuf.Message;
    +023import com.google.protobuf.util.FieldMaskUtil;
    +024import gust.backend.model.*;
    +025import gust.backend.runtime.Logging;
    +026import gust.backend.runtime.ReactiveFuture;
    +027import gust.backend.transport.GoogleAPIChannel;
    +028import gust.backend.transport.GoogleService;
    +029import io.micronaut.context.annotation.Context;
    +030import io.micronaut.context.annotation.Factory;
    +031import io.micronaut.runtime.context.scope.Refreshable;
    +032import org.slf4j.Logger;
    +033import tools.elide.core.*;
    +034
    +035import javax.annotation.Nonnull;
    +036import javax.annotation.Nullable;
    +037import javax.annotation.concurrent.Immutable;
    +038import javax.annotation.concurrent.ThreadSafe;
    +039import java.io.IOException;
    +040import java.util.*;
    +041import java.util.concurrent.TimeUnit;
    +042
    +043import static gust.backend.driver.spanner.SpannerUtil.*;
    +044import static com.google.common.util.concurrent.Futures.transformAsync;
    +045import static com.google.common.util.concurrent.Futures.immediateFuture;
    +046import static com.google.common.util.concurrent.Futures.withTimeout;
    +047import static gust.backend.runtime.ReactiveFuture.wrap;
    +048import static gust.backend.model.ModelMetadata.*;
    +049import static java.lang.String.format;
    +050
    +051
    +052/**
    +053 * Provides a {@link DatabaseDriver} implementation which enables seamless Protocol Buffer persistence with Google Cloud
    +054 * Spanner, for any {@link Message}-derived (schema-driven) business model in a given Gust app's ecosystem.
    +055 *
    +056 * <p>Model storage can be deeply customized on a per-model basis, thanks to the built-in proto annotations available
    +057 * in <code>gust.core</code>. The Spanner adapter supports basic persistence (i.e. as a regular
    +058 * <pre>PersistenceDriver</pre>), but also supports generic, object index-style queries.</p>
    +059 *
    +060 * <p>See the main {@link SpannerAdapter} for a full description of supported application-level functionality.</p>
    +061 *
    +062 * @param <Key> Typed {@link Message} which implements a concrete model key structure, as defined and annotated by the
    +063 *              core Gust annotations.
    +064 * @param <Model> Typed {@link Message} which implements a concrete model object structure, as defined and annotated by
    +065 *              the core Gust annotations.
    +066 * @see SpannerAdapter Main typed adapter interface for Spanner.
    +067 * @see SpannerManager Adapter instance manager and factory.
    +068 * @see SpannerDriverSettings Driver-level settings specific to Spanner.
    +069 * @see gust.backend.driver.firestore.FirestoreDriver Similar driver implementation, built on top of Cloud Firestore,
    +070 *      which itself is implemented on top of Cloud Spanner.
    +071 */
    +072@Immutable @ThreadSafe
    +073@SuppressWarnings({"UnstableApiUsage", "OptionalUsedAsFieldOrParameterType"})
    +074public final class SpannerDriver<Key extends Message, Model extends Message>
    +075        implements DatabaseDriver<Key, Model, Struct, Mutation> {
    +076    /** Private log pipe. */
    +077    private static final Logger logging = Logging.logger(SpannerDriver.class);
    +078
    +079    /** Executor service to use for async calls. */
    +080    private final @Nonnull ListeningScheduledExecutorService executorService;
    +081
    +082    /** Codec to use for serializing/de-serializing models. */
    +083    private final @Nonnull ModelCodec<Model, Mutation, Struct> codec;
    +084
    +085    /** Default database ID to interact with. */
    +086    private final @Nonnull DatabaseId defaultDatabase;
    +087
    +088    /** Settings for the Spanner driver. */
    +089    private final @Nonnull SpannerDriverSettings driverSettings;
    +090
    +091    /** Cloud Spanner client engine. */
    +092    final @Nonnull Spanner engine;
    +093
    +094    /** Defines generic Spanner operation-specific options. */
    +095    interface SpannerOperationOptions extends OperationOptions {
    +096        /** @return Database to use when connecting to Spanner. */
    +097        default @Nonnull Optional<DatabaseId> databaseId() {
    +098            return Optional.empty();
    +099        }
    +100    }
    +101
    +102    /** Defines Spanner-specific options for all read and query operations. */
    +103    interface SpannerReadOptions extends SpannerOperationOptions, FetchOptions {
    +104        /** @return Manual field projection, as applicable. Defaults to any present field mask. */
    +105        default @Nonnull Optional<FieldMask> projection() {
    +106            return this.fieldMask();
    +107        }
    +108    }
    +109
    +110    /** Defines Spanner-specific options for all write and mutation operations. */
    +111    interface SpannerWriteOptions extends SpannerOperationOptions, WriteOptions {
    +112        // Nothing yet.
    +113    }
    +114
    +115    /** Defines Spanner-specific fetch options. */
    +116    interface SpannerFetchOptions extends SpannerReadOptions {
    +117        /** Default set of fetch options. */
    +118        SpannerFetchOptions DEFAULTS = new SpannerFetchOptions() {};
    +119
    +120        /** @return Timestamp boundary for single-use reads. */
    +121        default @Nonnull Optional<TimestampBound> timestampBound() {
    +122            return Optional.empty();
    +123        }
    +124    }
    +125
    +126    /** Defines Spanner-specific mutative write options. */
    +127    interface SpannerMutationOptions extends SpannerWriteOptions {
    +128        /** Default set of mutation options. */
    +129        SpannerMutationOptions DEFAULTS = new SpannerMutationOptions() {};
    +130
    +131        /**
    +132         * Provides a transaction manger for a delete transaction. To activate transactions, one must also set the
    +133         * `transactional` configuration setting to `true`.
    +134         *
    +135         * @return Optional containing the transaction manager to use for this operation.
    +136         */
    +137        default @Nonnull Optional<TransactionContext> transactionContext() {
    +138            return Optional.empty();
    +139        }
    +140    }
    +141
    +142    /** Defines Spanner-specific mutative delete options. */
    +143    interface SpannerDeleteOptions extends SpannerWriteOptions {
    +144        /** Default set of delete options. */
    +145        SpannerDeleteOptions DEFAULTS = new SpannerDeleteOptions() {};
    +146    }
    +147
    +148    /**
    +149     * Construct a new Spanner driver from scratch.
    +150     *
    +151     * @param baseOptions Base options to apply to the Spanner driver.
    +152     * @param channelProvider Managed gRPC channel to use for Spanner RPCAPI interactions.
    +153     * @param credentialsProvider Transport credentials provider.
    +154     * @param defaultDatabase Default Spanner database to use and interact with.
    +155     * @param callCredentialProvider RPC call credential provider.
    +156     * @param transportOptions Options to apply to the transport layer.
    +157     * @param executorService Executor service to use when executing calls.
    +158     * @param codec Model codec to use with this driver.
    +159     * @param driverSettings Settings for the Spanner driver itself.
    +160     */
    +161    private SpannerDriver(@Nonnull SpannerOptions.Builder baseOptions,
    +162                          @Nonnull TransportChannelProvider channelProvider,
    +163                          @Nonnull DatabaseId defaultDatabase,
    +164                          @Nonnull Optional<CredentialsProvider> credentialsProvider,
    +165                          @Nonnull Optional<SpannerOptions.CallCredentialsProvider> callCredentialProvider,
    +166                          @Nonnull GrpcTransportOptions transportOptions,
    +167                          @Nonnull ListeningScheduledExecutorService executorService,
    +168                          @Nonnull ModelCodec<Model, Mutation, Struct> codec,
    +169                          @Nonnull SpannerDriverSettings driverSettings) {
    +170        this.codec = codec;
    +171        this.defaultDatabase = defaultDatabase;
    +172        this.executorService = executorService;
    +173        this.driverSettings = driverSettings;
    +174        SpannerOptions.Builder options = baseOptions
    +175                .setChannelProvider(channelProvider)
    +176                .setTransportOptions(transportOptions);
    +177
    +178        callCredentialProvider.ifPresent(options::setCallCredentialsProvider);
    +179        credentialsProvider.ifPresent((credentialProvider) -> options
    +180                .getSpannerStubSettingsBuilder()
    +181                .setCredentialsProvider(credentialProvider));
    +182
    +183        if (logging.isDebugEnabled())
    +184            logging.debug(String.format("Initializing Spanner driver with options:\n%s",
    +185                    options.build().getService().getOptions().toString()));
    +186        this.engine = options.build().getService();
    +187    }
    +188
    +189    /** Factory responsible for creating {@link SpannerDriver} instances from injected dependencies. */
    +190    @Factory static final class SpannerDriverFactory {
    +191        private SpannerDriverFactory() { /* Disallow construction. */ }
    +192
    +193        /**
    +194         * Acquire a new instance of the Spanner driver, using the specified configuration settings, and the specified
    +195         * injected channel.
    +196         *
    +197         * @param baseOptions Base options to apply to the Spanner driver.
    +198         * @param spannerChannel Managed gRPC channel provider.
    +199         * @param credentialsProvider Transport credentials provider.
    +200         * @param callCredentialProvider RPC call credential provider.
    +201         * @param transportOptions Options to apply to the Spanner channel.
    +202         * @param executorService Executor service to use when executing calls.
    +203         * @param keyInstance Key model class we are binding this driver to.
    +204         * @param modelInstance Default instance of the model we wish to make a driver for.
    +205         * @param driverSettings Settings for the Spanner driver.
    +206         * @return Spanner driver instance.
    +207         */
    +208        @Context
    +209        @Refreshable
    +210        public static @Nonnull <K extends Message, M extends Message> SpannerDriver<K, M> acquireDriver(
    +211                @Nonnull SpannerOptions.Builder baseOptions,
    +212                @Nonnull DatabaseId defaultDatabase,
    +213                @Nonnull @GoogleAPIChannel(service = GoogleService.SPANNER) TransportChannelProvider spannerChannel,
    +214                @Nonnull Optional<CredentialsProvider> credentialsProvider,
    +215                @Nonnull Optional<SpannerOptions.CallCredentialsProvider> callCredentialProvider,
    +216                @Nonnull GrpcTransportOptions transportOptions,
    +217                @Nonnull ListeningScheduledExecutorService executorService,
    +218                @SuppressWarnings("unused") @Nonnull K keyInstance,
    +219                @Nonnull M modelInstance,
    +220                @Nonnull SpannerDriverSettings driverSettings) {
    +221            return new SpannerDriver<>(
    +222                baseOptions,
    +223                spannerChannel,
    +224                defaultDatabase,
    +225                credentialsProvider,
    +226                callCredentialProvider,
    +227                transportOptions,
    +228                executorService,
    +229                SpannerCodec.forModel(
    +230                    modelInstance,
    +231                    SpannerMutationSerializer.forModel(
    +232                        modelInstance,
    +233                        driverSettings
    +234                    ),
    +235                    SpannerStructDeserializer.forModel(
    +236                        modelInstance,
    +237                        driverSettings
    +238                    )
    +239                ),
    +240                driverSettings
    +241            );
    +242        }
    +243    }
    +244
    +245    /** @inheritDoc */
    +246    @Override
    +247    public @Nonnull ListeningScheduledExecutorService executorService() {
    +248        return executorService;
    +249    }
    +250
    +251    /** @inheritDoc */
    +252    @Override
    +253    public @Nonnull ModelCodec<Model, Mutation, Struct> codec() {
    +254        return codec;
    +255    }
    +256
    +257    /** @inheritDoc */
    +258    @Override
    +259    public @Nonnull ReactiveFuture<Optional<Model>> retrieve(@Nonnull Key key,
    +260                                                             @Nonnull FetchOptions options) {
    +261        // null check all inputs
    +262        Objects.requireNonNull(key, "Cannot fetch model with `null` for key.");
    +263        Objects.requireNonNull(options, "Cannot fetch model without `options`.");
    +264        enforceRole(key, DatapointType.OBJECT_KEY);
    +265        var keyId = id(key).orElseThrow(() ->
    +266            new IllegalArgumentException("Cannot fetch model with empty key."));
    +267
    +268        // resolve the table where we should look for this entity
    +269        var table = resolveTableName(key);
    +270
    +271        // next, resolve the executor, database we should operate on, and corresponding client
    +272        ListeningScheduledExecutorService exec = options.executorService().orElseGet(this::executorService);
    +273        SpannerFetchOptions spannerOpts;
    +274
    +275        if (options.getClass().isAssignableFrom(SpannerFetchOptions.class)) {
    +276            spannerOpts = ((SpannerFetchOptions) options);
    +277        } else {
    +278            spannerOpts = SpannerFetchOptions.DEFAULTS;
    +279        }
    +280
    +281        DatabaseId db = spannerOpts.databaseId().orElse(defaultDatabase);
    +282        var client = engine.getDatabaseClient(db);
    +283        boolean transactional = spannerOpts.transactional().isPresent() && spannerOpts.transactional().get();
    +284
    +285        // with the DB client in hand, resolve the raw Spanner result
    +286        ReadContext context;
    +287        if (spannerOpts.timestampBound().isPresent()) {
    +288            if (transactional) {
    +289                context = client.readOnlyTransaction(spannerOpts.timestampBound().get());
    +290            } else {
    +291                context = client.singleUse(spannerOpts.timestampBound().get());
    +292            }
    +293        } else {
    +294            if (transactional) {
    +295                context = client.readOnlyTransaction();
    +296            } else {
    +297                context = client.singleUse();
    +298            }
    +299        }
    +300
    +301        // calculate the fields we should read from Spanner. because Spanner is a SQL-style DB with tables, we must
    +302        // enumerate each field we wish to load into the result set. this set of fields can either be specified via a
    +303        // field mask attached to the call options, or by generating a set of fields from the top-level model.
    +304        List<String> fieldsToRead;
    +305        if (spannerOpts.projection().isPresent()) {
    +306            fieldsToRead = FieldMaskUtil.normalize(spannerOpts.projection().get())
    +307                    .getPathsList();
    +308        } else {
    +309            fieldsToRead = calculateDefaultFields(
    +310                this.codec().instance().getDescriptorForType(),
    +311                driverSettings
    +312            );
    +313        }
    +314
    +315        var op = wrap(context.readRowAsync(
    +316            table,
    +317            com.google.cloud.spanner.Key.of(id(key).orElseThrow()),
    +318            fieldsToRead
    +319        ));
    +320
    +321        return wrap(transformAsync(withTimeout(op, 120, TimeUnit.SECONDS, exec), (result) -> {
    +322            if (result == null) {
    +323                if (logging.isDebugEnabled())
    +324                    logging.debug("Query option result was `null`. Returning empty result.");
    +325                return immediateFuture(Optional.empty());
    +326
    +327            } else {
    +328                if (logging.isDebugEnabled())
    +329                    logging.debug("Received non-null `Struct` result from Spanner. Deserializing...");
    +330
    +331                // deserialize the model
    +332                var deserialized = codec.deserialize(result);
    +333                if (logging.isDebugEnabled())
    +334                    logging.debug(format(
    +335                        "Found and deserialized model at ID '%s' from Spanner. Record follows:\n%s",
    +336                        keyId,
    +337                        deserialized));
    +338
    +339                return immediateFuture(Optional.of(
    +340                    spliceKey(applyMask(deserialized, options), Optional.of(key))
    +341                ));
    +342            }
    +343        }, exec));
    +344    }
    +345
    +346    /** @inheritDoc */
    +347    @Override
    +348    public @Nonnull ReactiveFuture<Model> persist(@Nullable Key key,
    +349                                                  @Nonnull Model model,
    +350                                                  @Nonnull WriteOptions options) {
    +351        // enforce model constraints
    +352        Objects.requireNonNull(key, "Cannot write model with `null` for key.");
    +353        Objects.requireNonNull(model, "Cannot write model which is, itself, `null`.");
    +354        Objects.requireNonNull(options, "Cannot write model without `options`.");
    +355        enforceRole(key, DatapointType.OBJECT_KEY);
    +356
    +357        // resolve executor
    +358        ListeningScheduledExecutorService exec = options.executorService().orElseGet(this::executorService);
    +359
    +360        // resolve existing ID, if any. if none can be resolved, generate one, and splice it into the key, and then
    +361        // likewise splice the key into the model. if an explicit ID is present, it is assumed that it is mounted
    +362        // correctly on the model.
    +363        Optional<Object> existingId = id(key);
    +364        Object modelId = existingId.orElseGet(() -> this.generateId(model));
    +365
    +366        if (existingId.isEmpty()) {
    +367            spliceKey(
    +368                model,
    +369                Optional.of(spliceId(
    +370                    key,
    +371                    Optional.of(modelId)
    +372                ))
    +373            );
    +374        }
    +375
    +376        try {
    +377            // resolve extended spanner mutation options
    +378            SpannerMutationOptions spannerOpts;
    +379            if (options.getClass().isAssignableFrom(SpannerMutationOptions.class)) {
    +380                spannerOpts = ((SpannerMutationOptions) options);
    +381            } else {
    +382                spannerOpts = SpannerMutationOptions.DEFAULTS;
    +383            }
    +384
    +385            // resolve the table where we should look for this entity
    +386            var table = resolveTableName(key);
    +387            DatabaseId db = spannerOpts.databaseId().orElse(defaultDatabase);
    +388            var client = engine.getDatabaseClient(db);
    +389            boolean transactional = spannerOpts.transactional().isPresent() ?
    +390                    spannerOpts.transactional().get() :
    +391                    options.transactional().orElse(false);
    +392
    +393            var writeMode = spannerOpts.writeMode().isPresent() ?
    +394                    spannerOpts.writeMode().get() :
    +395                    options.writeMode().orElse(WriteOptions.WriteDisposition.BLIND);
    +396
    +397            if (logging.isDebugEnabled())
    +398                logging.debug("Mode '{}' determined for Spanner write.", writeMode.name());
    +399
    +400            Mutation.WriteBuilder mutation;
    +401            switch (writeMode) {
    +402                case BLIND: mutation = Mutation.newInsertOrUpdateBuilder(table); break;
    +403                case MUST_EXIST: mutation = Mutation.newUpdateBuilder(table); break;
    +404                case MUST_NOT_EXIST: mutation = Mutation.newInsertBuilder(table); break;
    +405                default: mutation = Mutation.newReplaceBuilder(table); break;
    +406            }
    +407
    +408            if (codec instanceof SpannerCodec) {
    +409                if (logging.isTraceEnabled())
    +410                    logging.trace("Serializing model to Spanner `Mutation`: {}", model.toString());
    +411
    +412                // fill in the mutation
    +413                var serialized = ((SpannerCodec<Model>) codec).serialize(mutation, model);
    +414
    +415                return wrap(exec.submit(() -> {
    +416                    if (transactional && spannerOpts.transactionContext().isPresent()) {
    +417                        spannerOpts.transactionContext().get()
    +418                                .buffer(serialized);
    +419                    } else {
    +420                        // it's time to actually write the model
    +421                        var write = client.writeAtLeastOnce(Collections.singleton(serialized));
    +422                        Objects.requireNonNull(write, "write result from Spanner should never be null");
    +423                    }
    +424                    return model;
    +425                }));
    +426
    +427            } else {
    +428                throw new IllegalStateException("Cannot serialize Spanner model without `SpannerCodec`.");
    +429            }
    +430
    +431        } catch (IOException ioe) {
    +432            throw new IllegalStateException(ioe);
    +433        }
    +434    }
    +435
    +436    /** @inheritDoc */
    +437    @Override
    +438    public @Nonnull ReactiveFuture<Key> delete(@Nonnull Key key,
    +439                                               @Nonnull DeleteOptions baseOptions) {
    +440        Objects.requireNonNull(key, "cannot delete null key from Spanner");
    +441        Objects.requireNonNull(baseOptions, "cannot delete without valid Spanner options");
    +442        enforceRole(key, DatapointType.OBJECT_KEY);
    +443        Object keyId = id(key).orElseThrow(() ->
    +444                new IllegalArgumentException("Cannot delete key with empty or missing ID."));
    +445
    +446        SpannerMutationOptions options;
    +447        if (baseOptions instanceof SpannerMutationOptions) {
    +448            options = (SpannerMutationOptions) baseOptions;
    +449        } else {
    +450            options = SpannerMutationOptions.DEFAULTS;
    +451        }
    +452
    +453        // prep for an async delete action
    +454        ListeningScheduledExecutorService exec = options.executorService().orElseGet(this::executorService);
    +455
    +456        // resolve extended spanner mutation options
    +457        SpannerDeleteOptions spannerOpts;
    +458        if (options.getClass().isAssignableFrom(SpannerDeleteOptions.class)) {
    +459            spannerOpts = ((SpannerDeleteOptions) options);
    +460        } else {
    +461            spannerOpts = SpannerDeleteOptions.DEFAULTS;
    +462        }
    +463
    +464        // next, resolve the table we should work with, and any override DB
    +465        var table = resolveTableName(key);
    +466        DatabaseId db = spannerOpts.databaseId().orElse(defaultDatabase);
    +467        var client = engine.getDatabaseClient(db);
    +468        boolean transactional = spannerOpts.transactional().isPresent() ?
    +469                spannerOpts.transactional().get() :
    +470                options.transactional().orElse(false);
    +471
    +472        // prep the delete operation and fire it off
    +473        var deleteOperation = Mutation.delete(table, com.google.cloud.spanner.Key.of(keyId));
    +474        if (logging.isDebugEnabled())
    +475            logging.debug("Deleting model at ID `{}` in table `{}`.", keyId, table);
    +476        return wrap(exec.submit(() -> {
    +477            if (transactional && options.transactionContext().isPresent()) {
    +478                options.transactionContext().get().buffer(deleteOperation);
    +479            } else {
    +480                var result = client.write(Collections.singletonList(deleteOperation));
    +481                Objects.requireNonNull(result, "delete result from Spanner should never be null");
    +482            }
    +483            return key;
    +484        }));
    +485    }
    +486}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerDriverSettings.DefaultSettings.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerDriverSettings.DefaultSettings.html new file mode 100644 index 000000000..a544a053b --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerDriverSettings.DefaultSettings.html @@ -0,0 +1,145 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import javax.annotation.Nonnull;
    +016import javax.annotation.concurrent.Immutable;
    +017import javax.annotation.concurrent.ThreadSafe;
    +018
    +019
    +020/** Specifies settings for the Spanner driver and data storage implementation. */
    +021@Immutable
    +022@ThreadSafe
    +023public interface SpannerDriverSettings {
    +024    /** Concrete hard-coded driver defaults. */
    +025    final class DefaultSettings {
    +026        private DefaultSettings() { /* disallow construction */ }
    +027
    +028        /** Default value: Whether to preserve proto field names (`true`) or use JSON names (`false`, default). */
    +029        public static final Boolean DEFAULT_PRESERVE_FIELD_NAMES = false;
    +030
    +031        /** Default value: Whether to generate Spanner style names with initial capitals. */
    +032        public static final Boolean DEFAULT_CAPITALIZED_NAMES = true;
    +033
    +034        /** Default value: Whether to treat enumeration instances as numbers (`true`) or strings (`false`, default). */
    +035        public static final Boolean DEFAULT_ENUMS_AS_NUMBERS = false;
    +036
    +037        /** Default value: Whether to perform runtime deserialization checks (`true`, default) or not (`false`). */
    +038        public static final Boolean DEFAULT_CHECK_EXPECTED_TYPES = true;
    +039
    +040        /** Default size for `STRING` or `BYTES` column fields with no explicit setting. */
    +041        private static final int DEFAULT_COLUMN_SIZE = 2048;
    +042    }
    +043
    +044    /** Default set of configured settings for the Spanner driver. */
    +045    SpannerDriverSettings DEFAULTS = new SpannerDriverSettings() {};
    +046
    +047    /** @return Whether to preserve proto field names (`true`) or use JSON names (defaults to `false`). */
    +048    default @Nonnull Boolean preserveFieldNames() {
    +049        return DefaultSettings.DEFAULT_PRESERVE_FIELD_NAMES;
    +050    }
    +051
    +052    /** @return Whether to generate Spanner style names with initial capitals (i.e. `Name` instead of `name`). */
    +053    default @Nonnull Boolean defaultCapitalizedNames() {
    +054        return DefaultSettings.DEFAULT_CAPITALIZED_NAMES;
    +055    }
    +056
    +057    /** @return Whether to treat enumeration instances as numbers (`true`) or strings (defaults to `false`). */
    +058    default @Nonnull Boolean enumsAsNumbers() {
    +059        return DefaultSettings.DEFAULT_ENUMS_AS_NUMBERS;
    +060    }
    +061
    +062    /** @return Whether to perform runtime deserialization checks (defaults to `true`). */
    +063    default @Nonnull Boolean checkExpectedTypes() {
    +064        return DefaultSettings.DEFAULT_CHECK_EXPECTED_TYPES;
    +065    }
    +066
    +067    /** @return Default size to use for `STRING` or `BYTES` columns that don't otherwise specify a size. */
    +068    default int defaultColumnSize() {
    +069        return DefaultSettings.DEFAULT_COLUMN_SIZE;
    +070    }
    +071}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerDriverSettings.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerDriverSettings.html new file mode 100644 index 000000000..a544a053b --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerDriverSettings.html @@ -0,0 +1,145 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import javax.annotation.Nonnull;
    +016import javax.annotation.concurrent.Immutable;
    +017import javax.annotation.concurrent.ThreadSafe;
    +018
    +019
    +020/** Specifies settings for the Spanner driver and data storage implementation. */
    +021@Immutable
    +022@ThreadSafe
    +023public interface SpannerDriverSettings {
    +024    /** Concrete hard-coded driver defaults. */
    +025    final class DefaultSettings {
    +026        private DefaultSettings() { /* disallow construction */ }
    +027
    +028        /** Default value: Whether to preserve proto field names (`true`) or use JSON names (`false`, default). */
    +029        public static final Boolean DEFAULT_PRESERVE_FIELD_NAMES = false;
    +030
    +031        /** Default value: Whether to generate Spanner style names with initial capitals. */
    +032        public static final Boolean DEFAULT_CAPITALIZED_NAMES = true;
    +033
    +034        /** Default value: Whether to treat enumeration instances as numbers (`true`) or strings (`false`, default). */
    +035        public static final Boolean DEFAULT_ENUMS_AS_NUMBERS = false;
    +036
    +037        /** Default value: Whether to perform runtime deserialization checks (`true`, default) or not (`false`). */
    +038        public static final Boolean DEFAULT_CHECK_EXPECTED_TYPES = true;
    +039
    +040        /** Default size for `STRING` or `BYTES` column fields with no explicit setting. */
    +041        private static final int DEFAULT_COLUMN_SIZE = 2048;
    +042    }
    +043
    +044    /** Default set of configured settings for the Spanner driver. */
    +045    SpannerDriverSettings DEFAULTS = new SpannerDriverSettings() {};
    +046
    +047    /** @return Whether to preserve proto field names (`true`) or use JSON names (defaults to `false`). */
    +048    default @Nonnull Boolean preserveFieldNames() {
    +049        return DefaultSettings.DEFAULT_PRESERVE_FIELD_NAMES;
    +050    }
    +051
    +052    /** @return Whether to generate Spanner style names with initial capitals (i.e. `Name` instead of `name`). */
    +053    default @Nonnull Boolean defaultCapitalizedNames() {
    +054        return DefaultSettings.DEFAULT_CAPITALIZED_NAMES;
    +055    }
    +056
    +057    /** @return Whether to treat enumeration instances as numbers (`true`) or strings (defaults to `false`). */
    +058    default @Nonnull Boolean enumsAsNumbers() {
    +059        return DefaultSettings.DEFAULT_ENUMS_AS_NUMBERS;
    +060    }
    +061
    +062    /** @return Whether to perform runtime deserialization checks (defaults to `true`). */
    +063    default @Nonnull Boolean checkExpectedTypes() {
    +064        return DefaultSettings.DEFAULT_CHECK_EXPECTED_TYPES;
    +065    }
    +066
    +067    /** @return Default size to use for `STRING` or `BYTES` columns that don't otherwise specify a size. */
    +068    default int defaultColumnSize() {
    +069        return DefaultSettings.DEFAULT_COLUMN_SIZE;
    +070    }
    +071}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.Builder.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.Builder.html new file mode 100644 index 000000000..df55f2dd9 --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.Builder.html @@ -0,0 +1,859 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import com.google.cloud.spanner.Type;
    +016import com.google.protobuf.Message;
    +017import com.google.protobuf.Timestamp;
    +018import tools.elide.core.SpannerFieldOptions;
    +019
    +020import javax.annotation.Nonnull;
    +021import javax.annotation.concurrent.Immutable;
    +022import javax.annotation.concurrent.ThreadSafe;
    +023import java.util.*;
    +024import java.util.stream.Collectors;
    +025
    +026import static com.google.protobuf.Descriptors.Descriptor;
    +027import static com.google.protobuf.Descriptors.FieldDescriptor;
    +028import static gust.backend.driver.spanner.SpannerUtil.*;
    +029import static gust.backend.model.ModelMetadata.*;
    +030import static java.lang.String.format;
    +031import static java.lang.String.join;
    +032
    +033
    +034/** Container for generated schema-driven Spanner DDL. */
    +035@Immutable
    +036@ThreadSafe
    +037public final class SpannerGeneratedDDL {
    +038    /** Represents a DDL statement structure in code that can be rendered down to a string. */
    +039    public interface RenderableStatement {
    +040        /**
    +041         * Render this statement into a String buffer.
    +042         *
    +043         * @return Rendered statement.
    +044         */
    +045        @Nonnull StringBuilder render();
    +046    }
    +047
    +048    /** Sort direction settings which can apply to columns. */
    +049    public enum SortDirection {
    +050        /** Sort values in the column in ascending order. This is the default value. */
    +051        ASC,
    +052
    +053        /** Sort values in the column in descending order. */
    +054        DESC
    +055    }
    +056
    +057    /** Specifies options for reference action propagation (i.e. on-delete or on-update). */
    +058    public enum PropagatedAction {
    +059        /** Take no action. This is the default value. */
    +060        NO_ACTION,
    +061
    +062        /** Cascade changes on delete or update. */
    +063        CASCADE
    +064    }
    +065
    +066    /** Specifies a generic table constraint to include in a DDL statement. */
    +067    public static final class TableConstraint implements RenderableStatement {
    +068        final @Nonnull String name;
    +069        final @Nonnull String expression;
    +070
    +071        /**
    +072         * Private constructor for a table constraint specification.
    +073         *
    +074         * @param name Name of the table constraint.
    +075         * @param expression Expression to use as a constraint.
    +076         */
    +077        private TableConstraint(@Nonnull String name, @Nonnull String expression) {
    +078            this.name = name;
    +079            this.expression = expression;
    +080        }
    +081
    +082        /**
    +083         * Spawn a table constraint at the provided name, enforcing the provided expression.
    +084         *
    +085         * @param name Name of the constraint to enclose in the DDL statement.
    +086         * @param expression Expression to enforce as a table constraint.
    +087         * @return Table constraint specification.
    +088         */
    +089        public static @Nonnull TableConstraint named(@Nonnull String name,
    +090                                                     @Nonnull String expression) {
    +091            return new TableConstraint(name, expression);
    +092        }
    +093
    +094        /** @inheritDoc */
    +095        @Override
    +096        public @Nonnull StringBuilder render() {
    +097            return (new StringBuilder()).append(format(
    +098                "CONSTRAINT %s CHECK ( %s )",
    +099                this.name,
    +100                this.expression
    +101            ));
    +102        }
    +103    }
    +104
    +105    /** Specify a parent table against which this table is interleaved. */
    +106    public static final class InterleaveTarget implements RenderableStatement {
    +107        final @Nonnull String parent;
    +108        final @Nonnull Optional<PropagatedAction> action;
    +109
    +110        /**
    +111         * Private constructor for an interleave target for a Spanner table.
    +112         *
    +113         * @param parent Parent table where we should interleave this table.
    +114         * @param action Action to take, if any, when deletes or changes happen in the parent table.
    +115         */
    +116        private InterleaveTarget(@Nonnull String parent,
    +117                                 @Nonnull Optional<PropagatedAction> action) {
    +118            this.parent = parent;
    +119            this.action = action;
    +120        }
    +121
    +122        /**
    +123         * Generate an interleave target specification for the provided parent table name.
    +124         *
    +125         * @see #forParent(String, Optional) To pass a propagation action.
    +126         * @param parent Parent table where we should interleave a given table.
    +127         * @return Interleave target specification.
    +128         */
    +129        public static @Nonnull InterleaveTarget forParent(@Nonnull String parent) {
    +130            return forParent(parent, Optional.empty());
    +131        }
    +132
    +133        /**
    +134         * Generate an interleave target specification for the provided parent table name, optionally applying the
    +135         * provided propagation action.
    +136         *
    +137         * @param parent Parent table where we should interleave a given table.
    +138         * @param action Action to take, if any, when parent rows change that should affect the child table.
    +139         * @return Interleave target specification.
    +140         */
    +141        public static @Nonnull InterleaveTarget forParent(@Nonnull String parent,
    +142                                                          @Nonnull Optional<PropagatedAction> action) {
    +143            return new InterleaveTarget(parent, action);
    +144        }
    +145
    +146        /** @inheritDoc */
    +147        @Override
    +148        public @Nonnull StringBuilder render() {
    +149            var buf = new StringBuilder(format(
    +150                "INTERLEAVE IN PARENT %s",
    +151                this.parent
    +152            ));
    +153
    +154            if (this.action.isPresent()) {
    +155                buf.append(" ");
    +156                buf.append(format(
    +157                    "ON DELETE %s",
    +158                    this.action.get().name()
    +159                ));
    +160            }
    +161
    +162            return buf;
    +163        }
    +164    }
    +165
    +166    /** Specifies an individual field as part of a DDL statement. */
    +167    private static final class ColumnSpec implements RenderableStatement {
    +168        final @Nonnull String name;
    +169        final @Nonnull Type type;
    +170        final @Nonnull Integer length;
    +171        final @Nonnull FieldDescriptor field;
    +172        final @Nonnull Boolean nonnull;
    +173        final @Nonnull Optional<String> expression;
    +174        final @Nonnull Boolean expressionStored;
    +175        final @Nonnull Boolean allowCommitTimestamp;
    +176
    +177        /**
    +178         * Private constructor.
    +179         *
    +180         * @param name Column name in Spanner.
    +181         * @param type Type specification in Spanner.
    +182         * @param length Length for string fields, or `0`.
    +183         * @param nonnull Whether to mark the field as non-null.
    +184         * @param allowCommitTimestamp Whether to fill this column with the commit timestamp.
    +185         * @param field Model field that spawned this column specification.
    +186         */
    +187        ColumnSpec(@Nonnull String name,
    +188                   @Nonnull Type type,
    +189                   @Nonnull Integer length,
    +190                   @Nonnull Optional<Boolean> nonnull,
    +191                   @Nonnull Optional<String> expression,
    +192                   @Nonnull Optional<Boolean> expressionStored,
    +193                   @Nonnull Optional<Boolean> allowCommitTimestamp,
    +194                   @Nonnull FieldDescriptor field) {
    +195            this.name = name;
    +196            this.type = type;
    +197            this.length = length;
    +198            this.nonnull = nonnull.orElse(false);
    +199            this.expression = expression;
    +200            this.expressionStored = expressionStored.orElse(false);
    +201            this.allowCommitTimestamp = allowCommitTimestamp.orElse(false);
    +202            this.field = field;
    +203        }
    +204
    +205        /**
    +206         * Create a column spec for the provided field information, considering any active driver settings.
    +207         *
    +208         * @param fieldPointer Pointer to the field we should consider.
    +209         * @param settings Active set of Spanner driver settings.
    +210         * @return Spawned column corresponding to the provided field.
    +211         */
    +212        static @Nonnull ColumnSpec columnSpecForField(@Nonnull FieldPointer fieldPointer,
    +213                                                      @Nonnull SpannerDriverSettings settings) {
    +214            var columnOpts = columnOpts(fieldPointer);
    +215            var spannerOpts = spannerOpts(fieldPointer);
    +216            var fieldOpts = fieldOpts(fieldPointer);
    +217            var fieldName = resolveColumnName(fieldPointer, spannerOpts, columnOpts, settings);
    +218            var fieldType = resolveColumnType(fieldPointer, spannerOpts, columnOpts, settings);
    +219            Type innerType = fieldPointer.getField().isRepeated() ?
    +220                    fieldType.getArrayElementType() :
    +221                    fieldType;
    +222            var fieldSize = innerType.getCode() == Type.Code.STRING || innerType.getCode() == Type.Code.BYTES ?
    +223                    resolveColumnSize(fieldPointer.getField(), spannerOpts, columnOpts, settings) :
    +224                    -1;
    +225
    +226            // resolve spanner opts or defaults
    +227            var resolvedSpannerOpts = spannerOpts.orElse(SpannerFieldOptions.getDefaultInstance());
    +228            var expression = resolvedSpannerOpts.getExpression().length() > 0 ?
    +229                    Optional.of(resolvedSpannerOpts.getExpression()) : Optional.<String>empty();
    +230
    +231            var commitUpdate = false;
    +232            var protoType = fieldPointer.getField().getType();
    +233            if (fieldOpts.isPresent() && fieldOpts.get().getStampUpdate()) {
    +234                switch (protoType) {
    +235                    case STRING:
    +236                    case UINT64:
    +237                    case FIXED64:
    +238                        commitUpdate = true;
    +239                        break;
    +240
    +241                    case MESSAGE:
    +242                        if (fieldPointer.getField().getMessageType().getFullName().equals(
    +243                            Timestamp.getDescriptor().getFullName())) {
    +244                            commitUpdate = true;  // we can decode from a `Timestamp` record
    +245                        }
    +246                        break;
    +247
    +248                    default:
    +249                        // any other field type represents an illegal state
    +250                        throw new IllegalStateException(format(
    +251                            "Cannot place `commit_timestamp` in field of type '%s'", protoType.name()));
    +252                }
    +253            }
    +254
    +255            return new ColumnSpec(
    +256                fieldName,
    +257                fieldType,
    +258                fieldSize,
    +259                Optional.of(resolvedSpannerOpts.getNonnull()),
    +260                expression,
    +261                Optional.of(resolvedSpannerOpts.getStored()),
    +262                Optional.of(commitUpdate),
    +263                fieldPointer.getField()
    +264            );
    +265        }
    +266
    +267        /**
    +268         * Create a column spec for the provided model key field, considering any active driver settings.
    +269         *
    +270         * @param model Model schema for the object or key record.
    +271         * @param keyField Primary key field pre-resolved for a given Spanner table.
    +272         * @param settings Active Spanner driver settings.
    +273         * @return Spawned primary key column corresponding to the provided model key.
    +274         */
    +275        static @Nonnull ColumnSpec columnSpecForKey(@Nonnull Descriptor model,
    +276                                                    @Nonnull FieldPointer keyField,
    +277                                                    @Nonnull SpannerDriverSettings settings) {
    +278            var idField = idField(model).orElseThrow();
    +279            var keyName = resolveKeyColumn(idField, settings);
    +280            var keyType = resolveKeyType(idField);
    +281            var spannerOpts = spannerOpts(idField);
    +282            var columnOpts = columnOpts(idField);
    +283            int columnSize = -1;
    +284            if (keyType.getCode() == Type.Code.STRING ||
    +285                keyType.getCode() == Type.Code.BYTES) {
    +286                columnSize = resolveColumnSize(keyField.getField(), spannerOpts, columnOpts, settings);
    +287            }
    +288
    +289            return new ColumnSpec(
    +290                keyName,
    +291                keyType,
    +292                columnSize,
    +293                Optional.of(true),  // primary keys are always set to `NOT NULL`.
    +294                Optional.empty(),  // primary keys do not support expressions
    +295                Optional.empty(),
    +296                Optional.empty(),  // primary keys cannot be set to the commit timestamp
    +297                keyField.getField()
    +298            );
    +299        }
    +300
    +301        /**
    +302         * Render this column spec into a definition statement, suitable for use when creating a table.
    +303         *
    +304         * @return Rendered column spec statement.
    +305         */
    +306        @Override
    +307        public @Nonnull StringBuilder render() {
    +308            // prepare field statement
    +309            var buf = new StringBuilder();
    +310
    +311            // calculate field type designation first
    +312            String fieldType;
    +313            Type.Code innerType = this.field.isRepeated() ?
    +314                    this.type.getArrayElementType().getCode() :
    +315                    this.type.getCode();
    +316
    +317            String innerTypeSpec;
    +318            if (innerType == Type.Code.STRING || innerType == Type.Code.BYTES) {
    +319                innerTypeSpec = format(
    +320                    "%s(%s)",
    +321                    innerType.name(),
    +322                    this.length
    +323                );
    +324            } else {
    +325                innerTypeSpec = innerType.name();
    +326            }
    +327            if (this.type.getCode() == Type.Code.ARRAY) {
    +328                // it's a repeated field
    +329                fieldType = format(
    +330                    "ARRAY<%s>",
    +331                    innerTypeSpec
    +332                );
    +333            } else {
    +334                // it's a singular field. make sure to cover the special case for strings.
    +335                fieldType = innerTypeSpec;
    +336            }
    +337
    +338            buf.append(format(
    +339                "%s %s",
    +340                this.name,
    +341                fieldType
    +342            ));
    +343
    +344            // prepare field options
    +345            var optionsBuffer = new ArrayList<String>();
    +346
    +347            // consider NONNULL
    +348            if (this.nonnull) {
    +349                optionsBuffer.add("NOT NULL");
    +350            }
    +351
    +352            // consider expressions
    +353            if (this.expression.isPresent()) {
    +354                optionsBuffer.add(format("AS ( %s )", this.expression.get()));
    +355                if (this.expressionStored)
    +356                    optionsBuffer.add("STORED");
    +357            }
    +358
    +359            // consider options
    +360            if (this.allowCommitTimestamp) {
    +361                optionsBuffer.add("OPTIONS allow_commit_timestamp = true");
    +362            }
    +363            if (!optionsBuffer.isEmpty()) {
    +364                buf.append(" ");
    +365                buf.append(join(" ", optionsBuffer));
    +366            }
    +367            return buf;
    +368        }
    +369    }
    +370
    +371    /**
    +372     * Build properties for a generated Spanner table DDL statement, based on a given model instance as a base for
    +373     * configuring the table name (via annotations / calculated defaults) and set of typed Spanner value columns.
    +374     *
    +375     * <p>To build the actual DDL statement, fill out the builder, build it, and then ask the resulting object for the
    +376     * DDL as a string.</p>
    +377     */
    +378    @SuppressWarnings("unused")
    +379    public static final class Builder {
    +380        /** Base model on which this builder will operate. Immutable. */
    +381        final @Nonnull Descriptor model;
    +382
    +383        /** Active set of driver settings. Immutable. */
    +384        final @Nonnull SpannerDriverSettings settings;
    +385
    +386        /** Immutable: Name of the table in Spanner. */
    +387        final @Nonnull String tableName;
    +388
    +389        /** Immutable: Name of the primary key column. */
    +390        final @Nonnull String primaryKey;
    +391
    +392        /** Immutable: Generated columns in Spanner. */
    +393        final @Nonnull List<ColumnSpec> columns;
    +394
    +395        /** Mutable: Key column sort direction. */
    +396        @Nonnull SortDirection keySortDirection;
    +397
    +398        /** Mutable: List of table constraints. */
    +399        @Nonnull Optional<List<TableConstraint>> tableConstraints;
    +400
    +401        /** Mutable: Optimizer version to apply. */
    +402        @Nonnull Optional<Integer> optimizerVersion;
    +403
    +404        /** Mutable: Version retention period. */
    +405        @Nonnull Optional<String> versionRetentionPeriod;
    +406
    +407        /** Mutable: Table interleave target. */
    +408        @Nonnull Optional<InterleaveTarget> interleaveTarget;
    +409
    +410        /**
    +411         * Package-private constructor for a builder.
    +412         *
    +413         * @see SpannerGeneratedDDL#generateTableDDL(Descriptor, SpannerDriverSettings) to spawn one of
    +414         *      these from regular library or application code.
    +415         * @param model Descriptor for the model we are building against.
    +416         * @param primaryKey Primary key field name to use for this table by default.
    +417         * @param tableName Resolved table name to use for this table.
    +418         * @param defaultColumns Default set of columns to use for this table.
    +419         * @param settings Active driver settings to apply/consider.
    +420         */
    +421        Builder(@Nonnull Descriptor model,
    +422                @Nonnull String tableName,
    +423                @Nonnull String primaryKey,
    +424                @Nonnull List<ColumnSpec> defaultColumns,
    +425                @Nonnull SpannerDriverSettings settings) {
    +426            this.model = model;
    +427            this.tableName = tableName;
    +428            this.settings = settings;
    +429            this.columns = defaultColumns;
    +430            this.primaryKey = primaryKey;
    +431            this.keySortDirection = SortDirection.ASC;
    +432            this.tableConstraints = Optional.empty();
    +433            this.optimizerVersion = Optional.empty();
    +434            this.versionRetentionPeriod = Optional.empty();
    +435            this.interleaveTarget = Optional.empty();
    +436        }
    +437
    +438        /**
    +439         * Render column definition statements for a final DDL table create statement.
    +440         *
    +441         * @return Column definition statements, stacked in a buffer.
    +442         */
    +443        @Nonnull String renderColumnStatements() {
    +444            return this.columns.stream()
    +445                    .map(ColumnSpec::render)
    +446                    .collect(Collectors.joining(", "));
    +447        }
    +448
    +449        /**
    +450         * Render table-level constraint statements for a final DDL table create statement.
    +451         *
    +452         * @return Any applicable rendered table constraints.
    +453         */
    +454        @Nonnull String renderConstraintStatements() {
    +455            return this.tableConstraints.map(constraints -> constraints
    +456                    .stream()
    +457                    .map(TableConstraint::render)
    +458                    .collect(Collectors.joining(", ")))
    +459                    .orElse("");
    +460        }
    +461
    +462        /**
    +463         * Render inner statements in the CREATE TABLE DDL statement, including columns and constraints, as applicable.
    +464         * If no constraints are present, we simply return the column definitions alone.
    +465         *
    +466         * @return Rendered definitions of columns and table constraints.
    +467         */
    +468        @Nonnull String renderColumnStatementsAndConstraints() {
    +469            var columnList = renderColumnStatements();
    +470            if (this.tableConstraints.isPresent()) {
    +471                var constraints = renderConstraintStatements();
    +472                return format("%s, %s", columnList, constraints);
    +473            }
    +474            return columnList;
    +475        }
    +476
    +477        /**
    +478         * Render the primary key specification for a final DDL table create statement.
    +479         *
    +480         * @return Rendered primary key specification.
    +481         */
    +482        @Nonnull String renderPrimaryKey() {
    +483            return format(
    +484                "%s %s",
    +485                this.primaryKey,
    +486                this.keySortDirection.name()
    +487            );
    +488        }
    +489
    +490        /**
    +491         * Render the prepared DDL statement details into a statement string which can be passed to Spanner.
    +492         *
    +493         * @return Rendered DDL statement, according to local object settings.
    +494         */
    +495        @Nonnull StringBuilder renderCreateDDLStatement() {
    +496            var builder = new StringBuilder();
    +497            var buf = new ArrayList<StringBuilder>();
    +498            buf.add(new StringBuilder(format(
    +499                "CREATE TABLE %s (%s) PRIMARY KEY (%s)",
    +500                this.tableName,
    +501                this.renderColumnStatementsAndConstraints(),
    +502                this.renderPrimaryKey()
    +503            )));
    +504
    +505            // add interleave target statement, if specified
    +506            this.interleaveTarget.ifPresent(target -> buf.add(target.render()));
    +507
    +508            builder.append(join(", ", buf));
    +509            return builder;
    +510        }
    +511
    +512        /**
    +513         * Collapse the builder into an immutable DDL statement container
    +514         *
    +515         * @return Immutable DDL statement container.
    +516         */
    +517        public @Nonnull SpannerGeneratedDDL build() {
    +518            var fields = forEachField(
    +519                model,
    +520                Optional.of(onlySpannerEligibleFields(settings))
    +521            ).map((fieldPointer) ->
    +522                    ColumnSpec.columnSpecForField(fieldPointer, settings)
    +523            ).collect(Collectors.toUnmodifiableList());
    +524
    +525            return new SpannerGeneratedDDL(
    +526                tableName,
    +527                fields,
    +528                model,
    +529                renderCreateDDLStatement()
    +530            );
    +531        }
    +532
    +533        // -- Builder API: Getters -- //
    +534
    +535        /** @return Model descriptor this builder wraps. */
    +536        public @Nonnull Descriptor getModel() {
    +537            return model;
    +538        }
    +539
    +540        /** @return Active Spanner driver settings. */
    +541        public @Nonnull SpannerDriverSettings getSettings() {
    +542            return settings;
    +543        }
    +544
    +545        /** @return Generated or resolved Spanner table name. */
    +546        public @Nonnull String getTableName() {
    +547            return tableName;
    +548        }
    +549
    +550        /** @return Primary key column for this model/table. */
    +551        public @Nonnull String getPrimaryKey() {
    +552            return primaryKey;
    +553        }
    +554
    +555        /** @return Set of generated columns for this model in Spanner. */
    +556        public @Nonnull List<ColumnSpec> getColumns() {
    +557            return columns;
    +558        }
    +559
    +560        /** @return Primary key column sort direction. */
    +561        public @Nonnull SortDirection getKeySortDirection() {
    +562            return keySortDirection;
    +563        }
    +564
    +565        /** @return Set of constraints to apply to this table. */
    +566        public @Nonnull Optional<List<TableConstraint>> getTableConstraints() {
    +567            return tableConstraints;
    +568        }
    +569
    +570        /** @return Optimizer version to set for this table. */
    +571        public @Nonnull Optional<Integer> getOptimizerVersion() {
    +572            return optimizerVersion;
    +573        }
    +574
    +575        /** @return Data versioning retention period to set for this table. */
    +576        public @Nonnull Optional<String> getVersionRetentionPeriod() {
    +577            return versionRetentionPeriod;
    +578        }
    +579
    +580        /** @return Parent interleaving target for this table. */
    +581        public @Nonnull Optional<InterleaveTarget> getInterleaveTarget() {
    +582            return interleaveTarget;
    +583        }
    +584
    +585        // -- Builder API: Setters -- //
    +586
    +587        /**
    +588         * Set the sort direction for the primary key column in this table.
    +589         *
    +590         * @param keySortDirection Key column sort direction.
    +591         * @return Self, for chained calls to the builder.
    +592         */
    +593        public @Nonnull Builder setKeySortDirection(@Nonnull SortDirection keySortDirection) {
    +594            this.keySortDirection = keySortDirection;
    +595            return this;
    +596        }
    +597
    +598        /**
    +599         * Set, or clear, the set of table constraints added to this table.
    +600         *
    +601         * @param tableConstraints Desired table constraints to set or clear, as applicable.
    +602         * @return Self, for chained calls to the builder.
    +603         */
    +604        public @Nonnull Builder setTableConstraints(@Nonnull Optional<List<TableConstraint>> tableConstraints) {
    +605            this.tableConstraints = tableConstraints;
    +606            return this;
    +607        }
    +608
    +609        /**
    +610         * Set, or clear, the optimizer version to apply when creating this table.
    +611         *
    +612         * @param optimizerVersion Desired optimizer version to apply, as applicable.
    +613         * @return Self, for chained calls to the builder.
    +614         */
    +615        public @Nonnull Builder setOptimizerVersion(@Nonnull Optional<Integer> optimizerVersion) {
    +616            this.optimizerVersion = optimizerVersion;
    +617            return this;
    +618        }
    +619
    +620        /**
    +621         * Set, or clear, the data versioning retention period for this table.
    +622         *
    +623         * @param versionRetentionPeriod Desired data versioning retention period, as applicable.
    +624         * @return Self, for chained calls to the builder.
    +625         */
    +626        public @Nonnull Builder setVersionRetentionPeriod(@Nonnull Optional<String> versionRetentionPeriod) {
    +627            this.versionRetentionPeriod = versionRetentionPeriod;
    +628            return this;
    +629        }
    +630
    +631        /**
    +632         * Set, or clear, the parent interleave target for this table.
    +633         *
    +634         * @param interleaveTarget Desired parent interleave target, as applicable.
    +635         * @return Self, for chained calls to the builder.
    +636         */
    +637        public @Nonnull Builder setInterleaveTarget(@Nonnull Optional<InterleaveTarget> interleaveTarget) {
    +638            this.interleaveTarget = interleaveTarget;
    +639            return this;
    +640        }
    +641    }
    +642
    +643    /** Model that relates to this generated statement. */
    +644    private final @Nonnull Descriptor model;
    +645
    +646    /** Resolved name of the table. */
    +647    private final @Nonnull String tableName;
    +648
    +649    /** Set of generated columns determined to be part of this table. */
    +650    private final @Nonnull List<ColumnSpec> columns;
    +651
    +652    /** Holds the generated query in a string buffer. */
    +653    private final @Nonnull StringBuilder generatedStatement;
    +654
    +655    /**
    +656     * Private constructor.
    +657     *
    +658     * @param tableName Name of the generated table.
    +659     * @param columns Generated set of columns.
    +660     * @param model Model this table corresponds to.
    +661     * @param generatedStatement Rendered DDL statement.
    +662     */
    +663    private SpannerGeneratedDDL(@Nonnull String tableName,
    +664                                @Nonnull List<ColumnSpec> columns,
    +665                                @Nonnull Descriptor model,
    +666                                @Nonnull StringBuilder generatedStatement) {
    +667        this.tableName = tableName;
    +668        this.columns = columns;
    +669        this.generatedStatement = generatedStatement;
    +670        this.model = model;
    +671    }
    +672
    +673    /**
    +674     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +675     * that model's properties. This method variant operates from a full model instance.
    +676     *
    +677     * <p>This method offers no ability to control driver settings. See below if you need alternatives.</p>
    +678     *
    +679     * @see #generateTableDDL(Message, Optional) For control over driver settings, optionally.
    +680     * @param instance Model instance to generate a table statement for.
    +681     * @return Generated DDL statement object.
    +682     */
    +683    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(@Nonnull Message instance) {
    +684        return generateTableDDL(instance, Optional.of(SpannerDriverSettings.DEFAULTS));
    +685    }
    +686
    +687    /**
    +688     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +689     * that model's properties. This method variant operates from a full model instance.
    +690     *
    +691     * @param instance Model instance to generate a table statement for.
    +692     * @param settings Settings to employ for the driver. These must align at runtime.
    +693     * @return Generated DDL statement object.
    +694     */
    +695    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(
    +696            @Nonnull Message instance,
    +697            @Nonnull Optional<SpannerDriverSettings> settings) {
    +698        return generateTableDDL(
    +699            instance.getDescriptorForType(),
    +700            settings.orElse(SpannerDriverSettings.DEFAULTS)
    +701        );
    +702    }
    +703
    +704    /**
    +705     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +706     * that model's properties.
    +707     *
    +708     * @param model Model schema to generate a table statement for.
    +709     * @return Generated DDL statement object.
    +710     */
    +711    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(
    +712            @Nonnull Descriptor model,
    +713            @Nonnull SpannerDriverSettings settings) {
    +714        return new SpannerGeneratedDDL.Builder(
    +715            model,
    +716            resolveTableName(model),
    +717            resolveKeyColumn(idField(model).orElseThrow(), settings),
    +718            resolveDefaultColumns(model, settings),
    +719            settings
    +720        );
    +721    }
    +722
    +723    /**
    +724     * Resolve the default calculated set of Spanner columns for a given model structure.
    +725     *
    +726     * @param model Model to traverse and generate columns for.
    +727     * @return Set of generated and type-resolved columns.
    +728     */
    +729    public static @Nonnull List<ColumnSpec> resolveDefaultColumns(@Nonnull Descriptor model,
    +730                                                                  @Nonnull SpannerDriverSettings settings) {
    +731        var keyField = keyField(model).orElseThrow();
    +732        var fieldSet = new LinkedList<ColumnSpec>();
    +733
    +734        // first up: generate the column which implements the model's primary key
    +735        fieldSet.add(ColumnSpec.columnSpecForKey(
    +736            model,
    +737            keyField,
    +738            settings
    +739        ));
    +740
    +741        // next: generate all remaining data columns
    +742        forEachField(
    +743            model,
    +744            Optional.of(onlySpannerEligibleFields(settings))
    +745        ).filter((fieldPointer) ->
    +746            // filter out key fields: we'll handle those separately
    +747            !keyField.getField().getFullName().equals(fieldPointer.getField().getFullName())
    +748        ).map((fieldPointer) ->
    +749            ColumnSpec.columnSpecForField(fieldPointer, settings)
    +750        ).forEach(fieldSet::add);
    +751
    +752        return Collections.unmodifiableList(fieldSet);
    +753    }
    +754
    +755    // -- Accessors -- //
    +756
    +757    /** @return Model for which this object generates a table create statement. */
    +758    public @Nonnull Descriptor getModel() {
    +759        return model;
    +760    }
    +761
    +762    /** @return Resolved name of the table to be created. */
    +763    public @Nonnull String getTableName() {
    +764        return tableName;
    +765    }
    +766
    +767    /** @return Resolved set of Spanner columns. */
    +768    public @Nonnull List<ColumnSpec> getColumns() {
    +769        return columns;
    +770    }
    +771
    +772    /** @return Rendered generated DDL statement. */
    +773    public @Nonnull StringBuilder getGeneratedStatement() {
    +774        return generatedStatement;
    +775    }
    +776
    +777    @Override
    +778    public String toString() {
    +779        return "SpannerDDL{" +
    +780            "model=" + model.getFullName() +
    +781            ", tableName='" + tableName + '\'' +
    +782            ", statement=\"" + generatedStatement.toString() +
    +783        "\"}";
    +784    }
    +785}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.InterleaveTarget.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.InterleaveTarget.html new file mode 100644 index 000000000..df55f2dd9 --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.InterleaveTarget.html @@ -0,0 +1,859 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import com.google.cloud.spanner.Type;
    +016import com.google.protobuf.Message;
    +017import com.google.protobuf.Timestamp;
    +018import tools.elide.core.SpannerFieldOptions;
    +019
    +020import javax.annotation.Nonnull;
    +021import javax.annotation.concurrent.Immutable;
    +022import javax.annotation.concurrent.ThreadSafe;
    +023import java.util.*;
    +024import java.util.stream.Collectors;
    +025
    +026import static com.google.protobuf.Descriptors.Descriptor;
    +027import static com.google.protobuf.Descriptors.FieldDescriptor;
    +028import static gust.backend.driver.spanner.SpannerUtil.*;
    +029import static gust.backend.model.ModelMetadata.*;
    +030import static java.lang.String.format;
    +031import static java.lang.String.join;
    +032
    +033
    +034/** Container for generated schema-driven Spanner DDL. */
    +035@Immutable
    +036@ThreadSafe
    +037public final class SpannerGeneratedDDL {
    +038    /** Represents a DDL statement structure in code that can be rendered down to a string. */
    +039    public interface RenderableStatement {
    +040        /**
    +041         * Render this statement into a String buffer.
    +042         *
    +043         * @return Rendered statement.
    +044         */
    +045        @Nonnull StringBuilder render();
    +046    }
    +047
    +048    /** Sort direction settings which can apply to columns. */
    +049    public enum SortDirection {
    +050        /** Sort values in the column in ascending order. This is the default value. */
    +051        ASC,
    +052
    +053        /** Sort values in the column in descending order. */
    +054        DESC
    +055    }
    +056
    +057    /** Specifies options for reference action propagation (i.e. on-delete or on-update). */
    +058    public enum PropagatedAction {
    +059        /** Take no action. This is the default value. */
    +060        NO_ACTION,
    +061
    +062        /** Cascade changes on delete or update. */
    +063        CASCADE
    +064    }
    +065
    +066    /** Specifies a generic table constraint to include in a DDL statement. */
    +067    public static final class TableConstraint implements RenderableStatement {
    +068        final @Nonnull String name;
    +069        final @Nonnull String expression;
    +070
    +071        /**
    +072         * Private constructor for a table constraint specification.
    +073         *
    +074         * @param name Name of the table constraint.
    +075         * @param expression Expression to use as a constraint.
    +076         */
    +077        private TableConstraint(@Nonnull String name, @Nonnull String expression) {
    +078            this.name = name;
    +079            this.expression = expression;
    +080        }
    +081
    +082        /**
    +083         * Spawn a table constraint at the provided name, enforcing the provided expression.
    +084         *
    +085         * @param name Name of the constraint to enclose in the DDL statement.
    +086         * @param expression Expression to enforce as a table constraint.
    +087         * @return Table constraint specification.
    +088         */
    +089        public static @Nonnull TableConstraint named(@Nonnull String name,
    +090                                                     @Nonnull String expression) {
    +091            return new TableConstraint(name, expression);
    +092        }
    +093
    +094        /** @inheritDoc */
    +095        @Override
    +096        public @Nonnull StringBuilder render() {
    +097            return (new StringBuilder()).append(format(
    +098                "CONSTRAINT %s CHECK ( %s )",
    +099                this.name,
    +100                this.expression
    +101            ));
    +102        }
    +103    }
    +104
    +105    /** Specify a parent table against which this table is interleaved. */
    +106    public static final class InterleaveTarget implements RenderableStatement {
    +107        final @Nonnull String parent;
    +108        final @Nonnull Optional<PropagatedAction> action;
    +109
    +110        /**
    +111         * Private constructor for an interleave target for a Spanner table.
    +112         *
    +113         * @param parent Parent table where we should interleave this table.
    +114         * @param action Action to take, if any, when deletes or changes happen in the parent table.
    +115         */
    +116        private InterleaveTarget(@Nonnull String parent,
    +117                                 @Nonnull Optional<PropagatedAction> action) {
    +118            this.parent = parent;
    +119            this.action = action;
    +120        }
    +121
    +122        /**
    +123         * Generate an interleave target specification for the provided parent table name.
    +124         *
    +125         * @see #forParent(String, Optional) To pass a propagation action.
    +126         * @param parent Parent table where we should interleave a given table.
    +127         * @return Interleave target specification.
    +128         */
    +129        public static @Nonnull InterleaveTarget forParent(@Nonnull String parent) {
    +130            return forParent(parent, Optional.empty());
    +131        }
    +132
    +133        /**
    +134         * Generate an interleave target specification for the provided parent table name, optionally applying the
    +135         * provided propagation action.
    +136         *
    +137         * @param parent Parent table where we should interleave a given table.
    +138         * @param action Action to take, if any, when parent rows change that should affect the child table.
    +139         * @return Interleave target specification.
    +140         */
    +141        public static @Nonnull InterleaveTarget forParent(@Nonnull String parent,
    +142                                                          @Nonnull Optional<PropagatedAction> action) {
    +143            return new InterleaveTarget(parent, action);
    +144        }
    +145
    +146        /** @inheritDoc */
    +147        @Override
    +148        public @Nonnull StringBuilder render() {
    +149            var buf = new StringBuilder(format(
    +150                "INTERLEAVE IN PARENT %s",
    +151                this.parent
    +152            ));
    +153
    +154            if (this.action.isPresent()) {
    +155                buf.append(" ");
    +156                buf.append(format(
    +157                    "ON DELETE %s",
    +158                    this.action.get().name()
    +159                ));
    +160            }
    +161
    +162            return buf;
    +163        }
    +164    }
    +165
    +166    /** Specifies an individual field as part of a DDL statement. */
    +167    private static final class ColumnSpec implements RenderableStatement {
    +168        final @Nonnull String name;
    +169        final @Nonnull Type type;
    +170        final @Nonnull Integer length;
    +171        final @Nonnull FieldDescriptor field;
    +172        final @Nonnull Boolean nonnull;
    +173        final @Nonnull Optional<String> expression;
    +174        final @Nonnull Boolean expressionStored;
    +175        final @Nonnull Boolean allowCommitTimestamp;
    +176
    +177        /**
    +178         * Private constructor.
    +179         *
    +180         * @param name Column name in Spanner.
    +181         * @param type Type specification in Spanner.
    +182         * @param length Length for string fields, or `0`.
    +183         * @param nonnull Whether to mark the field as non-null.
    +184         * @param allowCommitTimestamp Whether to fill this column with the commit timestamp.
    +185         * @param field Model field that spawned this column specification.
    +186         */
    +187        ColumnSpec(@Nonnull String name,
    +188                   @Nonnull Type type,
    +189                   @Nonnull Integer length,
    +190                   @Nonnull Optional<Boolean> nonnull,
    +191                   @Nonnull Optional<String> expression,
    +192                   @Nonnull Optional<Boolean> expressionStored,
    +193                   @Nonnull Optional<Boolean> allowCommitTimestamp,
    +194                   @Nonnull FieldDescriptor field) {
    +195            this.name = name;
    +196            this.type = type;
    +197            this.length = length;
    +198            this.nonnull = nonnull.orElse(false);
    +199            this.expression = expression;
    +200            this.expressionStored = expressionStored.orElse(false);
    +201            this.allowCommitTimestamp = allowCommitTimestamp.orElse(false);
    +202            this.field = field;
    +203        }
    +204
    +205        /**
    +206         * Create a column spec for the provided field information, considering any active driver settings.
    +207         *
    +208         * @param fieldPointer Pointer to the field we should consider.
    +209         * @param settings Active set of Spanner driver settings.
    +210         * @return Spawned column corresponding to the provided field.
    +211         */
    +212        static @Nonnull ColumnSpec columnSpecForField(@Nonnull FieldPointer fieldPointer,
    +213                                                      @Nonnull SpannerDriverSettings settings) {
    +214            var columnOpts = columnOpts(fieldPointer);
    +215            var spannerOpts = spannerOpts(fieldPointer);
    +216            var fieldOpts = fieldOpts(fieldPointer);
    +217            var fieldName = resolveColumnName(fieldPointer, spannerOpts, columnOpts, settings);
    +218            var fieldType = resolveColumnType(fieldPointer, spannerOpts, columnOpts, settings);
    +219            Type innerType = fieldPointer.getField().isRepeated() ?
    +220                    fieldType.getArrayElementType() :
    +221                    fieldType;
    +222            var fieldSize = innerType.getCode() == Type.Code.STRING || innerType.getCode() == Type.Code.BYTES ?
    +223                    resolveColumnSize(fieldPointer.getField(), spannerOpts, columnOpts, settings) :
    +224                    -1;
    +225
    +226            // resolve spanner opts or defaults
    +227            var resolvedSpannerOpts = spannerOpts.orElse(SpannerFieldOptions.getDefaultInstance());
    +228            var expression = resolvedSpannerOpts.getExpression().length() > 0 ?
    +229                    Optional.of(resolvedSpannerOpts.getExpression()) : Optional.<String>empty();
    +230
    +231            var commitUpdate = false;
    +232            var protoType = fieldPointer.getField().getType();
    +233            if (fieldOpts.isPresent() && fieldOpts.get().getStampUpdate()) {
    +234                switch (protoType) {
    +235                    case STRING:
    +236                    case UINT64:
    +237                    case FIXED64:
    +238                        commitUpdate = true;
    +239                        break;
    +240
    +241                    case MESSAGE:
    +242                        if (fieldPointer.getField().getMessageType().getFullName().equals(
    +243                            Timestamp.getDescriptor().getFullName())) {
    +244                            commitUpdate = true;  // we can decode from a `Timestamp` record
    +245                        }
    +246                        break;
    +247
    +248                    default:
    +249                        // any other field type represents an illegal state
    +250                        throw new IllegalStateException(format(
    +251                            "Cannot place `commit_timestamp` in field of type '%s'", protoType.name()));
    +252                }
    +253            }
    +254
    +255            return new ColumnSpec(
    +256                fieldName,
    +257                fieldType,
    +258                fieldSize,
    +259                Optional.of(resolvedSpannerOpts.getNonnull()),
    +260                expression,
    +261                Optional.of(resolvedSpannerOpts.getStored()),
    +262                Optional.of(commitUpdate),
    +263                fieldPointer.getField()
    +264            );
    +265        }
    +266
    +267        /**
    +268         * Create a column spec for the provided model key field, considering any active driver settings.
    +269         *
    +270         * @param model Model schema for the object or key record.
    +271         * @param keyField Primary key field pre-resolved for a given Spanner table.
    +272         * @param settings Active Spanner driver settings.
    +273         * @return Spawned primary key column corresponding to the provided model key.
    +274         */
    +275        static @Nonnull ColumnSpec columnSpecForKey(@Nonnull Descriptor model,
    +276                                                    @Nonnull FieldPointer keyField,
    +277                                                    @Nonnull SpannerDriverSettings settings) {
    +278            var idField = idField(model).orElseThrow();
    +279            var keyName = resolveKeyColumn(idField, settings);
    +280            var keyType = resolveKeyType(idField);
    +281            var spannerOpts = spannerOpts(idField);
    +282            var columnOpts = columnOpts(idField);
    +283            int columnSize = -1;
    +284            if (keyType.getCode() == Type.Code.STRING ||
    +285                keyType.getCode() == Type.Code.BYTES) {
    +286                columnSize = resolveColumnSize(keyField.getField(), spannerOpts, columnOpts, settings);
    +287            }
    +288
    +289            return new ColumnSpec(
    +290                keyName,
    +291                keyType,
    +292                columnSize,
    +293                Optional.of(true),  // primary keys are always set to `NOT NULL`.
    +294                Optional.empty(),  // primary keys do not support expressions
    +295                Optional.empty(),
    +296                Optional.empty(),  // primary keys cannot be set to the commit timestamp
    +297                keyField.getField()
    +298            );
    +299        }
    +300
    +301        /**
    +302         * Render this column spec into a definition statement, suitable for use when creating a table.
    +303         *
    +304         * @return Rendered column spec statement.
    +305         */
    +306        @Override
    +307        public @Nonnull StringBuilder render() {
    +308            // prepare field statement
    +309            var buf = new StringBuilder();
    +310
    +311            // calculate field type designation first
    +312            String fieldType;
    +313            Type.Code innerType = this.field.isRepeated() ?
    +314                    this.type.getArrayElementType().getCode() :
    +315                    this.type.getCode();
    +316
    +317            String innerTypeSpec;
    +318            if (innerType == Type.Code.STRING || innerType == Type.Code.BYTES) {
    +319                innerTypeSpec = format(
    +320                    "%s(%s)",
    +321                    innerType.name(),
    +322                    this.length
    +323                );
    +324            } else {
    +325                innerTypeSpec = innerType.name();
    +326            }
    +327            if (this.type.getCode() == Type.Code.ARRAY) {
    +328                // it's a repeated field
    +329                fieldType = format(
    +330                    "ARRAY<%s>",
    +331                    innerTypeSpec
    +332                );
    +333            } else {
    +334                // it's a singular field. make sure to cover the special case for strings.
    +335                fieldType = innerTypeSpec;
    +336            }
    +337
    +338            buf.append(format(
    +339                "%s %s",
    +340                this.name,
    +341                fieldType
    +342            ));
    +343
    +344            // prepare field options
    +345            var optionsBuffer = new ArrayList<String>();
    +346
    +347            // consider NONNULL
    +348            if (this.nonnull) {
    +349                optionsBuffer.add("NOT NULL");
    +350            }
    +351
    +352            // consider expressions
    +353            if (this.expression.isPresent()) {
    +354                optionsBuffer.add(format("AS ( %s )", this.expression.get()));
    +355                if (this.expressionStored)
    +356                    optionsBuffer.add("STORED");
    +357            }
    +358
    +359            // consider options
    +360            if (this.allowCommitTimestamp) {
    +361                optionsBuffer.add("OPTIONS allow_commit_timestamp = true");
    +362            }
    +363            if (!optionsBuffer.isEmpty()) {
    +364                buf.append(" ");
    +365                buf.append(join(" ", optionsBuffer));
    +366            }
    +367            return buf;
    +368        }
    +369    }
    +370
    +371    /**
    +372     * Build properties for a generated Spanner table DDL statement, based on a given model instance as a base for
    +373     * configuring the table name (via annotations / calculated defaults) and set of typed Spanner value columns.
    +374     *
    +375     * <p>To build the actual DDL statement, fill out the builder, build it, and then ask the resulting object for the
    +376     * DDL as a string.</p>
    +377     */
    +378    @SuppressWarnings("unused")
    +379    public static final class Builder {
    +380        /** Base model on which this builder will operate. Immutable. */
    +381        final @Nonnull Descriptor model;
    +382
    +383        /** Active set of driver settings. Immutable. */
    +384        final @Nonnull SpannerDriverSettings settings;
    +385
    +386        /** Immutable: Name of the table in Spanner. */
    +387        final @Nonnull String tableName;
    +388
    +389        /** Immutable: Name of the primary key column. */
    +390        final @Nonnull String primaryKey;
    +391
    +392        /** Immutable: Generated columns in Spanner. */
    +393        final @Nonnull List<ColumnSpec> columns;
    +394
    +395        /** Mutable: Key column sort direction. */
    +396        @Nonnull SortDirection keySortDirection;
    +397
    +398        /** Mutable: List of table constraints. */
    +399        @Nonnull Optional<List<TableConstraint>> tableConstraints;
    +400
    +401        /** Mutable: Optimizer version to apply. */
    +402        @Nonnull Optional<Integer> optimizerVersion;
    +403
    +404        /** Mutable: Version retention period. */
    +405        @Nonnull Optional<String> versionRetentionPeriod;
    +406
    +407        /** Mutable: Table interleave target. */
    +408        @Nonnull Optional<InterleaveTarget> interleaveTarget;
    +409
    +410        /**
    +411         * Package-private constructor for a builder.
    +412         *
    +413         * @see SpannerGeneratedDDL#generateTableDDL(Descriptor, SpannerDriverSettings) to spawn one of
    +414         *      these from regular library or application code.
    +415         * @param model Descriptor for the model we are building against.
    +416         * @param primaryKey Primary key field name to use for this table by default.
    +417         * @param tableName Resolved table name to use for this table.
    +418         * @param defaultColumns Default set of columns to use for this table.
    +419         * @param settings Active driver settings to apply/consider.
    +420         */
    +421        Builder(@Nonnull Descriptor model,
    +422                @Nonnull String tableName,
    +423                @Nonnull String primaryKey,
    +424                @Nonnull List<ColumnSpec> defaultColumns,
    +425                @Nonnull SpannerDriverSettings settings) {
    +426            this.model = model;
    +427            this.tableName = tableName;
    +428            this.settings = settings;
    +429            this.columns = defaultColumns;
    +430            this.primaryKey = primaryKey;
    +431            this.keySortDirection = SortDirection.ASC;
    +432            this.tableConstraints = Optional.empty();
    +433            this.optimizerVersion = Optional.empty();
    +434            this.versionRetentionPeriod = Optional.empty();
    +435            this.interleaveTarget = Optional.empty();
    +436        }
    +437
    +438        /**
    +439         * Render column definition statements for a final DDL table create statement.
    +440         *
    +441         * @return Column definition statements, stacked in a buffer.
    +442         */
    +443        @Nonnull String renderColumnStatements() {
    +444            return this.columns.stream()
    +445                    .map(ColumnSpec::render)
    +446                    .collect(Collectors.joining(", "));
    +447        }
    +448
    +449        /**
    +450         * Render table-level constraint statements for a final DDL table create statement.
    +451         *
    +452         * @return Any applicable rendered table constraints.
    +453         */
    +454        @Nonnull String renderConstraintStatements() {
    +455            return this.tableConstraints.map(constraints -> constraints
    +456                    .stream()
    +457                    .map(TableConstraint::render)
    +458                    .collect(Collectors.joining(", ")))
    +459                    .orElse("");
    +460        }
    +461
    +462        /**
    +463         * Render inner statements in the CREATE TABLE DDL statement, including columns and constraints, as applicable.
    +464         * If no constraints are present, we simply return the column definitions alone.
    +465         *
    +466         * @return Rendered definitions of columns and table constraints.
    +467         */
    +468        @Nonnull String renderColumnStatementsAndConstraints() {
    +469            var columnList = renderColumnStatements();
    +470            if (this.tableConstraints.isPresent()) {
    +471                var constraints = renderConstraintStatements();
    +472                return format("%s, %s", columnList, constraints);
    +473            }
    +474            return columnList;
    +475        }
    +476
    +477        /**
    +478         * Render the primary key specification for a final DDL table create statement.
    +479         *
    +480         * @return Rendered primary key specification.
    +481         */
    +482        @Nonnull String renderPrimaryKey() {
    +483            return format(
    +484                "%s %s",
    +485                this.primaryKey,
    +486                this.keySortDirection.name()
    +487            );
    +488        }
    +489
    +490        /**
    +491         * Render the prepared DDL statement details into a statement string which can be passed to Spanner.
    +492         *
    +493         * @return Rendered DDL statement, according to local object settings.
    +494         */
    +495        @Nonnull StringBuilder renderCreateDDLStatement() {
    +496            var builder = new StringBuilder();
    +497            var buf = new ArrayList<StringBuilder>();
    +498            buf.add(new StringBuilder(format(
    +499                "CREATE TABLE %s (%s) PRIMARY KEY (%s)",
    +500                this.tableName,
    +501                this.renderColumnStatementsAndConstraints(),
    +502                this.renderPrimaryKey()
    +503            )));
    +504
    +505            // add interleave target statement, if specified
    +506            this.interleaveTarget.ifPresent(target -> buf.add(target.render()));
    +507
    +508            builder.append(join(", ", buf));
    +509            return builder;
    +510        }
    +511
    +512        /**
    +513         * Collapse the builder into an immutable DDL statement container
    +514         *
    +515         * @return Immutable DDL statement container.
    +516         */
    +517        public @Nonnull SpannerGeneratedDDL build() {
    +518            var fields = forEachField(
    +519                model,
    +520                Optional.of(onlySpannerEligibleFields(settings))
    +521            ).map((fieldPointer) ->
    +522                    ColumnSpec.columnSpecForField(fieldPointer, settings)
    +523            ).collect(Collectors.toUnmodifiableList());
    +524
    +525            return new SpannerGeneratedDDL(
    +526                tableName,
    +527                fields,
    +528                model,
    +529                renderCreateDDLStatement()
    +530            );
    +531        }
    +532
    +533        // -- Builder API: Getters -- //
    +534
    +535        /** @return Model descriptor this builder wraps. */
    +536        public @Nonnull Descriptor getModel() {
    +537            return model;
    +538        }
    +539
    +540        /** @return Active Spanner driver settings. */
    +541        public @Nonnull SpannerDriverSettings getSettings() {
    +542            return settings;
    +543        }
    +544
    +545        /** @return Generated or resolved Spanner table name. */
    +546        public @Nonnull String getTableName() {
    +547            return tableName;
    +548        }
    +549
    +550        /** @return Primary key column for this model/table. */
    +551        public @Nonnull String getPrimaryKey() {
    +552            return primaryKey;
    +553        }
    +554
    +555        /** @return Set of generated columns for this model in Spanner. */
    +556        public @Nonnull List<ColumnSpec> getColumns() {
    +557            return columns;
    +558        }
    +559
    +560        /** @return Primary key column sort direction. */
    +561        public @Nonnull SortDirection getKeySortDirection() {
    +562            return keySortDirection;
    +563        }
    +564
    +565        /** @return Set of constraints to apply to this table. */
    +566        public @Nonnull Optional<List<TableConstraint>> getTableConstraints() {
    +567            return tableConstraints;
    +568        }
    +569
    +570        /** @return Optimizer version to set for this table. */
    +571        public @Nonnull Optional<Integer> getOptimizerVersion() {
    +572            return optimizerVersion;
    +573        }
    +574
    +575        /** @return Data versioning retention period to set for this table. */
    +576        public @Nonnull Optional<String> getVersionRetentionPeriod() {
    +577            return versionRetentionPeriod;
    +578        }
    +579
    +580        /** @return Parent interleaving target for this table. */
    +581        public @Nonnull Optional<InterleaveTarget> getInterleaveTarget() {
    +582            return interleaveTarget;
    +583        }
    +584
    +585        // -- Builder API: Setters -- //
    +586
    +587        /**
    +588         * Set the sort direction for the primary key column in this table.
    +589         *
    +590         * @param keySortDirection Key column sort direction.
    +591         * @return Self, for chained calls to the builder.
    +592         */
    +593        public @Nonnull Builder setKeySortDirection(@Nonnull SortDirection keySortDirection) {
    +594            this.keySortDirection = keySortDirection;
    +595            return this;
    +596        }
    +597
    +598        /**
    +599         * Set, or clear, the set of table constraints added to this table.
    +600         *
    +601         * @param tableConstraints Desired table constraints to set or clear, as applicable.
    +602         * @return Self, for chained calls to the builder.
    +603         */
    +604        public @Nonnull Builder setTableConstraints(@Nonnull Optional<List<TableConstraint>> tableConstraints) {
    +605            this.tableConstraints = tableConstraints;
    +606            return this;
    +607        }
    +608
    +609        /**
    +610         * Set, or clear, the optimizer version to apply when creating this table.
    +611         *
    +612         * @param optimizerVersion Desired optimizer version to apply, as applicable.
    +613         * @return Self, for chained calls to the builder.
    +614         */
    +615        public @Nonnull Builder setOptimizerVersion(@Nonnull Optional<Integer> optimizerVersion) {
    +616            this.optimizerVersion = optimizerVersion;
    +617            return this;
    +618        }
    +619
    +620        /**
    +621         * Set, or clear, the data versioning retention period for this table.
    +622         *
    +623         * @param versionRetentionPeriod Desired data versioning retention period, as applicable.
    +624         * @return Self, for chained calls to the builder.
    +625         */
    +626        public @Nonnull Builder setVersionRetentionPeriod(@Nonnull Optional<String> versionRetentionPeriod) {
    +627            this.versionRetentionPeriod = versionRetentionPeriod;
    +628            return this;
    +629        }
    +630
    +631        /**
    +632         * Set, or clear, the parent interleave target for this table.
    +633         *
    +634         * @param interleaveTarget Desired parent interleave target, as applicable.
    +635         * @return Self, for chained calls to the builder.
    +636         */
    +637        public @Nonnull Builder setInterleaveTarget(@Nonnull Optional<InterleaveTarget> interleaveTarget) {
    +638            this.interleaveTarget = interleaveTarget;
    +639            return this;
    +640        }
    +641    }
    +642
    +643    /** Model that relates to this generated statement. */
    +644    private final @Nonnull Descriptor model;
    +645
    +646    /** Resolved name of the table. */
    +647    private final @Nonnull String tableName;
    +648
    +649    /** Set of generated columns determined to be part of this table. */
    +650    private final @Nonnull List<ColumnSpec> columns;
    +651
    +652    /** Holds the generated query in a string buffer. */
    +653    private final @Nonnull StringBuilder generatedStatement;
    +654
    +655    /**
    +656     * Private constructor.
    +657     *
    +658     * @param tableName Name of the generated table.
    +659     * @param columns Generated set of columns.
    +660     * @param model Model this table corresponds to.
    +661     * @param generatedStatement Rendered DDL statement.
    +662     */
    +663    private SpannerGeneratedDDL(@Nonnull String tableName,
    +664                                @Nonnull List<ColumnSpec> columns,
    +665                                @Nonnull Descriptor model,
    +666                                @Nonnull StringBuilder generatedStatement) {
    +667        this.tableName = tableName;
    +668        this.columns = columns;
    +669        this.generatedStatement = generatedStatement;
    +670        this.model = model;
    +671    }
    +672
    +673    /**
    +674     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +675     * that model's properties. This method variant operates from a full model instance.
    +676     *
    +677     * <p>This method offers no ability to control driver settings. See below if you need alternatives.</p>
    +678     *
    +679     * @see #generateTableDDL(Message, Optional) For control over driver settings, optionally.
    +680     * @param instance Model instance to generate a table statement for.
    +681     * @return Generated DDL statement object.
    +682     */
    +683    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(@Nonnull Message instance) {
    +684        return generateTableDDL(instance, Optional.of(SpannerDriverSettings.DEFAULTS));
    +685    }
    +686
    +687    /**
    +688     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +689     * that model's properties. This method variant operates from a full model instance.
    +690     *
    +691     * @param instance Model instance to generate a table statement for.
    +692     * @param settings Settings to employ for the driver. These must align at runtime.
    +693     * @return Generated DDL statement object.
    +694     */
    +695    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(
    +696            @Nonnull Message instance,
    +697            @Nonnull Optional<SpannerDriverSettings> settings) {
    +698        return generateTableDDL(
    +699            instance.getDescriptorForType(),
    +700            settings.orElse(SpannerDriverSettings.DEFAULTS)
    +701        );
    +702    }
    +703
    +704    /**
    +705     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +706     * that model's properties.
    +707     *
    +708     * @param model Model schema to generate a table statement for.
    +709     * @return Generated DDL statement object.
    +710     */
    +711    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(
    +712            @Nonnull Descriptor model,
    +713            @Nonnull SpannerDriverSettings settings) {
    +714        return new SpannerGeneratedDDL.Builder(
    +715            model,
    +716            resolveTableName(model),
    +717            resolveKeyColumn(idField(model).orElseThrow(), settings),
    +718            resolveDefaultColumns(model, settings),
    +719            settings
    +720        );
    +721    }
    +722
    +723    /**
    +724     * Resolve the default calculated set of Spanner columns for a given model structure.
    +725     *
    +726     * @param model Model to traverse and generate columns for.
    +727     * @return Set of generated and type-resolved columns.
    +728     */
    +729    public static @Nonnull List<ColumnSpec> resolveDefaultColumns(@Nonnull Descriptor model,
    +730                                                                  @Nonnull SpannerDriverSettings settings) {
    +731        var keyField = keyField(model).orElseThrow();
    +732        var fieldSet = new LinkedList<ColumnSpec>();
    +733
    +734        // first up: generate the column which implements the model's primary key
    +735        fieldSet.add(ColumnSpec.columnSpecForKey(
    +736            model,
    +737            keyField,
    +738            settings
    +739        ));
    +740
    +741        // next: generate all remaining data columns
    +742        forEachField(
    +743            model,
    +744            Optional.of(onlySpannerEligibleFields(settings))
    +745        ).filter((fieldPointer) ->
    +746            // filter out key fields: we'll handle those separately
    +747            !keyField.getField().getFullName().equals(fieldPointer.getField().getFullName())
    +748        ).map((fieldPointer) ->
    +749            ColumnSpec.columnSpecForField(fieldPointer, settings)
    +750        ).forEach(fieldSet::add);
    +751
    +752        return Collections.unmodifiableList(fieldSet);
    +753    }
    +754
    +755    // -- Accessors -- //
    +756
    +757    /** @return Model for which this object generates a table create statement. */
    +758    public @Nonnull Descriptor getModel() {
    +759        return model;
    +760    }
    +761
    +762    /** @return Resolved name of the table to be created. */
    +763    public @Nonnull String getTableName() {
    +764        return tableName;
    +765    }
    +766
    +767    /** @return Resolved set of Spanner columns. */
    +768    public @Nonnull List<ColumnSpec> getColumns() {
    +769        return columns;
    +770    }
    +771
    +772    /** @return Rendered generated DDL statement. */
    +773    public @Nonnull StringBuilder getGeneratedStatement() {
    +774        return generatedStatement;
    +775    }
    +776
    +777    @Override
    +778    public String toString() {
    +779        return "SpannerDDL{" +
    +780            "model=" + model.getFullName() +
    +781            ", tableName='" + tableName + '\'' +
    +782            ", statement=\"" + generatedStatement.toString() +
    +783        "\"}";
    +784    }
    +785}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.PropagatedAction.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.PropagatedAction.html new file mode 100644 index 000000000..df55f2dd9 --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.PropagatedAction.html @@ -0,0 +1,859 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import com.google.cloud.spanner.Type;
    +016import com.google.protobuf.Message;
    +017import com.google.protobuf.Timestamp;
    +018import tools.elide.core.SpannerFieldOptions;
    +019
    +020import javax.annotation.Nonnull;
    +021import javax.annotation.concurrent.Immutable;
    +022import javax.annotation.concurrent.ThreadSafe;
    +023import java.util.*;
    +024import java.util.stream.Collectors;
    +025
    +026import static com.google.protobuf.Descriptors.Descriptor;
    +027import static com.google.protobuf.Descriptors.FieldDescriptor;
    +028import static gust.backend.driver.spanner.SpannerUtil.*;
    +029import static gust.backend.model.ModelMetadata.*;
    +030import static java.lang.String.format;
    +031import static java.lang.String.join;
    +032
    +033
    +034/** Container for generated schema-driven Spanner DDL. */
    +035@Immutable
    +036@ThreadSafe
    +037public final class SpannerGeneratedDDL {
    +038    /** Represents a DDL statement structure in code that can be rendered down to a string. */
    +039    public interface RenderableStatement {
    +040        /**
    +041         * Render this statement into a String buffer.
    +042         *
    +043         * @return Rendered statement.
    +044         */
    +045        @Nonnull StringBuilder render();
    +046    }
    +047
    +048    /** Sort direction settings which can apply to columns. */
    +049    public enum SortDirection {
    +050        /** Sort values in the column in ascending order. This is the default value. */
    +051        ASC,
    +052
    +053        /** Sort values in the column in descending order. */
    +054        DESC
    +055    }
    +056
    +057    /** Specifies options for reference action propagation (i.e. on-delete or on-update). */
    +058    public enum PropagatedAction {
    +059        /** Take no action. This is the default value. */
    +060        NO_ACTION,
    +061
    +062        /** Cascade changes on delete or update. */
    +063        CASCADE
    +064    }
    +065
    +066    /** Specifies a generic table constraint to include in a DDL statement. */
    +067    public static final class TableConstraint implements RenderableStatement {
    +068        final @Nonnull String name;
    +069        final @Nonnull String expression;
    +070
    +071        /**
    +072         * Private constructor for a table constraint specification.
    +073         *
    +074         * @param name Name of the table constraint.
    +075         * @param expression Expression to use as a constraint.
    +076         */
    +077        private TableConstraint(@Nonnull String name, @Nonnull String expression) {
    +078            this.name = name;
    +079            this.expression = expression;
    +080        }
    +081
    +082        /**
    +083         * Spawn a table constraint at the provided name, enforcing the provided expression.
    +084         *
    +085         * @param name Name of the constraint to enclose in the DDL statement.
    +086         * @param expression Expression to enforce as a table constraint.
    +087         * @return Table constraint specification.
    +088         */
    +089        public static @Nonnull TableConstraint named(@Nonnull String name,
    +090                                                     @Nonnull String expression) {
    +091            return new TableConstraint(name, expression);
    +092        }
    +093
    +094        /** @inheritDoc */
    +095        @Override
    +096        public @Nonnull StringBuilder render() {
    +097            return (new StringBuilder()).append(format(
    +098                "CONSTRAINT %s CHECK ( %s )",
    +099                this.name,
    +100                this.expression
    +101            ));
    +102        }
    +103    }
    +104
    +105    /** Specify a parent table against which this table is interleaved. */
    +106    public static final class InterleaveTarget implements RenderableStatement {
    +107        final @Nonnull String parent;
    +108        final @Nonnull Optional<PropagatedAction> action;
    +109
    +110        /**
    +111         * Private constructor for an interleave target for a Spanner table.
    +112         *
    +113         * @param parent Parent table where we should interleave this table.
    +114         * @param action Action to take, if any, when deletes or changes happen in the parent table.
    +115         */
    +116        private InterleaveTarget(@Nonnull String parent,
    +117                                 @Nonnull Optional<PropagatedAction> action) {
    +118            this.parent = parent;
    +119            this.action = action;
    +120        }
    +121
    +122        /**
    +123         * Generate an interleave target specification for the provided parent table name.
    +124         *
    +125         * @see #forParent(String, Optional) To pass a propagation action.
    +126         * @param parent Parent table where we should interleave a given table.
    +127         * @return Interleave target specification.
    +128         */
    +129        public static @Nonnull InterleaveTarget forParent(@Nonnull String parent) {
    +130            return forParent(parent, Optional.empty());
    +131        }
    +132
    +133        /**
    +134         * Generate an interleave target specification for the provided parent table name, optionally applying the
    +135         * provided propagation action.
    +136         *
    +137         * @param parent Parent table where we should interleave a given table.
    +138         * @param action Action to take, if any, when parent rows change that should affect the child table.
    +139         * @return Interleave target specification.
    +140         */
    +141        public static @Nonnull InterleaveTarget forParent(@Nonnull String parent,
    +142                                                          @Nonnull Optional<PropagatedAction> action) {
    +143            return new InterleaveTarget(parent, action);
    +144        }
    +145
    +146        /** @inheritDoc */
    +147        @Override
    +148        public @Nonnull StringBuilder render() {
    +149            var buf = new StringBuilder(format(
    +150                "INTERLEAVE IN PARENT %s",
    +151                this.parent
    +152            ));
    +153
    +154            if (this.action.isPresent()) {
    +155                buf.append(" ");
    +156                buf.append(format(
    +157                    "ON DELETE %s",
    +158                    this.action.get().name()
    +159                ));
    +160            }
    +161
    +162            return buf;
    +163        }
    +164    }
    +165
    +166    /** Specifies an individual field as part of a DDL statement. */
    +167    private static final class ColumnSpec implements RenderableStatement {
    +168        final @Nonnull String name;
    +169        final @Nonnull Type type;
    +170        final @Nonnull Integer length;
    +171        final @Nonnull FieldDescriptor field;
    +172        final @Nonnull Boolean nonnull;
    +173        final @Nonnull Optional<String> expression;
    +174        final @Nonnull Boolean expressionStored;
    +175        final @Nonnull Boolean allowCommitTimestamp;
    +176
    +177        /**
    +178         * Private constructor.
    +179         *
    +180         * @param name Column name in Spanner.
    +181         * @param type Type specification in Spanner.
    +182         * @param length Length for string fields, or `0`.
    +183         * @param nonnull Whether to mark the field as non-null.
    +184         * @param allowCommitTimestamp Whether to fill this column with the commit timestamp.
    +185         * @param field Model field that spawned this column specification.
    +186         */
    +187        ColumnSpec(@Nonnull String name,
    +188                   @Nonnull Type type,
    +189                   @Nonnull Integer length,
    +190                   @Nonnull Optional<Boolean> nonnull,
    +191                   @Nonnull Optional<String> expression,
    +192                   @Nonnull Optional<Boolean> expressionStored,
    +193                   @Nonnull Optional<Boolean> allowCommitTimestamp,
    +194                   @Nonnull FieldDescriptor field) {
    +195            this.name = name;
    +196            this.type = type;
    +197            this.length = length;
    +198            this.nonnull = nonnull.orElse(false);
    +199            this.expression = expression;
    +200            this.expressionStored = expressionStored.orElse(false);
    +201            this.allowCommitTimestamp = allowCommitTimestamp.orElse(false);
    +202            this.field = field;
    +203        }
    +204
    +205        /**
    +206         * Create a column spec for the provided field information, considering any active driver settings.
    +207         *
    +208         * @param fieldPointer Pointer to the field we should consider.
    +209         * @param settings Active set of Spanner driver settings.
    +210         * @return Spawned column corresponding to the provided field.
    +211         */
    +212        static @Nonnull ColumnSpec columnSpecForField(@Nonnull FieldPointer fieldPointer,
    +213                                                      @Nonnull SpannerDriverSettings settings) {
    +214            var columnOpts = columnOpts(fieldPointer);
    +215            var spannerOpts = spannerOpts(fieldPointer);
    +216            var fieldOpts = fieldOpts(fieldPointer);
    +217            var fieldName = resolveColumnName(fieldPointer, spannerOpts, columnOpts, settings);
    +218            var fieldType = resolveColumnType(fieldPointer, spannerOpts, columnOpts, settings);
    +219            Type innerType = fieldPointer.getField().isRepeated() ?
    +220                    fieldType.getArrayElementType() :
    +221                    fieldType;
    +222            var fieldSize = innerType.getCode() == Type.Code.STRING || innerType.getCode() == Type.Code.BYTES ?
    +223                    resolveColumnSize(fieldPointer.getField(), spannerOpts, columnOpts, settings) :
    +224                    -1;
    +225
    +226            // resolve spanner opts or defaults
    +227            var resolvedSpannerOpts = spannerOpts.orElse(SpannerFieldOptions.getDefaultInstance());
    +228            var expression = resolvedSpannerOpts.getExpression().length() > 0 ?
    +229                    Optional.of(resolvedSpannerOpts.getExpression()) : Optional.<String>empty();
    +230
    +231            var commitUpdate = false;
    +232            var protoType = fieldPointer.getField().getType();
    +233            if (fieldOpts.isPresent() && fieldOpts.get().getStampUpdate()) {
    +234                switch (protoType) {
    +235                    case STRING:
    +236                    case UINT64:
    +237                    case FIXED64:
    +238                        commitUpdate = true;
    +239                        break;
    +240
    +241                    case MESSAGE:
    +242                        if (fieldPointer.getField().getMessageType().getFullName().equals(
    +243                            Timestamp.getDescriptor().getFullName())) {
    +244                            commitUpdate = true;  // we can decode from a `Timestamp` record
    +245                        }
    +246                        break;
    +247
    +248                    default:
    +249                        // any other field type represents an illegal state
    +250                        throw new IllegalStateException(format(
    +251                            "Cannot place `commit_timestamp` in field of type '%s'", protoType.name()));
    +252                }
    +253            }
    +254
    +255            return new ColumnSpec(
    +256                fieldName,
    +257                fieldType,
    +258                fieldSize,
    +259                Optional.of(resolvedSpannerOpts.getNonnull()),
    +260                expression,
    +261                Optional.of(resolvedSpannerOpts.getStored()),
    +262                Optional.of(commitUpdate),
    +263                fieldPointer.getField()
    +264            );
    +265        }
    +266
    +267        /**
    +268         * Create a column spec for the provided model key field, considering any active driver settings.
    +269         *
    +270         * @param model Model schema for the object or key record.
    +271         * @param keyField Primary key field pre-resolved for a given Spanner table.
    +272         * @param settings Active Spanner driver settings.
    +273         * @return Spawned primary key column corresponding to the provided model key.
    +274         */
    +275        static @Nonnull ColumnSpec columnSpecForKey(@Nonnull Descriptor model,
    +276                                                    @Nonnull FieldPointer keyField,
    +277                                                    @Nonnull SpannerDriverSettings settings) {
    +278            var idField = idField(model).orElseThrow();
    +279            var keyName = resolveKeyColumn(idField, settings);
    +280            var keyType = resolveKeyType(idField);
    +281            var spannerOpts = spannerOpts(idField);
    +282            var columnOpts = columnOpts(idField);
    +283            int columnSize = -1;
    +284            if (keyType.getCode() == Type.Code.STRING ||
    +285                keyType.getCode() == Type.Code.BYTES) {
    +286                columnSize = resolveColumnSize(keyField.getField(), spannerOpts, columnOpts, settings);
    +287            }
    +288
    +289            return new ColumnSpec(
    +290                keyName,
    +291                keyType,
    +292                columnSize,
    +293                Optional.of(true),  // primary keys are always set to `NOT NULL`.
    +294                Optional.empty(),  // primary keys do not support expressions
    +295                Optional.empty(),
    +296                Optional.empty(),  // primary keys cannot be set to the commit timestamp
    +297                keyField.getField()
    +298            );
    +299        }
    +300
    +301        /**
    +302         * Render this column spec into a definition statement, suitable for use when creating a table.
    +303         *
    +304         * @return Rendered column spec statement.
    +305         */
    +306        @Override
    +307        public @Nonnull StringBuilder render() {
    +308            // prepare field statement
    +309            var buf = new StringBuilder();
    +310
    +311            // calculate field type designation first
    +312            String fieldType;
    +313            Type.Code innerType = this.field.isRepeated() ?
    +314                    this.type.getArrayElementType().getCode() :
    +315                    this.type.getCode();
    +316
    +317            String innerTypeSpec;
    +318            if (innerType == Type.Code.STRING || innerType == Type.Code.BYTES) {
    +319                innerTypeSpec = format(
    +320                    "%s(%s)",
    +321                    innerType.name(),
    +322                    this.length
    +323                );
    +324            } else {
    +325                innerTypeSpec = innerType.name();
    +326            }
    +327            if (this.type.getCode() == Type.Code.ARRAY) {
    +328                // it's a repeated field
    +329                fieldType = format(
    +330                    "ARRAY<%s>",
    +331                    innerTypeSpec
    +332                );
    +333            } else {
    +334                // it's a singular field. make sure to cover the special case for strings.
    +335                fieldType = innerTypeSpec;
    +336            }
    +337
    +338            buf.append(format(
    +339                "%s %s",
    +340                this.name,
    +341                fieldType
    +342            ));
    +343
    +344            // prepare field options
    +345            var optionsBuffer = new ArrayList<String>();
    +346
    +347            // consider NONNULL
    +348            if (this.nonnull) {
    +349                optionsBuffer.add("NOT NULL");
    +350            }
    +351
    +352            // consider expressions
    +353            if (this.expression.isPresent()) {
    +354                optionsBuffer.add(format("AS ( %s )", this.expression.get()));
    +355                if (this.expressionStored)
    +356                    optionsBuffer.add("STORED");
    +357            }
    +358
    +359            // consider options
    +360            if (this.allowCommitTimestamp) {
    +361                optionsBuffer.add("OPTIONS allow_commit_timestamp = true");
    +362            }
    +363            if (!optionsBuffer.isEmpty()) {
    +364                buf.append(" ");
    +365                buf.append(join(" ", optionsBuffer));
    +366            }
    +367            return buf;
    +368        }
    +369    }
    +370
    +371    /**
    +372     * Build properties for a generated Spanner table DDL statement, based on a given model instance as a base for
    +373     * configuring the table name (via annotations / calculated defaults) and set of typed Spanner value columns.
    +374     *
    +375     * <p>To build the actual DDL statement, fill out the builder, build it, and then ask the resulting object for the
    +376     * DDL as a string.</p>
    +377     */
    +378    @SuppressWarnings("unused")
    +379    public static final class Builder {
    +380        /** Base model on which this builder will operate. Immutable. */
    +381        final @Nonnull Descriptor model;
    +382
    +383        /** Active set of driver settings. Immutable. */
    +384        final @Nonnull SpannerDriverSettings settings;
    +385
    +386        /** Immutable: Name of the table in Spanner. */
    +387        final @Nonnull String tableName;
    +388
    +389        /** Immutable: Name of the primary key column. */
    +390        final @Nonnull String primaryKey;
    +391
    +392        /** Immutable: Generated columns in Spanner. */
    +393        final @Nonnull List<ColumnSpec> columns;
    +394
    +395        /** Mutable: Key column sort direction. */
    +396        @Nonnull SortDirection keySortDirection;
    +397
    +398        /** Mutable: List of table constraints. */
    +399        @Nonnull Optional<List<TableConstraint>> tableConstraints;
    +400
    +401        /** Mutable: Optimizer version to apply. */
    +402        @Nonnull Optional<Integer> optimizerVersion;
    +403
    +404        /** Mutable: Version retention period. */
    +405        @Nonnull Optional<String> versionRetentionPeriod;
    +406
    +407        /** Mutable: Table interleave target. */
    +408        @Nonnull Optional<InterleaveTarget> interleaveTarget;
    +409
    +410        /**
    +411         * Package-private constructor for a builder.
    +412         *
    +413         * @see SpannerGeneratedDDL#generateTableDDL(Descriptor, SpannerDriverSettings) to spawn one of
    +414         *      these from regular library or application code.
    +415         * @param model Descriptor for the model we are building against.
    +416         * @param primaryKey Primary key field name to use for this table by default.
    +417         * @param tableName Resolved table name to use for this table.
    +418         * @param defaultColumns Default set of columns to use for this table.
    +419         * @param settings Active driver settings to apply/consider.
    +420         */
    +421        Builder(@Nonnull Descriptor model,
    +422                @Nonnull String tableName,
    +423                @Nonnull String primaryKey,
    +424                @Nonnull List<ColumnSpec> defaultColumns,
    +425                @Nonnull SpannerDriverSettings settings) {
    +426            this.model = model;
    +427            this.tableName = tableName;
    +428            this.settings = settings;
    +429            this.columns = defaultColumns;
    +430            this.primaryKey = primaryKey;
    +431            this.keySortDirection = SortDirection.ASC;
    +432            this.tableConstraints = Optional.empty();
    +433            this.optimizerVersion = Optional.empty();
    +434            this.versionRetentionPeriod = Optional.empty();
    +435            this.interleaveTarget = Optional.empty();
    +436        }
    +437
    +438        /**
    +439         * Render column definition statements for a final DDL table create statement.
    +440         *
    +441         * @return Column definition statements, stacked in a buffer.
    +442         */
    +443        @Nonnull String renderColumnStatements() {
    +444            return this.columns.stream()
    +445                    .map(ColumnSpec::render)
    +446                    .collect(Collectors.joining(", "));
    +447        }
    +448
    +449        /**
    +450         * Render table-level constraint statements for a final DDL table create statement.
    +451         *
    +452         * @return Any applicable rendered table constraints.
    +453         */
    +454        @Nonnull String renderConstraintStatements() {
    +455            return this.tableConstraints.map(constraints -> constraints
    +456                    .stream()
    +457                    .map(TableConstraint::render)
    +458                    .collect(Collectors.joining(", ")))
    +459                    .orElse("");
    +460        }
    +461
    +462        /**
    +463         * Render inner statements in the CREATE TABLE DDL statement, including columns and constraints, as applicable.
    +464         * If no constraints are present, we simply return the column definitions alone.
    +465         *
    +466         * @return Rendered definitions of columns and table constraints.
    +467         */
    +468        @Nonnull String renderColumnStatementsAndConstraints() {
    +469            var columnList = renderColumnStatements();
    +470            if (this.tableConstraints.isPresent()) {
    +471                var constraints = renderConstraintStatements();
    +472                return format("%s, %s", columnList, constraints);
    +473            }
    +474            return columnList;
    +475        }
    +476
    +477        /**
    +478         * Render the primary key specification for a final DDL table create statement.
    +479         *
    +480         * @return Rendered primary key specification.
    +481         */
    +482        @Nonnull String renderPrimaryKey() {
    +483            return format(
    +484                "%s %s",
    +485                this.primaryKey,
    +486                this.keySortDirection.name()
    +487            );
    +488        }
    +489
    +490        /**
    +491         * Render the prepared DDL statement details into a statement string which can be passed to Spanner.
    +492         *
    +493         * @return Rendered DDL statement, according to local object settings.
    +494         */
    +495        @Nonnull StringBuilder renderCreateDDLStatement() {
    +496            var builder = new StringBuilder();
    +497            var buf = new ArrayList<StringBuilder>();
    +498            buf.add(new StringBuilder(format(
    +499                "CREATE TABLE %s (%s) PRIMARY KEY (%s)",
    +500                this.tableName,
    +501                this.renderColumnStatementsAndConstraints(),
    +502                this.renderPrimaryKey()
    +503            )));
    +504
    +505            // add interleave target statement, if specified
    +506            this.interleaveTarget.ifPresent(target -> buf.add(target.render()));
    +507
    +508            builder.append(join(", ", buf));
    +509            return builder;
    +510        }
    +511
    +512        /**
    +513         * Collapse the builder into an immutable DDL statement container
    +514         *
    +515         * @return Immutable DDL statement container.
    +516         */
    +517        public @Nonnull SpannerGeneratedDDL build() {
    +518            var fields = forEachField(
    +519                model,
    +520                Optional.of(onlySpannerEligibleFields(settings))
    +521            ).map((fieldPointer) ->
    +522                    ColumnSpec.columnSpecForField(fieldPointer, settings)
    +523            ).collect(Collectors.toUnmodifiableList());
    +524
    +525            return new SpannerGeneratedDDL(
    +526                tableName,
    +527                fields,
    +528                model,
    +529                renderCreateDDLStatement()
    +530            );
    +531        }
    +532
    +533        // -- Builder API: Getters -- //
    +534
    +535        /** @return Model descriptor this builder wraps. */
    +536        public @Nonnull Descriptor getModel() {
    +537            return model;
    +538        }
    +539
    +540        /** @return Active Spanner driver settings. */
    +541        public @Nonnull SpannerDriverSettings getSettings() {
    +542            return settings;
    +543        }
    +544
    +545        /** @return Generated or resolved Spanner table name. */
    +546        public @Nonnull String getTableName() {
    +547            return tableName;
    +548        }
    +549
    +550        /** @return Primary key column for this model/table. */
    +551        public @Nonnull String getPrimaryKey() {
    +552            return primaryKey;
    +553        }
    +554
    +555        /** @return Set of generated columns for this model in Spanner. */
    +556        public @Nonnull List<ColumnSpec> getColumns() {
    +557            return columns;
    +558        }
    +559
    +560        /** @return Primary key column sort direction. */
    +561        public @Nonnull SortDirection getKeySortDirection() {
    +562            return keySortDirection;
    +563        }
    +564
    +565        /** @return Set of constraints to apply to this table. */
    +566        public @Nonnull Optional<List<TableConstraint>> getTableConstraints() {
    +567            return tableConstraints;
    +568        }
    +569
    +570        /** @return Optimizer version to set for this table. */
    +571        public @Nonnull Optional<Integer> getOptimizerVersion() {
    +572            return optimizerVersion;
    +573        }
    +574
    +575        /** @return Data versioning retention period to set for this table. */
    +576        public @Nonnull Optional<String> getVersionRetentionPeriod() {
    +577            return versionRetentionPeriod;
    +578        }
    +579
    +580        /** @return Parent interleaving target for this table. */
    +581        public @Nonnull Optional<InterleaveTarget> getInterleaveTarget() {
    +582            return interleaveTarget;
    +583        }
    +584
    +585        // -- Builder API: Setters -- //
    +586
    +587        /**
    +588         * Set the sort direction for the primary key column in this table.
    +589         *
    +590         * @param keySortDirection Key column sort direction.
    +591         * @return Self, for chained calls to the builder.
    +592         */
    +593        public @Nonnull Builder setKeySortDirection(@Nonnull SortDirection keySortDirection) {
    +594            this.keySortDirection = keySortDirection;
    +595            return this;
    +596        }
    +597
    +598        /**
    +599         * Set, or clear, the set of table constraints added to this table.
    +600         *
    +601         * @param tableConstraints Desired table constraints to set or clear, as applicable.
    +602         * @return Self, for chained calls to the builder.
    +603         */
    +604        public @Nonnull Builder setTableConstraints(@Nonnull Optional<List<TableConstraint>> tableConstraints) {
    +605            this.tableConstraints = tableConstraints;
    +606            return this;
    +607        }
    +608
    +609        /**
    +610         * Set, or clear, the optimizer version to apply when creating this table.
    +611         *
    +612         * @param optimizerVersion Desired optimizer version to apply, as applicable.
    +613         * @return Self, for chained calls to the builder.
    +614         */
    +615        public @Nonnull Builder setOptimizerVersion(@Nonnull Optional<Integer> optimizerVersion) {
    +616            this.optimizerVersion = optimizerVersion;
    +617            return this;
    +618        }
    +619
    +620        /**
    +621         * Set, or clear, the data versioning retention period for this table.
    +622         *
    +623         * @param versionRetentionPeriod Desired data versioning retention period, as applicable.
    +624         * @return Self, for chained calls to the builder.
    +625         */
    +626        public @Nonnull Builder setVersionRetentionPeriod(@Nonnull Optional<String> versionRetentionPeriod) {
    +627            this.versionRetentionPeriod = versionRetentionPeriod;
    +628            return this;
    +629        }
    +630
    +631        /**
    +632         * Set, or clear, the parent interleave target for this table.
    +633         *
    +634         * @param interleaveTarget Desired parent interleave target, as applicable.
    +635         * @return Self, for chained calls to the builder.
    +636         */
    +637        public @Nonnull Builder setInterleaveTarget(@Nonnull Optional<InterleaveTarget> interleaveTarget) {
    +638            this.interleaveTarget = interleaveTarget;
    +639            return this;
    +640        }
    +641    }
    +642
    +643    /** Model that relates to this generated statement. */
    +644    private final @Nonnull Descriptor model;
    +645
    +646    /** Resolved name of the table. */
    +647    private final @Nonnull String tableName;
    +648
    +649    /** Set of generated columns determined to be part of this table. */
    +650    private final @Nonnull List<ColumnSpec> columns;
    +651
    +652    /** Holds the generated query in a string buffer. */
    +653    private final @Nonnull StringBuilder generatedStatement;
    +654
    +655    /**
    +656     * Private constructor.
    +657     *
    +658     * @param tableName Name of the generated table.
    +659     * @param columns Generated set of columns.
    +660     * @param model Model this table corresponds to.
    +661     * @param generatedStatement Rendered DDL statement.
    +662     */
    +663    private SpannerGeneratedDDL(@Nonnull String tableName,
    +664                                @Nonnull List<ColumnSpec> columns,
    +665                                @Nonnull Descriptor model,
    +666                                @Nonnull StringBuilder generatedStatement) {
    +667        this.tableName = tableName;
    +668        this.columns = columns;
    +669        this.generatedStatement = generatedStatement;
    +670        this.model = model;
    +671    }
    +672
    +673    /**
    +674     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +675     * that model's properties. This method variant operates from a full model instance.
    +676     *
    +677     * <p>This method offers no ability to control driver settings. See below if you need alternatives.</p>
    +678     *
    +679     * @see #generateTableDDL(Message, Optional) For control over driver settings, optionally.
    +680     * @param instance Model instance to generate a table statement for.
    +681     * @return Generated DDL statement object.
    +682     */
    +683    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(@Nonnull Message instance) {
    +684        return generateTableDDL(instance, Optional.of(SpannerDriverSettings.DEFAULTS));
    +685    }
    +686
    +687    /**
    +688     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +689     * that model's properties. This method variant operates from a full model instance.
    +690     *
    +691     * @param instance Model instance to generate a table statement for.
    +692     * @param settings Settings to employ for the driver. These must align at runtime.
    +693     * @return Generated DDL statement object.
    +694     */
    +695    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(
    +696            @Nonnull Message instance,
    +697            @Nonnull Optional<SpannerDriverSettings> settings) {
    +698        return generateTableDDL(
    +699            instance.getDescriptorForType(),
    +700            settings.orElse(SpannerDriverSettings.DEFAULTS)
    +701        );
    +702    }
    +703
    +704    /**
    +705     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +706     * that model's properties.
    +707     *
    +708     * @param model Model schema to generate a table statement for.
    +709     * @return Generated DDL statement object.
    +710     */
    +711    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(
    +712            @Nonnull Descriptor model,
    +713            @Nonnull SpannerDriverSettings settings) {
    +714        return new SpannerGeneratedDDL.Builder(
    +715            model,
    +716            resolveTableName(model),
    +717            resolveKeyColumn(idField(model).orElseThrow(), settings),
    +718            resolveDefaultColumns(model, settings),
    +719            settings
    +720        );
    +721    }
    +722
    +723    /**
    +724     * Resolve the default calculated set of Spanner columns for a given model structure.
    +725     *
    +726     * @param model Model to traverse and generate columns for.
    +727     * @return Set of generated and type-resolved columns.
    +728     */
    +729    public static @Nonnull List<ColumnSpec> resolveDefaultColumns(@Nonnull Descriptor model,
    +730                                                                  @Nonnull SpannerDriverSettings settings) {
    +731        var keyField = keyField(model).orElseThrow();
    +732        var fieldSet = new LinkedList<ColumnSpec>();
    +733
    +734        // first up: generate the column which implements the model's primary key
    +735        fieldSet.add(ColumnSpec.columnSpecForKey(
    +736            model,
    +737            keyField,
    +738            settings
    +739        ));
    +740
    +741        // next: generate all remaining data columns
    +742        forEachField(
    +743            model,
    +744            Optional.of(onlySpannerEligibleFields(settings))
    +745        ).filter((fieldPointer) ->
    +746            // filter out key fields: we'll handle those separately
    +747            !keyField.getField().getFullName().equals(fieldPointer.getField().getFullName())
    +748        ).map((fieldPointer) ->
    +749            ColumnSpec.columnSpecForField(fieldPointer, settings)
    +750        ).forEach(fieldSet::add);
    +751
    +752        return Collections.unmodifiableList(fieldSet);
    +753    }
    +754
    +755    // -- Accessors -- //
    +756
    +757    /** @return Model for which this object generates a table create statement. */
    +758    public @Nonnull Descriptor getModel() {
    +759        return model;
    +760    }
    +761
    +762    /** @return Resolved name of the table to be created. */
    +763    public @Nonnull String getTableName() {
    +764        return tableName;
    +765    }
    +766
    +767    /** @return Resolved set of Spanner columns. */
    +768    public @Nonnull List<ColumnSpec> getColumns() {
    +769        return columns;
    +770    }
    +771
    +772    /** @return Rendered generated DDL statement. */
    +773    public @Nonnull StringBuilder getGeneratedStatement() {
    +774        return generatedStatement;
    +775    }
    +776
    +777    @Override
    +778    public String toString() {
    +779        return "SpannerDDL{" +
    +780            "model=" + model.getFullName() +
    +781            ", tableName='" + tableName + '\'' +
    +782            ", statement=\"" + generatedStatement.toString() +
    +783        "\"}";
    +784    }
    +785}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.RenderableStatement.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.RenderableStatement.html new file mode 100644 index 000000000..df55f2dd9 --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.RenderableStatement.html @@ -0,0 +1,859 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import com.google.cloud.spanner.Type;
    +016import com.google.protobuf.Message;
    +017import com.google.protobuf.Timestamp;
    +018import tools.elide.core.SpannerFieldOptions;
    +019
    +020import javax.annotation.Nonnull;
    +021import javax.annotation.concurrent.Immutable;
    +022import javax.annotation.concurrent.ThreadSafe;
    +023import java.util.*;
    +024import java.util.stream.Collectors;
    +025
    +026import static com.google.protobuf.Descriptors.Descriptor;
    +027import static com.google.protobuf.Descriptors.FieldDescriptor;
    +028import static gust.backend.driver.spanner.SpannerUtil.*;
    +029import static gust.backend.model.ModelMetadata.*;
    +030import static java.lang.String.format;
    +031import static java.lang.String.join;
    +032
    +033
    +034/** Container for generated schema-driven Spanner DDL. */
    +035@Immutable
    +036@ThreadSafe
    +037public final class SpannerGeneratedDDL {
    +038    /** Represents a DDL statement structure in code that can be rendered down to a string. */
    +039    public interface RenderableStatement {
    +040        /**
    +041         * Render this statement into a String buffer.
    +042         *
    +043         * @return Rendered statement.
    +044         */
    +045        @Nonnull StringBuilder render();
    +046    }
    +047
    +048    /** Sort direction settings which can apply to columns. */
    +049    public enum SortDirection {
    +050        /** Sort values in the column in ascending order. This is the default value. */
    +051        ASC,
    +052
    +053        /** Sort values in the column in descending order. */
    +054        DESC
    +055    }
    +056
    +057    /** Specifies options for reference action propagation (i.e. on-delete or on-update). */
    +058    public enum PropagatedAction {
    +059        /** Take no action. This is the default value. */
    +060        NO_ACTION,
    +061
    +062        /** Cascade changes on delete or update. */
    +063        CASCADE
    +064    }
    +065
    +066    /** Specifies a generic table constraint to include in a DDL statement. */
    +067    public static final class TableConstraint implements RenderableStatement {
    +068        final @Nonnull String name;
    +069        final @Nonnull String expression;
    +070
    +071        /**
    +072         * Private constructor for a table constraint specification.
    +073         *
    +074         * @param name Name of the table constraint.
    +075         * @param expression Expression to use as a constraint.
    +076         */
    +077        private TableConstraint(@Nonnull String name, @Nonnull String expression) {
    +078            this.name = name;
    +079            this.expression = expression;
    +080        }
    +081
    +082        /**
    +083         * Spawn a table constraint at the provided name, enforcing the provided expression.
    +084         *
    +085         * @param name Name of the constraint to enclose in the DDL statement.
    +086         * @param expression Expression to enforce as a table constraint.
    +087         * @return Table constraint specification.
    +088         */
    +089        public static @Nonnull TableConstraint named(@Nonnull String name,
    +090                                                     @Nonnull String expression) {
    +091            return new TableConstraint(name, expression);
    +092        }
    +093
    +094        /** @inheritDoc */
    +095        @Override
    +096        public @Nonnull StringBuilder render() {
    +097            return (new StringBuilder()).append(format(
    +098                "CONSTRAINT %s CHECK ( %s )",
    +099                this.name,
    +100                this.expression
    +101            ));
    +102        }
    +103    }
    +104
    +105    /** Specify a parent table against which this table is interleaved. */
    +106    public static final class InterleaveTarget implements RenderableStatement {
    +107        final @Nonnull String parent;
    +108        final @Nonnull Optional<PropagatedAction> action;
    +109
    +110        /**
    +111         * Private constructor for an interleave target for a Spanner table.
    +112         *
    +113         * @param parent Parent table where we should interleave this table.
    +114         * @param action Action to take, if any, when deletes or changes happen in the parent table.
    +115         */
    +116        private InterleaveTarget(@Nonnull String parent,
    +117                                 @Nonnull Optional<PropagatedAction> action) {
    +118            this.parent = parent;
    +119            this.action = action;
    +120        }
    +121
    +122        /**
    +123         * Generate an interleave target specification for the provided parent table name.
    +124         *
    +125         * @see #forParent(String, Optional) To pass a propagation action.
    +126         * @param parent Parent table where we should interleave a given table.
    +127         * @return Interleave target specification.
    +128         */
    +129        public static @Nonnull InterleaveTarget forParent(@Nonnull String parent) {
    +130            return forParent(parent, Optional.empty());
    +131        }
    +132
    +133        /**
    +134         * Generate an interleave target specification for the provided parent table name, optionally applying the
    +135         * provided propagation action.
    +136         *
    +137         * @param parent Parent table where we should interleave a given table.
    +138         * @param action Action to take, if any, when parent rows change that should affect the child table.
    +139         * @return Interleave target specification.
    +140         */
    +141        public static @Nonnull InterleaveTarget forParent(@Nonnull String parent,
    +142                                                          @Nonnull Optional<PropagatedAction> action) {
    +143            return new InterleaveTarget(parent, action);
    +144        }
    +145
    +146        /** @inheritDoc */
    +147        @Override
    +148        public @Nonnull StringBuilder render() {
    +149            var buf = new StringBuilder(format(
    +150                "INTERLEAVE IN PARENT %s",
    +151                this.parent
    +152            ));
    +153
    +154            if (this.action.isPresent()) {
    +155                buf.append(" ");
    +156                buf.append(format(
    +157                    "ON DELETE %s",
    +158                    this.action.get().name()
    +159                ));
    +160            }
    +161
    +162            return buf;
    +163        }
    +164    }
    +165
    +166    /** Specifies an individual field as part of a DDL statement. */
    +167    private static final class ColumnSpec implements RenderableStatement {
    +168        final @Nonnull String name;
    +169        final @Nonnull Type type;
    +170        final @Nonnull Integer length;
    +171        final @Nonnull FieldDescriptor field;
    +172        final @Nonnull Boolean nonnull;
    +173        final @Nonnull Optional<String> expression;
    +174        final @Nonnull Boolean expressionStored;
    +175        final @Nonnull Boolean allowCommitTimestamp;
    +176
    +177        /**
    +178         * Private constructor.
    +179         *
    +180         * @param name Column name in Spanner.
    +181         * @param type Type specification in Spanner.
    +182         * @param length Length for string fields, or `0`.
    +183         * @param nonnull Whether to mark the field as non-null.
    +184         * @param allowCommitTimestamp Whether to fill this column with the commit timestamp.
    +185         * @param field Model field that spawned this column specification.
    +186         */
    +187        ColumnSpec(@Nonnull String name,
    +188                   @Nonnull Type type,
    +189                   @Nonnull Integer length,
    +190                   @Nonnull Optional<Boolean> nonnull,
    +191                   @Nonnull Optional<String> expression,
    +192                   @Nonnull Optional<Boolean> expressionStored,
    +193                   @Nonnull Optional<Boolean> allowCommitTimestamp,
    +194                   @Nonnull FieldDescriptor field) {
    +195            this.name = name;
    +196            this.type = type;
    +197            this.length = length;
    +198            this.nonnull = nonnull.orElse(false);
    +199            this.expression = expression;
    +200            this.expressionStored = expressionStored.orElse(false);
    +201            this.allowCommitTimestamp = allowCommitTimestamp.orElse(false);
    +202            this.field = field;
    +203        }
    +204
    +205        /**
    +206         * Create a column spec for the provided field information, considering any active driver settings.
    +207         *
    +208         * @param fieldPointer Pointer to the field we should consider.
    +209         * @param settings Active set of Spanner driver settings.
    +210         * @return Spawned column corresponding to the provided field.
    +211         */
    +212        static @Nonnull ColumnSpec columnSpecForField(@Nonnull FieldPointer fieldPointer,
    +213                                                      @Nonnull SpannerDriverSettings settings) {
    +214            var columnOpts = columnOpts(fieldPointer);
    +215            var spannerOpts = spannerOpts(fieldPointer);
    +216            var fieldOpts = fieldOpts(fieldPointer);
    +217            var fieldName = resolveColumnName(fieldPointer, spannerOpts, columnOpts, settings);
    +218            var fieldType = resolveColumnType(fieldPointer, spannerOpts, columnOpts, settings);
    +219            Type innerType = fieldPointer.getField().isRepeated() ?
    +220                    fieldType.getArrayElementType() :
    +221                    fieldType;
    +222            var fieldSize = innerType.getCode() == Type.Code.STRING || innerType.getCode() == Type.Code.BYTES ?
    +223                    resolveColumnSize(fieldPointer.getField(), spannerOpts, columnOpts, settings) :
    +224                    -1;
    +225
    +226            // resolve spanner opts or defaults
    +227            var resolvedSpannerOpts = spannerOpts.orElse(SpannerFieldOptions.getDefaultInstance());
    +228            var expression = resolvedSpannerOpts.getExpression().length() > 0 ?
    +229                    Optional.of(resolvedSpannerOpts.getExpression()) : Optional.<String>empty();
    +230
    +231            var commitUpdate = false;
    +232            var protoType = fieldPointer.getField().getType();
    +233            if (fieldOpts.isPresent() && fieldOpts.get().getStampUpdate()) {
    +234                switch (protoType) {
    +235                    case STRING:
    +236                    case UINT64:
    +237                    case FIXED64:
    +238                        commitUpdate = true;
    +239                        break;
    +240
    +241                    case MESSAGE:
    +242                        if (fieldPointer.getField().getMessageType().getFullName().equals(
    +243                            Timestamp.getDescriptor().getFullName())) {
    +244                            commitUpdate = true;  // we can decode from a `Timestamp` record
    +245                        }
    +246                        break;
    +247
    +248                    default:
    +249                        // any other field type represents an illegal state
    +250                        throw new IllegalStateException(format(
    +251                            "Cannot place `commit_timestamp` in field of type '%s'", protoType.name()));
    +252                }
    +253            }
    +254
    +255            return new ColumnSpec(
    +256                fieldName,
    +257                fieldType,
    +258                fieldSize,
    +259                Optional.of(resolvedSpannerOpts.getNonnull()),
    +260                expression,
    +261                Optional.of(resolvedSpannerOpts.getStored()),
    +262                Optional.of(commitUpdate),
    +263                fieldPointer.getField()
    +264            );
    +265        }
    +266
    +267        /**
    +268         * Create a column spec for the provided model key field, considering any active driver settings.
    +269         *
    +270         * @param model Model schema for the object or key record.
    +271         * @param keyField Primary key field pre-resolved for a given Spanner table.
    +272         * @param settings Active Spanner driver settings.
    +273         * @return Spawned primary key column corresponding to the provided model key.
    +274         */
    +275        static @Nonnull ColumnSpec columnSpecForKey(@Nonnull Descriptor model,
    +276                                                    @Nonnull FieldPointer keyField,
    +277                                                    @Nonnull SpannerDriverSettings settings) {
    +278            var idField = idField(model).orElseThrow();
    +279            var keyName = resolveKeyColumn(idField, settings);
    +280            var keyType = resolveKeyType(idField);
    +281            var spannerOpts = spannerOpts(idField);
    +282            var columnOpts = columnOpts(idField);
    +283            int columnSize = -1;
    +284            if (keyType.getCode() == Type.Code.STRING ||
    +285                keyType.getCode() == Type.Code.BYTES) {
    +286                columnSize = resolveColumnSize(keyField.getField(), spannerOpts, columnOpts, settings);
    +287            }
    +288
    +289            return new ColumnSpec(
    +290                keyName,
    +291                keyType,
    +292                columnSize,
    +293                Optional.of(true),  // primary keys are always set to `NOT NULL`.
    +294                Optional.empty(),  // primary keys do not support expressions
    +295                Optional.empty(),
    +296                Optional.empty(),  // primary keys cannot be set to the commit timestamp
    +297                keyField.getField()
    +298            );
    +299        }
    +300
    +301        /**
    +302         * Render this column spec into a definition statement, suitable for use when creating a table.
    +303         *
    +304         * @return Rendered column spec statement.
    +305         */
    +306        @Override
    +307        public @Nonnull StringBuilder render() {
    +308            // prepare field statement
    +309            var buf = new StringBuilder();
    +310
    +311            // calculate field type designation first
    +312            String fieldType;
    +313            Type.Code innerType = this.field.isRepeated() ?
    +314                    this.type.getArrayElementType().getCode() :
    +315                    this.type.getCode();
    +316
    +317            String innerTypeSpec;
    +318            if (innerType == Type.Code.STRING || innerType == Type.Code.BYTES) {
    +319                innerTypeSpec = format(
    +320                    "%s(%s)",
    +321                    innerType.name(),
    +322                    this.length
    +323                );
    +324            } else {
    +325                innerTypeSpec = innerType.name();
    +326            }
    +327            if (this.type.getCode() == Type.Code.ARRAY) {
    +328                // it's a repeated field
    +329                fieldType = format(
    +330                    "ARRAY<%s>",
    +331                    innerTypeSpec
    +332                );
    +333            } else {
    +334                // it's a singular field. make sure to cover the special case for strings.
    +335                fieldType = innerTypeSpec;
    +336            }
    +337
    +338            buf.append(format(
    +339                "%s %s",
    +340                this.name,
    +341                fieldType
    +342            ));
    +343
    +344            // prepare field options
    +345            var optionsBuffer = new ArrayList<String>();
    +346
    +347            // consider NONNULL
    +348            if (this.nonnull) {
    +349                optionsBuffer.add("NOT NULL");
    +350            }
    +351
    +352            // consider expressions
    +353            if (this.expression.isPresent()) {
    +354                optionsBuffer.add(format("AS ( %s )", this.expression.get()));
    +355                if (this.expressionStored)
    +356                    optionsBuffer.add("STORED");
    +357            }
    +358
    +359            // consider options
    +360            if (this.allowCommitTimestamp) {
    +361                optionsBuffer.add("OPTIONS allow_commit_timestamp = true");
    +362            }
    +363            if (!optionsBuffer.isEmpty()) {
    +364                buf.append(" ");
    +365                buf.append(join(" ", optionsBuffer));
    +366            }
    +367            return buf;
    +368        }
    +369    }
    +370
    +371    /**
    +372     * Build properties for a generated Spanner table DDL statement, based on a given model instance as a base for
    +373     * configuring the table name (via annotations / calculated defaults) and set of typed Spanner value columns.
    +374     *
    +375     * <p>To build the actual DDL statement, fill out the builder, build it, and then ask the resulting object for the
    +376     * DDL as a string.</p>
    +377     */
    +378    @SuppressWarnings("unused")
    +379    public static final class Builder {
    +380        /** Base model on which this builder will operate. Immutable. */
    +381        final @Nonnull Descriptor model;
    +382
    +383        /** Active set of driver settings. Immutable. */
    +384        final @Nonnull SpannerDriverSettings settings;
    +385
    +386        /** Immutable: Name of the table in Spanner. */
    +387        final @Nonnull String tableName;
    +388
    +389        /** Immutable: Name of the primary key column. */
    +390        final @Nonnull String primaryKey;
    +391
    +392        /** Immutable: Generated columns in Spanner. */
    +393        final @Nonnull List<ColumnSpec> columns;
    +394
    +395        /** Mutable: Key column sort direction. */
    +396        @Nonnull SortDirection keySortDirection;
    +397
    +398        /** Mutable: List of table constraints. */
    +399        @Nonnull Optional<List<TableConstraint>> tableConstraints;
    +400
    +401        /** Mutable: Optimizer version to apply. */
    +402        @Nonnull Optional<Integer> optimizerVersion;
    +403
    +404        /** Mutable: Version retention period. */
    +405        @Nonnull Optional<String> versionRetentionPeriod;
    +406
    +407        /** Mutable: Table interleave target. */
    +408        @Nonnull Optional<InterleaveTarget> interleaveTarget;
    +409
    +410        /**
    +411         * Package-private constructor for a builder.
    +412         *
    +413         * @see SpannerGeneratedDDL#generateTableDDL(Descriptor, SpannerDriverSettings) to spawn one of
    +414         *      these from regular library or application code.
    +415         * @param model Descriptor for the model we are building against.
    +416         * @param primaryKey Primary key field name to use for this table by default.
    +417         * @param tableName Resolved table name to use for this table.
    +418         * @param defaultColumns Default set of columns to use for this table.
    +419         * @param settings Active driver settings to apply/consider.
    +420         */
    +421        Builder(@Nonnull Descriptor model,
    +422                @Nonnull String tableName,
    +423                @Nonnull String primaryKey,
    +424                @Nonnull List<ColumnSpec> defaultColumns,
    +425                @Nonnull SpannerDriverSettings settings) {
    +426            this.model = model;
    +427            this.tableName = tableName;
    +428            this.settings = settings;
    +429            this.columns = defaultColumns;
    +430            this.primaryKey = primaryKey;
    +431            this.keySortDirection = SortDirection.ASC;
    +432            this.tableConstraints = Optional.empty();
    +433            this.optimizerVersion = Optional.empty();
    +434            this.versionRetentionPeriod = Optional.empty();
    +435            this.interleaveTarget = Optional.empty();
    +436        }
    +437
    +438        /**
    +439         * Render column definition statements for a final DDL table create statement.
    +440         *
    +441         * @return Column definition statements, stacked in a buffer.
    +442         */
    +443        @Nonnull String renderColumnStatements() {
    +444            return this.columns.stream()
    +445                    .map(ColumnSpec::render)
    +446                    .collect(Collectors.joining(", "));
    +447        }
    +448
    +449        /**
    +450         * Render table-level constraint statements for a final DDL table create statement.
    +451         *
    +452         * @return Any applicable rendered table constraints.
    +453         */
    +454        @Nonnull String renderConstraintStatements() {
    +455            return this.tableConstraints.map(constraints -> constraints
    +456                    .stream()
    +457                    .map(TableConstraint::render)
    +458                    .collect(Collectors.joining(", ")))
    +459                    .orElse("");
    +460        }
    +461
    +462        /**
    +463         * Render inner statements in the CREATE TABLE DDL statement, including columns and constraints, as applicable.
    +464         * If no constraints are present, we simply return the column definitions alone.
    +465         *
    +466         * @return Rendered definitions of columns and table constraints.
    +467         */
    +468        @Nonnull String renderColumnStatementsAndConstraints() {
    +469            var columnList = renderColumnStatements();
    +470            if (this.tableConstraints.isPresent()) {
    +471                var constraints = renderConstraintStatements();
    +472                return format("%s, %s", columnList, constraints);
    +473            }
    +474            return columnList;
    +475        }
    +476
    +477        /**
    +478         * Render the primary key specification for a final DDL table create statement.
    +479         *
    +480         * @return Rendered primary key specification.
    +481         */
    +482        @Nonnull String renderPrimaryKey() {
    +483            return format(
    +484                "%s %s",
    +485                this.primaryKey,
    +486                this.keySortDirection.name()
    +487            );
    +488        }
    +489
    +490        /**
    +491         * Render the prepared DDL statement details into a statement string which can be passed to Spanner.
    +492         *
    +493         * @return Rendered DDL statement, according to local object settings.
    +494         */
    +495        @Nonnull StringBuilder renderCreateDDLStatement() {
    +496            var builder = new StringBuilder();
    +497            var buf = new ArrayList<StringBuilder>();
    +498            buf.add(new StringBuilder(format(
    +499                "CREATE TABLE %s (%s) PRIMARY KEY (%s)",
    +500                this.tableName,
    +501                this.renderColumnStatementsAndConstraints(),
    +502                this.renderPrimaryKey()
    +503            )));
    +504
    +505            // add interleave target statement, if specified
    +506            this.interleaveTarget.ifPresent(target -> buf.add(target.render()));
    +507
    +508            builder.append(join(", ", buf));
    +509            return builder;
    +510        }
    +511
    +512        /**
    +513         * Collapse the builder into an immutable DDL statement container
    +514         *
    +515         * @return Immutable DDL statement container.
    +516         */
    +517        public @Nonnull SpannerGeneratedDDL build() {
    +518            var fields = forEachField(
    +519                model,
    +520                Optional.of(onlySpannerEligibleFields(settings))
    +521            ).map((fieldPointer) ->
    +522                    ColumnSpec.columnSpecForField(fieldPointer, settings)
    +523            ).collect(Collectors.toUnmodifiableList());
    +524
    +525            return new SpannerGeneratedDDL(
    +526                tableName,
    +527                fields,
    +528                model,
    +529                renderCreateDDLStatement()
    +530            );
    +531        }
    +532
    +533        // -- Builder API: Getters -- //
    +534
    +535        /** @return Model descriptor this builder wraps. */
    +536        public @Nonnull Descriptor getModel() {
    +537            return model;
    +538        }
    +539
    +540        /** @return Active Spanner driver settings. */
    +541        public @Nonnull SpannerDriverSettings getSettings() {
    +542            return settings;
    +543        }
    +544
    +545        /** @return Generated or resolved Spanner table name. */
    +546        public @Nonnull String getTableName() {
    +547            return tableName;
    +548        }
    +549
    +550        /** @return Primary key column for this model/table. */
    +551        public @Nonnull String getPrimaryKey() {
    +552            return primaryKey;
    +553        }
    +554
    +555        /** @return Set of generated columns for this model in Spanner. */
    +556        public @Nonnull List<ColumnSpec> getColumns() {
    +557            return columns;
    +558        }
    +559
    +560        /** @return Primary key column sort direction. */
    +561        public @Nonnull SortDirection getKeySortDirection() {
    +562            return keySortDirection;
    +563        }
    +564
    +565        /** @return Set of constraints to apply to this table. */
    +566        public @Nonnull Optional<List<TableConstraint>> getTableConstraints() {
    +567            return tableConstraints;
    +568        }
    +569
    +570        /** @return Optimizer version to set for this table. */
    +571        public @Nonnull Optional<Integer> getOptimizerVersion() {
    +572            return optimizerVersion;
    +573        }
    +574
    +575        /** @return Data versioning retention period to set for this table. */
    +576        public @Nonnull Optional<String> getVersionRetentionPeriod() {
    +577            return versionRetentionPeriod;
    +578        }
    +579
    +580        /** @return Parent interleaving target for this table. */
    +581        public @Nonnull Optional<InterleaveTarget> getInterleaveTarget() {
    +582            return interleaveTarget;
    +583        }
    +584
    +585        // -- Builder API: Setters -- //
    +586
    +587        /**
    +588         * Set the sort direction for the primary key column in this table.
    +589         *
    +590         * @param keySortDirection Key column sort direction.
    +591         * @return Self, for chained calls to the builder.
    +592         */
    +593        public @Nonnull Builder setKeySortDirection(@Nonnull SortDirection keySortDirection) {
    +594            this.keySortDirection = keySortDirection;
    +595            return this;
    +596        }
    +597
    +598        /**
    +599         * Set, or clear, the set of table constraints added to this table.
    +600         *
    +601         * @param tableConstraints Desired table constraints to set or clear, as applicable.
    +602         * @return Self, for chained calls to the builder.
    +603         */
    +604        public @Nonnull Builder setTableConstraints(@Nonnull Optional<List<TableConstraint>> tableConstraints) {
    +605            this.tableConstraints = tableConstraints;
    +606            return this;
    +607        }
    +608
    +609        /**
    +610         * Set, or clear, the optimizer version to apply when creating this table.
    +611         *
    +612         * @param optimizerVersion Desired optimizer version to apply, as applicable.
    +613         * @return Self, for chained calls to the builder.
    +614         */
    +615        public @Nonnull Builder setOptimizerVersion(@Nonnull Optional<Integer> optimizerVersion) {
    +616            this.optimizerVersion = optimizerVersion;
    +617            return this;
    +618        }
    +619
    +620        /**
    +621         * Set, or clear, the data versioning retention period for this table.
    +622         *
    +623         * @param versionRetentionPeriod Desired data versioning retention period, as applicable.
    +624         * @return Self, for chained calls to the builder.
    +625         */
    +626        public @Nonnull Builder setVersionRetentionPeriod(@Nonnull Optional<String> versionRetentionPeriod) {
    +627            this.versionRetentionPeriod = versionRetentionPeriod;
    +628            return this;
    +629        }
    +630
    +631        /**
    +632         * Set, or clear, the parent interleave target for this table.
    +633         *
    +634         * @param interleaveTarget Desired parent interleave target, as applicable.
    +635         * @return Self, for chained calls to the builder.
    +636         */
    +637        public @Nonnull Builder setInterleaveTarget(@Nonnull Optional<InterleaveTarget> interleaveTarget) {
    +638            this.interleaveTarget = interleaveTarget;
    +639            return this;
    +640        }
    +641    }
    +642
    +643    /** Model that relates to this generated statement. */
    +644    private final @Nonnull Descriptor model;
    +645
    +646    /** Resolved name of the table. */
    +647    private final @Nonnull String tableName;
    +648
    +649    /** Set of generated columns determined to be part of this table. */
    +650    private final @Nonnull List<ColumnSpec> columns;
    +651
    +652    /** Holds the generated query in a string buffer. */
    +653    private final @Nonnull StringBuilder generatedStatement;
    +654
    +655    /**
    +656     * Private constructor.
    +657     *
    +658     * @param tableName Name of the generated table.
    +659     * @param columns Generated set of columns.
    +660     * @param model Model this table corresponds to.
    +661     * @param generatedStatement Rendered DDL statement.
    +662     */
    +663    private SpannerGeneratedDDL(@Nonnull String tableName,
    +664                                @Nonnull List<ColumnSpec> columns,
    +665                                @Nonnull Descriptor model,
    +666                                @Nonnull StringBuilder generatedStatement) {
    +667        this.tableName = tableName;
    +668        this.columns = columns;
    +669        this.generatedStatement = generatedStatement;
    +670        this.model = model;
    +671    }
    +672
    +673    /**
    +674     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +675     * that model's properties. This method variant operates from a full model instance.
    +676     *
    +677     * <p>This method offers no ability to control driver settings. See below if you need alternatives.</p>
    +678     *
    +679     * @see #generateTableDDL(Message, Optional) For control over driver settings, optionally.
    +680     * @param instance Model instance to generate a table statement for.
    +681     * @return Generated DDL statement object.
    +682     */
    +683    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(@Nonnull Message instance) {
    +684        return generateTableDDL(instance, Optional.of(SpannerDriverSettings.DEFAULTS));
    +685    }
    +686
    +687    /**
    +688     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +689     * that model's properties. This method variant operates from a full model instance.
    +690     *
    +691     * @param instance Model instance to generate a table statement for.
    +692     * @param settings Settings to employ for the driver. These must align at runtime.
    +693     * @return Generated DDL statement object.
    +694     */
    +695    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(
    +696            @Nonnull Message instance,
    +697            @Nonnull Optional<SpannerDriverSettings> settings) {
    +698        return generateTableDDL(
    +699            instance.getDescriptorForType(),
    +700            settings.orElse(SpannerDriverSettings.DEFAULTS)
    +701        );
    +702    }
    +703
    +704    /**
    +705     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +706     * that model's properties.
    +707     *
    +708     * @param model Model schema to generate a table statement for.
    +709     * @return Generated DDL statement object.
    +710     */
    +711    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(
    +712            @Nonnull Descriptor model,
    +713            @Nonnull SpannerDriverSettings settings) {
    +714        return new SpannerGeneratedDDL.Builder(
    +715            model,
    +716            resolveTableName(model),
    +717            resolveKeyColumn(idField(model).orElseThrow(), settings),
    +718            resolveDefaultColumns(model, settings),
    +719            settings
    +720        );
    +721    }
    +722
    +723    /**
    +724     * Resolve the default calculated set of Spanner columns for a given model structure.
    +725     *
    +726     * @param model Model to traverse and generate columns for.
    +727     * @return Set of generated and type-resolved columns.
    +728     */
    +729    public static @Nonnull List<ColumnSpec> resolveDefaultColumns(@Nonnull Descriptor model,
    +730                                                                  @Nonnull SpannerDriverSettings settings) {
    +731        var keyField = keyField(model).orElseThrow();
    +732        var fieldSet = new LinkedList<ColumnSpec>();
    +733
    +734        // first up: generate the column which implements the model's primary key
    +735        fieldSet.add(ColumnSpec.columnSpecForKey(
    +736            model,
    +737            keyField,
    +738            settings
    +739        ));
    +740
    +741        // next: generate all remaining data columns
    +742        forEachField(
    +743            model,
    +744            Optional.of(onlySpannerEligibleFields(settings))
    +745        ).filter((fieldPointer) ->
    +746            // filter out key fields: we'll handle those separately
    +747            !keyField.getField().getFullName().equals(fieldPointer.getField().getFullName())
    +748        ).map((fieldPointer) ->
    +749            ColumnSpec.columnSpecForField(fieldPointer, settings)
    +750        ).forEach(fieldSet::add);
    +751
    +752        return Collections.unmodifiableList(fieldSet);
    +753    }
    +754
    +755    // -- Accessors -- //
    +756
    +757    /** @return Model for which this object generates a table create statement. */
    +758    public @Nonnull Descriptor getModel() {
    +759        return model;
    +760    }
    +761
    +762    /** @return Resolved name of the table to be created. */
    +763    public @Nonnull String getTableName() {
    +764        return tableName;
    +765    }
    +766
    +767    /** @return Resolved set of Spanner columns. */
    +768    public @Nonnull List<ColumnSpec> getColumns() {
    +769        return columns;
    +770    }
    +771
    +772    /** @return Rendered generated DDL statement. */
    +773    public @Nonnull StringBuilder getGeneratedStatement() {
    +774        return generatedStatement;
    +775    }
    +776
    +777    @Override
    +778    public String toString() {
    +779        return "SpannerDDL{" +
    +780            "model=" + model.getFullName() +
    +781            ", tableName='" + tableName + '\'' +
    +782            ", statement=\"" + generatedStatement.toString() +
    +783        "\"}";
    +784    }
    +785}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.SortDirection.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.SortDirection.html new file mode 100644 index 000000000..df55f2dd9 --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.SortDirection.html @@ -0,0 +1,859 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import com.google.cloud.spanner.Type;
    +016import com.google.protobuf.Message;
    +017import com.google.protobuf.Timestamp;
    +018import tools.elide.core.SpannerFieldOptions;
    +019
    +020import javax.annotation.Nonnull;
    +021import javax.annotation.concurrent.Immutable;
    +022import javax.annotation.concurrent.ThreadSafe;
    +023import java.util.*;
    +024import java.util.stream.Collectors;
    +025
    +026import static com.google.protobuf.Descriptors.Descriptor;
    +027import static com.google.protobuf.Descriptors.FieldDescriptor;
    +028import static gust.backend.driver.spanner.SpannerUtil.*;
    +029import static gust.backend.model.ModelMetadata.*;
    +030import static java.lang.String.format;
    +031import static java.lang.String.join;
    +032
    +033
    +034/** Container for generated schema-driven Spanner DDL. */
    +035@Immutable
    +036@ThreadSafe
    +037public final class SpannerGeneratedDDL {
    +038    /** Represents a DDL statement structure in code that can be rendered down to a string. */
    +039    public interface RenderableStatement {
    +040        /**
    +041         * Render this statement into a String buffer.
    +042         *
    +043         * @return Rendered statement.
    +044         */
    +045        @Nonnull StringBuilder render();
    +046    }
    +047
    +048    /** Sort direction settings which can apply to columns. */
    +049    public enum SortDirection {
    +050        /** Sort values in the column in ascending order. This is the default value. */
    +051        ASC,
    +052
    +053        /** Sort values in the column in descending order. */
    +054        DESC
    +055    }
    +056
    +057    /** Specifies options for reference action propagation (i.e. on-delete or on-update). */
    +058    public enum PropagatedAction {
    +059        /** Take no action. This is the default value. */
    +060        NO_ACTION,
    +061
    +062        /** Cascade changes on delete or update. */
    +063        CASCADE
    +064    }
    +065
    +066    /** Specifies a generic table constraint to include in a DDL statement. */
    +067    public static final class TableConstraint implements RenderableStatement {
    +068        final @Nonnull String name;
    +069        final @Nonnull String expression;
    +070
    +071        /**
    +072         * Private constructor for a table constraint specification.
    +073         *
    +074         * @param name Name of the table constraint.
    +075         * @param expression Expression to use as a constraint.
    +076         */
    +077        private TableConstraint(@Nonnull String name, @Nonnull String expression) {
    +078            this.name = name;
    +079            this.expression = expression;
    +080        }
    +081
    +082        /**
    +083         * Spawn a table constraint at the provided name, enforcing the provided expression.
    +084         *
    +085         * @param name Name of the constraint to enclose in the DDL statement.
    +086         * @param expression Expression to enforce as a table constraint.
    +087         * @return Table constraint specification.
    +088         */
    +089        public static @Nonnull TableConstraint named(@Nonnull String name,
    +090                                                     @Nonnull String expression) {
    +091            return new TableConstraint(name, expression);
    +092        }
    +093
    +094        /** @inheritDoc */
    +095        @Override
    +096        public @Nonnull StringBuilder render() {
    +097            return (new StringBuilder()).append(format(
    +098                "CONSTRAINT %s CHECK ( %s )",
    +099                this.name,
    +100                this.expression
    +101            ));
    +102        }
    +103    }
    +104
    +105    /** Specify a parent table against which this table is interleaved. */
    +106    public static final class InterleaveTarget implements RenderableStatement {
    +107        final @Nonnull String parent;
    +108        final @Nonnull Optional<PropagatedAction> action;
    +109
    +110        /**
    +111         * Private constructor for an interleave target for a Spanner table.
    +112         *
    +113         * @param parent Parent table where we should interleave this table.
    +114         * @param action Action to take, if any, when deletes or changes happen in the parent table.
    +115         */
    +116        private InterleaveTarget(@Nonnull String parent,
    +117                                 @Nonnull Optional<PropagatedAction> action) {
    +118            this.parent = parent;
    +119            this.action = action;
    +120        }
    +121
    +122        /**
    +123         * Generate an interleave target specification for the provided parent table name.
    +124         *
    +125         * @see #forParent(String, Optional) To pass a propagation action.
    +126         * @param parent Parent table where we should interleave a given table.
    +127         * @return Interleave target specification.
    +128         */
    +129        public static @Nonnull InterleaveTarget forParent(@Nonnull String parent) {
    +130            return forParent(parent, Optional.empty());
    +131        }
    +132
    +133        /**
    +134         * Generate an interleave target specification for the provided parent table name, optionally applying the
    +135         * provided propagation action.
    +136         *
    +137         * @param parent Parent table where we should interleave a given table.
    +138         * @param action Action to take, if any, when parent rows change that should affect the child table.
    +139         * @return Interleave target specification.
    +140         */
    +141        public static @Nonnull InterleaveTarget forParent(@Nonnull String parent,
    +142                                                          @Nonnull Optional<PropagatedAction> action) {
    +143            return new InterleaveTarget(parent, action);
    +144        }
    +145
    +146        /** @inheritDoc */
    +147        @Override
    +148        public @Nonnull StringBuilder render() {
    +149            var buf = new StringBuilder(format(
    +150                "INTERLEAVE IN PARENT %s",
    +151                this.parent
    +152            ));
    +153
    +154            if (this.action.isPresent()) {
    +155                buf.append(" ");
    +156                buf.append(format(
    +157                    "ON DELETE %s",
    +158                    this.action.get().name()
    +159                ));
    +160            }
    +161
    +162            return buf;
    +163        }
    +164    }
    +165
    +166    /** Specifies an individual field as part of a DDL statement. */
    +167    private static final class ColumnSpec implements RenderableStatement {
    +168        final @Nonnull String name;
    +169        final @Nonnull Type type;
    +170        final @Nonnull Integer length;
    +171        final @Nonnull FieldDescriptor field;
    +172        final @Nonnull Boolean nonnull;
    +173        final @Nonnull Optional<String> expression;
    +174        final @Nonnull Boolean expressionStored;
    +175        final @Nonnull Boolean allowCommitTimestamp;
    +176
    +177        /**
    +178         * Private constructor.
    +179         *
    +180         * @param name Column name in Spanner.
    +181         * @param type Type specification in Spanner.
    +182         * @param length Length for string fields, or `0`.
    +183         * @param nonnull Whether to mark the field as non-null.
    +184         * @param allowCommitTimestamp Whether to fill this column with the commit timestamp.
    +185         * @param field Model field that spawned this column specification.
    +186         */
    +187        ColumnSpec(@Nonnull String name,
    +188                   @Nonnull Type type,
    +189                   @Nonnull Integer length,
    +190                   @Nonnull Optional<Boolean> nonnull,
    +191                   @Nonnull Optional<String> expression,
    +192                   @Nonnull Optional<Boolean> expressionStored,
    +193                   @Nonnull Optional<Boolean> allowCommitTimestamp,
    +194                   @Nonnull FieldDescriptor field) {
    +195            this.name = name;
    +196            this.type = type;
    +197            this.length = length;
    +198            this.nonnull = nonnull.orElse(false);
    +199            this.expression = expression;
    +200            this.expressionStored = expressionStored.orElse(false);
    +201            this.allowCommitTimestamp = allowCommitTimestamp.orElse(false);
    +202            this.field = field;
    +203        }
    +204
    +205        /**
    +206         * Create a column spec for the provided field information, considering any active driver settings.
    +207         *
    +208         * @param fieldPointer Pointer to the field we should consider.
    +209         * @param settings Active set of Spanner driver settings.
    +210         * @return Spawned column corresponding to the provided field.
    +211         */
    +212        static @Nonnull ColumnSpec columnSpecForField(@Nonnull FieldPointer fieldPointer,
    +213                                                      @Nonnull SpannerDriverSettings settings) {
    +214            var columnOpts = columnOpts(fieldPointer);
    +215            var spannerOpts = spannerOpts(fieldPointer);
    +216            var fieldOpts = fieldOpts(fieldPointer);
    +217            var fieldName = resolveColumnName(fieldPointer, spannerOpts, columnOpts, settings);
    +218            var fieldType = resolveColumnType(fieldPointer, spannerOpts, columnOpts, settings);
    +219            Type innerType = fieldPointer.getField().isRepeated() ?
    +220                    fieldType.getArrayElementType() :
    +221                    fieldType;
    +222            var fieldSize = innerType.getCode() == Type.Code.STRING || innerType.getCode() == Type.Code.BYTES ?
    +223                    resolveColumnSize(fieldPointer.getField(), spannerOpts, columnOpts, settings) :
    +224                    -1;
    +225
    +226            // resolve spanner opts or defaults
    +227            var resolvedSpannerOpts = spannerOpts.orElse(SpannerFieldOptions.getDefaultInstance());
    +228            var expression = resolvedSpannerOpts.getExpression().length() > 0 ?
    +229                    Optional.of(resolvedSpannerOpts.getExpression()) : Optional.<String>empty();
    +230
    +231            var commitUpdate = false;
    +232            var protoType = fieldPointer.getField().getType();
    +233            if (fieldOpts.isPresent() && fieldOpts.get().getStampUpdate()) {
    +234                switch (protoType) {
    +235                    case STRING:
    +236                    case UINT64:
    +237                    case FIXED64:
    +238                        commitUpdate = true;
    +239                        break;
    +240
    +241                    case MESSAGE:
    +242                        if (fieldPointer.getField().getMessageType().getFullName().equals(
    +243                            Timestamp.getDescriptor().getFullName())) {
    +244                            commitUpdate = true;  // we can decode from a `Timestamp` record
    +245                        }
    +246                        break;
    +247
    +248                    default:
    +249                        // any other field type represents an illegal state
    +250                        throw new IllegalStateException(format(
    +251                            "Cannot place `commit_timestamp` in field of type '%s'", protoType.name()));
    +252                }
    +253            }
    +254
    +255            return new ColumnSpec(
    +256                fieldName,
    +257                fieldType,
    +258                fieldSize,
    +259                Optional.of(resolvedSpannerOpts.getNonnull()),
    +260                expression,
    +261                Optional.of(resolvedSpannerOpts.getStored()),
    +262                Optional.of(commitUpdate),
    +263                fieldPointer.getField()
    +264            );
    +265        }
    +266
    +267        /**
    +268         * Create a column spec for the provided model key field, considering any active driver settings.
    +269         *
    +270         * @param model Model schema for the object or key record.
    +271         * @param keyField Primary key field pre-resolved for a given Spanner table.
    +272         * @param settings Active Spanner driver settings.
    +273         * @return Spawned primary key column corresponding to the provided model key.
    +274         */
    +275        static @Nonnull ColumnSpec columnSpecForKey(@Nonnull Descriptor model,
    +276                                                    @Nonnull FieldPointer keyField,
    +277                                                    @Nonnull SpannerDriverSettings settings) {
    +278            var idField = idField(model).orElseThrow();
    +279            var keyName = resolveKeyColumn(idField, settings);
    +280            var keyType = resolveKeyType(idField);
    +281            var spannerOpts = spannerOpts(idField);
    +282            var columnOpts = columnOpts(idField);
    +283            int columnSize = -1;
    +284            if (keyType.getCode() == Type.Code.STRING ||
    +285                keyType.getCode() == Type.Code.BYTES) {
    +286                columnSize = resolveColumnSize(keyField.getField(), spannerOpts, columnOpts, settings);
    +287            }
    +288
    +289            return new ColumnSpec(
    +290                keyName,
    +291                keyType,
    +292                columnSize,
    +293                Optional.of(true),  // primary keys are always set to `NOT NULL`.
    +294                Optional.empty(),  // primary keys do not support expressions
    +295                Optional.empty(),
    +296                Optional.empty(),  // primary keys cannot be set to the commit timestamp
    +297                keyField.getField()
    +298            );
    +299        }
    +300
    +301        /**
    +302         * Render this column spec into a definition statement, suitable for use when creating a table.
    +303         *
    +304         * @return Rendered column spec statement.
    +305         */
    +306        @Override
    +307        public @Nonnull StringBuilder render() {
    +308            // prepare field statement
    +309            var buf = new StringBuilder();
    +310
    +311            // calculate field type designation first
    +312            String fieldType;
    +313            Type.Code innerType = this.field.isRepeated() ?
    +314                    this.type.getArrayElementType().getCode() :
    +315                    this.type.getCode();
    +316
    +317            String innerTypeSpec;
    +318            if (innerType == Type.Code.STRING || innerType == Type.Code.BYTES) {
    +319                innerTypeSpec = format(
    +320                    "%s(%s)",
    +321                    innerType.name(),
    +322                    this.length
    +323                );
    +324            } else {
    +325                innerTypeSpec = innerType.name();
    +326            }
    +327            if (this.type.getCode() == Type.Code.ARRAY) {
    +328                // it's a repeated field
    +329                fieldType = format(
    +330                    "ARRAY<%s>",
    +331                    innerTypeSpec
    +332                );
    +333            } else {
    +334                // it's a singular field. make sure to cover the special case for strings.
    +335                fieldType = innerTypeSpec;
    +336            }
    +337
    +338            buf.append(format(
    +339                "%s %s",
    +340                this.name,
    +341                fieldType
    +342            ));
    +343
    +344            // prepare field options
    +345            var optionsBuffer = new ArrayList<String>();
    +346
    +347            // consider NONNULL
    +348            if (this.nonnull) {
    +349                optionsBuffer.add("NOT NULL");
    +350            }
    +351
    +352            // consider expressions
    +353            if (this.expression.isPresent()) {
    +354                optionsBuffer.add(format("AS ( %s )", this.expression.get()));
    +355                if (this.expressionStored)
    +356                    optionsBuffer.add("STORED");
    +357            }
    +358
    +359            // consider options
    +360            if (this.allowCommitTimestamp) {
    +361                optionsBuffer.add("OPTIONS allow_commit_timestamp = true");
    +362            }
    +363            if (!optionsBuffer.isEmpty()) {
    +364                buf.append(" ");
    +365                buf.append(join(" ", optionsBuffer));
    +366            }
    +367            return buf;
    +368        }
    +369    }
    +370
    +371    /**
    +372     * Build properties for a generated Spanner table DDL statement, based on a given model instance as a base for
    +373     * configuring the table name (via annotations / calculated defaults) and set of typed Spanner value columns.
    +374     *
    +375     * <p>To build the actual DDL statement, fill out the builder, build it, and then ask the resulting object for the
    +376     * DDL as a string.</p>
    +377     */
    +378    @SuppressWarnings("unused")
    +379    public static final class Builder {
    +380        /** Base model on which this builder will operate. Immutable. */
    +381        final @Nonnull Descriptor model;
    +382
    +383        /** Active set of driver settings. Immutable. */
    +384        final @Nonnull SpannerDriverSettings settings;
    +385
    +386        /** Immutable: Name of the table in Spanner. */
    +387        final @Nonnull String tableName;
    +388
    +389        /** Immutable: Name of the primary key column. */
    +390        final @Nonnull String primaryKey;
    +391
    +392        /** Immutable: Generated columns in Spanner. */
    +393        final @Nonnull List<ColumnSpec> columns;
    +394
    +395        /** Mutable: Key column sort direction. */
    +396        @Nonnull SortDirection keySortDirection;
    +397
    +398        /** Mutable: List of table constraints. */
    +399        @Nonnull Optional<List<TableConstraint>> tableConstraints;
    +400
    +401        /** Mutable: Optimizer version to apply. */
    +402        @Nonnull Optional<Integer> optimizerVersion;
    +403
    +404        /** Mutable: Version retention period. */
    +405        @Nonnull Optional<String> versionRetentionPeriod;
    +406
    +407        /** Mutable: Table interleave target. */
    +408        @Nonnull Optional<InterleaveTarget> interleaveTarget;
    +409
    +410        /**
    +411         * Package-private constructor for a builder.
    +412         *
    +413         * @see SpannerGeneratedDDL#generateTableDDL(Descriptor, SpannerDriverSettings) to spawn one of
    +414         *      these from regular library or application code.
    +415         * @param model Descriptor for the model we are building against.
    +416         * @param primaryKey Primary key field name to use for this table by default.
    +417         * @param tableName Resolved table name to use for this table.
    +418         * @param defaultColumns Default set of columns to use for this table.
    +419         * @param settings Active driver settings to apply/consider.
    +420         */
    +421        Builder(@Nonnull Descriptor model,
    +422                @Nonnull String tableName,
    +423                @Nonnull String primaryKey,
    +424                @Nonnull List<ColumnSpec> defaultColumns,
    +425                @Nonnull SpannerDriverSettings settings) {
    +426            this.model = model;
    +427            this.tableName = tableName;
    +428            this.settings = settings;
    +429            this.columns = defaultColumns;
    +430            this.primaryKey = primaryKey;
    +431            this.keySortDirection = SortDirection.ASC;
    +432            this.tableConstraints = Optional.empty();
    +433            this.optimizerVersion = Optional.empty();
    +434            this.versionRetentionPeriod = Optional.empty();
    +435            this.interleaveTarget = Optional.empty();
    +436        }
    +437
    +438        /**
    +439         * Render column definition statements for a final DDL table create statement.
    +440         *
    +441         * @return Column definition statements, stacked in a buffer.
    +442         */
    +443        @Nonnull String renderColumnStatements() {
    +444            return this.columns.stream()
    +445                    .map(ColumnSpec::render)
    +446                    .collect(Collectors.joining(", "));
    +447        }
    +448
    +449        /**
    +450         * Render table-level constraint statements for a final DDL table create statement.
    +451         *
    +452         * @return Any applicable rendered table constraints.
    +453         */
    +454        @Nonnull String renderConstraintStatements() {
    +455            return this.tableConstraints.map(constraints -> constraints
    +456                    .stream()
    +457                    .map(TableConstraint::render)
    +458                    .collect(Collectors.joining(", ")))
    +459                    .orElse("");
    +460        }
    +461
    +462        /**
    +463         * Render inner statements in the CREATE TABLE DDL statement, including columns and constraints, as applicable.
    +464         * If no constraints are present, we simply return the column definitions alone.
    +465         *
    +466         * @return Rendered definitions of columns and table constraints.
    +467         */
    +468        @Nonnull String renderColumnStatementsAndConstraints() {
    +469            var columnList = renderColumnStatements();
    +470            if (this.tableConstraints.isPresent()) {
    +471                var constraints = renderConstraintStatements();
    +472                return format("%s, %s", columnList, constraints);
    +473            }
    +474            return columnList;
    +475        }
    +476
    +477        /**
    +478         * Render the primary key specification for a final DDL table create statement.
    +479         *
    +480         * @return Rendered primary key specification.
    +481         */
    +482        @Nonnull String renderPrimaryKey() {
    +483            return format(
    +484                "%s %s",
    +485                this.primaryKey,
    +486                this.keySortDirection.name()
    +487            );
    +488        }
    +489
    +490        /**
    +491         * Render the prepared DDL statement details into a statement string which can be passed to Spanner.
    +492         *
    +493         * @return Rendered DDL statement, according to local object settings.
    +494         */
    +495        @Nonnull StringBuilder renderCreateDDLStatement() {
    +496            var builder = new StringBuilder();
    +497            var buf = new ArrayList<StringBuilder>();
    +498            buf.add(new StringBuilder(format(
    +499                "CREATE TABLE %s (%s) PRIMARY KEY (%s)",
    +500                this.tableName,
    +501                this.renderColumnStatementsAndConstraints(),
    +502                this.renderPrimaryKey()
    +503            )));
    +504
    +505            // add interleave target statement, if specified
    +506            this.interleaveTarget.ifPresent(target -> buf.add(target.render()));
    +507
    +508            builder.append(join(", ", buf));
    +509            return builder;
    +510        }
    +511
    +512        /**
    +513         * Collapse the builder into an immutable DDL statement container
    +514         *
    +515         * @return Immutable DDL statement container.
    +516         */
    +517        public @Nonnull SpannerGeneratedDDL build() {
    +518            var fields = forEachField(
    +519                model,
    +520                Optional.of(onlySpannerEligibleFields(settings))
    +521            ).map((fieldPointer) ->
    +522                    ColumnSpec.columnSpecForField(fieldPointer, settings)
    +523            ).collect(Collectors.toUnmodifiableList());
    +524
    +525            return new SpannerGeneratedDDL(
    +526                tableName,
    +527                fields,
    +528                model,
    +529                renderCreateDDLStatement()
    +530            );
    +531        }
    +532
    +533        // -- Builder API: Getters -- //
    +534
    +535        /** @return Model descriptor this builder wraps. */
    +536        public @Nonnull Descriptor getModel() {
    +537            return model;
    +538        }
    +539
    +540        /** @return Active Spanner driver settings. */
    +541        public @Nonnull SpannerDriverSettings getSettings() {
    +542            return settings;
    +543        }
    +544
    +545        /** @return Generated or resolved Spanner table name. */
    +546        public @Nonnull String getTableName() {
    +547            return tableName;
    +548        }
    +549
    +550        /** @return Primary key column for this model/table. */
    +551        public @Nonnull String getPrimaryKey() {
    +552            return primaryKey;
    +553        }
    +554
    +555        /** @return Set of generated columns for this model in Spanner. */
    +556        public @Nonnull List<ColumnSpec> getColumns() {
    +557            return columns;
    +558        }
    +559
    +560        /** @return Primary key column sort direction. */
    +561        public @Nonnull SortDirection getKeySortDirection() {
    +562            return keySortDirection;
    +563        }
    +564
    +565        /** @return Set of constraints to apply to this table. */
    +566        public @Nonnull Optional<List<TableConstraint>> getTableConstraints() {
    +567            return tableConstraints;
    +568        }
    +569
    +570        /** @return Optimizer version to set for this table. */
    +571        public @Nonnull Optional<Integer> getOptimizerVersion() {
    +572            return optimizerVersion;
    +573        }
    +574
    +575        /** @return Data versioning retention period to set for this table. */
    +576        public @Nonnull Optional<String> getVersionRetentionPeriod() {
    +577            return versionRetentionPeriod;
    +578        }
    +579
    +580        /** @return Parent interleaving target for this table. */
    +581        public @Nonnull Optional<InterleaveTarget> getInterleaveTarget() {
    +582            return interleaveTarget;
    +583        }
    +584
    +585        // -- Builder API: Setters -- //
    +586
    +587        /**
    +588         * Set the sort direction for the primary key column in this table.
    +589         *
    +590         * @param keySortDirection Key column sort direction.
    +591         * @return Self, for chained calls to the builder.
    +592         */
    +593        public @Nonnull Builder setKeySortDirection(@Nonnull SortDirection keySortDirection) {
    +594            this.keySortDirection = keySortDirection;
    +595            return this;
    +596        }
    +597
    +598        /**
    +599         * Set, or clear, the set of table constraints added to this table.
    +600         *
    +601         * @param tableConstraints Desired table constraints to set or clear, as applicable.
    +602         * @return Self, for chained calls to the builder.
    +603         */
    +604        public @Nonnull Builder setTableConstraints(@Nonnull Optional<List<TableConstraint>> tableConstraints) {
    +605            this.tableConstraints = tableConstraints;
    +606            return this;
    +607        }
    +608
    +609        /**
    +610         * Set, or clear, the optimizer version to apply when creating this table.
    +611         *
    +612         * @param optimizerVersion Desired optimizer version to apply, as applicable.
    +613         * @return Self, for chained calls to the builder.
    +614         */
    +615        public @Nonnull Builder setOptimizerVersion(@Nonnull Optional<Integer> optimizerVersion) {
    +616            this.optimizerVersion = optimizerVersion;
    +617            return this;
    +618        }
    +619
    +620        /**
    +621         * Set, or clear, the data versioning retention period for this table.
    +622         *
    +623         * @param versionRetentionPeriod Desired data versioning retention period, as applicable.
    +624         * @return Self, for chained calls to the builder.
    +625         */
    +626        public @Nonnull Builder setVersionRetentionPeriod(@Nonnull Optional<String> versionRetentionPeriod) {
    +627            this.versionRetentionPeriod = versionRetentionPeriod;
    +628            return this;
    +629        }
    +630
    +631        /**
    +632         * Set, or clear, the parent interleave target for this table.
    +633         *
    +634         * @param interleaveTarget Desired parent interleave target, as applicable.
    +635         * @return Self, for chained calls to the builder.
    +636         */
    +637        public @Nonnull Builder setInterleaveTarget(@Nonnull Optional<InterleaveTarget> interleaveTarget) {
    +638            this.interleaveTarget = interleaveTarget;
    +639            return this;
    +640        }
    +641    }
    +642
    +643    /** Model that relates to this generated statement. */
    +644    private final @Nonnull Descriptor model;
    +645
    +646    /** Resolved name of the table. */
    +647    private final @Nonnull String tableName;
    +648
    +649    /** Set of generated columns determined to be part of this table. */
    +650    private final @Nonnull List<ColumnSpec> columns;
    +651
    +652    /** Holds the generated query in a string buffer. */
    +653    private final @Nonnull StringBuilder generatedStatement;
    +654
    +655    /**
    +656     * Private constructor.
    +657     *
    +658     * @param tableName Name of the generated table.
    +659     * @param columns Generated set of columns.
    +660     * @param model Model this table corresponds to.
    +661     * @param generatedStatement Rendered DDL statement.
    +662     */
    +663    private SpannerGeneratedDDL(@Nonnull String tableName,
    +664                                @Nonnull List<ColumnSpec> columns,
    +665                                @Nonnull Descriptor model,
    +666                                @Nonnull StringBuilder generatedStatement) {
    +667        this.tableName = tableName;
    +668        this.columns = columns;
    +669        this.generatedStatement = generatedStatement;
    +670        this.model = model;
    +671    }
    +672
    +673    /**
    +674     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +675     * that model's properties. This method variant operates from a full model instance.
    +676     *
    +677     * <p>This method offers no ability to control driver settings. See below if you need alternatives.</p>
    +678     *
    +679     * @see #generateTableDDL(Message, Optional) For control over driver settings, optionally.
    +680     * @param instance Model instance to generate a table statement for.
    +681     * @return Generated DDL statement object.
    +682     */
    +683    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(@Nonnull Message instance) {
    +684        return generateTableDDL(instance, Optional.of(SpannerDriverSettings.DEFAULTS));
    +685    }
    +686
    +687    /**
    +688     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +689     * that model's properties. This method variant operates from a full model instance.
    +690     *
    +691     * @param instance Model instance to generate a table statement for.
    +692     * @param settings Settings to employ for the driver. These must align at runtime.
    +693     * @return Generated DDL statement object.
    +694     */
    +695    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(
    +696            @Nonnull Message instance,
    +697            @Nonnull Optional<SpannerDriverSettings> settings) {
    +698        return generateTableDDL(
    +699            instance.getDescriptorForType(),
    +700            settings.orElse(SpannerDriverSettings.DEFAULTS)
    +701        );
    +702    }
    +703
    +704    /**
    +705     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +706     * that model's properties.
    +707     *
    +708     * @param model Model schema to generate a table statement for.
    +709     * @return Generated DDL statement object.
    +710     */
    +711    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(
    +712            @Nonnull Descriptor model,
    +713            @Nonnull SpannerDriverSettings settings) {
    +714        return new SpannerGeneratedDDL.Builder(
    +715            model,
    +716            resolveTableName(model),
    +717            resolveKeyColumn(idField(model).orElseThrow(), settings),
    +718            resolveDefaultColumns(model, settings),
    +719            settings
    +720        );
    +721    }
    +722
    +723    /**
    +724     * Resolve the default calculated set of Spanner columns for a given model structure.
    +725     *
    +726     * @param model Model to traverse and generate columns for.
    +727     * @return Set of generated and type-resolved columns.
    +728     */
    +729    public static @Nonnull List<ColumnSpec> resolveDefaultColumns(@Nonnull Descriptor model,
    +730                                                                  @Nonnull SpannerDriverSettings settings) {
    +731        var keyField = keyField(model).orElseThrow();
    +732        var fieldSet = new LinkedList<ColumnSpec>();
    +733
    +734        // first up: generate the column which implements the model's primary key
    +735        fieldSet.add(ColumnSpec.columnSpecForKey(
    +736            model,
    +737            keyField,
    +738            settings
    +739        ));
    +740
    +741        // next: generate all remaining data columns
    +742        forEachField(
    +743            model,
    +744            Optional.of(onlySpannerEligibleFields(settings))
    +745        ).filter((fieldPointer) ->
    +746            // filter out key fields: we'll handle those separately
    +747            !keyField.getField().getFullName().equals(fieldPointer.getField().getFullName())
    +748        ).map((fieldPointer) ->
    +749            ColumnSpec.columnSpecForField(fieldPointer, settings)
    +750        ).forEach(fieldSet::add);
    +751
    +752        return Collections.unmodifiableList(fieldSet);
    +753    }
    +754
    +755    // -- Accessors -- //
    +756
    +757    /** @return Model for which this object generates a table create statement. */
    +758    public @Nonnull Descriptor getModel() {
    +759        return model;
    +760    }
    +761
    +762    /** @return Resolved name of the table to be created. */
    +763    public @Nonnull String getTableName() {
    +764        return tableName;
    +765    }
    +766
    +767    /** @return Resolved set of Spanner columns. */
    +768    public @Nonnull List<ColumnSpec> getColumns() {
    +769        return columns;
    +770    }
    +771
    +772    /** @return Rendered generated DDL statement. */
    +773    public @Nonnull StringBuilder getGeneratedStatement() {
    +774        return generatedStatement;
    +775    }
    +776
    +777    @Override
    +778    public String toString() {
    +779        return "SpannerDDL{" +
    +780            "model=" + model.getFullName() +
    +781            ", tableName='" + tableName + '\'' +
    +782            ", statement=\"" + generatedStatement.toString() +
    +783        "\"}";
    +784    }
    +785}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.TableConstraint.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.TableConstraint.html new file mode 100644 index 000000000..df55f2dd9 --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.TableConstraint.html @@ -0,0 +1,859 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import com.google.cloud.spanner.Type;
    +016import com.google.protobuf.Message;
    +017import com.google.protobuf.Timestamp;
    +018import tools.elide.core.SpannerFieldOptions;
    +019
    +020import javax.annotation.Nonnull;
    +021import javax.annotation.concurrent.Immutable;
    +022import javax.annotation.concurrent.ThreadSafe;
    +023import java.util.*;
    +024import java.util.stream.Collectors;
    +025
    +026import static com.google.protobuf.Descriptors.Descriptor;
    +027import static com.google.protobuf.Descriptors.FieldDescriptor;
    +028import static gust.backend.driver.spanner.SpannerUtil.*;
    +029import static gust.backend.model.ModelMetadata.*;
    +030import static java.lang.String.format;
    +031import static java.lang.String.join;
    +032
    +033
    +034/** Container for generated schema-driven Spanner DDL. */
    +035@Immutable
    +036@ThreadSafe
    +037public final class SpannerGeneratedDDL {
    +038    /** Represents a DDL statement structure in code that can be rendered down to a string. */
    +039    public interface RenderableStatement {
    +040        /**
    +041         * Render this statement into a String buffer.
    +042         *
    +043         * @return Rendered statement.
    +044         */
    +045        @Nonnull StringBuilder render();
    +046    }
    +047
    +048    /** Sort direction settings which can apply to columns. */
    +049    public enum SortDirection {
    +050        /** Sort values in the column in ascending order. This is the default value. */
    +051        ASC,
    +052
    +053        /** Sort values in the column in descending order. */
    +054        DESC
    +055    }
    +056
    +057    /** Specifies options for reference action propagation (i.e. on-delete or on-update). */
    +058    public enum PropagatedAction {
    +059        /** Take no action. This is the default value. */
    +060        NO_ACTION,
    +061
    +062        /** Cascade changes on delete or update. */
    +063        CASCADE
    +064    }
    +065
    +066    /** Specifies a generic table constraint to include in a DDL statement. */
    +067    public static final class TableConstraint implements RenderableStatement {
    +068        final @Nonnull String name;
    +069        final @Nonnull String expression;
    +070
    +071        /**
    +072         * Private constructor for a table constraint specification.
    +073         *
    +074         * @param name Name of the table constraint.
    +075         * @param expression Expression to use as a constraint.
    +076         */
    +077        private TableConstraint(@Nonnull String name, @Nonnull String expression) {
    +078            this.name = name;
    +079            this.expression = expression;
    +080        }
    +081
    +082        /**
    +083         * Spawn a table constraint at the provided name, enforcing the provided expression.
    +084         *
    +085         * @param name Name of the constraint to enclose in the DDL statement.
    +086         * @param expression Expression to enforce as a table constraint.
    +087         * @return Table constraint specification.
    +088         */
    +089        public static @Nonnull TableConstraint named(@Nonnull String name,
    +090                                                     @Nonnull String expression) {
    +091            return new TableConstraint(name, expression);
    +092        }
    +093
    +094        /** @inheritDoc */
    +095        @Override
    +096        public @Nonnull StringBuilder render() {
    +097            return (new StringBuilder()).append(format(
    +098                "CONSTRAINT %s CHECK ( %s )",
    +099                this.name,
    +100                this.expression
    +101            ));
    +102        }
    +103    }
    +104
    +105    /** Specify a parent table against which this table is interleaved. */
    +106    public static final class InterleaveTarget implements RenderableStatement {
    +107        final @Nonnull String parent;
    +108        final @Nonnull Optional<PropagatedAction> action;
    +109
    +110        /**
    +111         * Private constructor for an interleave target for a Spanner table.
    +112         *
    +113         * @param parent Parent table where we should interleave this table.
    +114         * @param action Action to take, if any, when deletes or changes happen in the parent table.
    +115         */
    +116        private InterleaveTarget(@Nonnull String parent,
    +117                                 @Nonnull Optional<PropagatedAction> action) {
    +118            this.parent = parent;
    +119            this.action = action;
    +120        }
    +121
    +122        /**
    +123         * Generate an interleave target specification for the provided parent table name.
    +124         *
    +125         * @see #forParent(String, Optional) To pass a propagation action.
    +126         * @param parent Parent table where we should interleave a given table.
    +127         * @return Interleave target specification.
    +128         */
    +129        public static @Nonnull InterleaveTarget forParent(@Nonnull String parent) {
    +130            return forParent(parent, Optional.empty());
    +131        }
    +132
    +133        /**
    +134         * Generate an interleave target specification for the provided parent table name, optionally applying the
    +135         * provided propagation action.
    +136         *
    +137         * @param parent Parent table where we should interleave a given table.
    +138         * @param action Action to take, if any, when parent rows change that should affect the child table.
    +139         * @return Interleave target specification.
    +140         */
    +141        public static @Nonnull InterleaveTarget forParent(@Nonnull String parent,
    +142                                                          @Nonnull Optional<PropagatedAction> action) {
    +143            return new InterleaveTarget(parent, action);
    +144        }
    +145
    +146        /** @inheritDoc */
    +147        @Override
    +148        public @Nonnull StringBuilder render() {
    +149            var buf = new StringBuilder(format(
    +150                "INTERLEAVE IN PARENT %s",
    +151                this.parent
    +152            ));
    +153
    +154            if (this.action.isPresent()) {
    +155                buf.append(" ");
    +156                buf.append(format(
    +157                    "ON DELETE %s",
    +158                    this.action.get().name()
    +159                ));
    +160            }
    +161
    +162            return buf;
    +163        }
    +164    }
    +165
    +166    /** Specifies an individual field as part of a DDL statement. */
    +167    private static final class ColumnSpec implements RenderableStatement {
    +168        final @Nonnull String name;
    +169        final @Nonnull Type type;
    +170        final @Nonnull Integer length;
    +171        final @Nonnull FieldDescriptor field;
    +172        final @Nonnull Boolean nonnull;
    +173        final @Nonnull Optional<String> expression;
    +174        final @Nonnull Boolean expressionStored;
    +175        final @Nonnull Boolean allowCommitTimestamp;
    +176
    +177        /**
    +178         * Private constructor.
    +179         *
    +180         * @param name Column name in Spanner.
    +181         * @param type Type specification in Spanner.
    +182         * @param length Length for string fields, or `0`.
    +183         * @param nonnull Whether to mark the field as non-null.
    +184         * @param allowCommitTimestamp Whether to fill this column with the commit timestamp.
    +185         * @param field Model field that spawned this column specification.
    +186         */
    +187        ColumnSpec(@Nonnull String name,
    +188                   @Nonnull Type type,
    +189                   @Nonnull Integer length,
    +190                   @Nonnull Optional<Boolean> nonnull,
    +191                   @Nonnull Optional<String> expression,
    +192                   @Nonnull Optional<Boolean> expressionStored,
    +193                   @Nonnull Optional<Boolean> allowCommitTimestamp,
    +194                   @Nonnull FieldDescriptor field) {
    +195            this.name = name;
    +196            this.type = type;
    +197            this.length = length;
    +198            this.nonnull = nonnull.orElse(false);
    +199            this.expression = expression;
    +200            this.expressionStored = expressionStored.orElse(false);
    +201            this.allowCommitTimestamp = allowCommitTimestamp.orElse(false);
    +202            this.field = field;
    +203        }
    +204
    +205        /**
    +206         * Create a column spec for the provided field information, considering any active driver settings.
    +207         *
    +208         * @param fieldPointer Pointer to the field we should consider.
    +209         * @param settings Active set of Spanner driver settings.
    +210         * @return Spawned column corresponding to the provided field.
    +211         */
    +212        static @Nonnull ColumnSpec columnSpecForField(@Nonnull FieldPointer fieldPointer,
    +213                                                      @Nonnull SpannerDriverSettings settings) {
    +214            var columnOpts = columnOpts(fieldPointer);
    +215            var spannerOpts = spannerOpts(fieldPointer);
    +216            var fieldOpts = fieldOpts(fieldPointer);
    +217            var fieldName = resolveColumnName(fieldPointer, spannerOpts, columnOpts, settings);
    +218            var fieldType = resolveColumnType(fieldPointer, spannerOpts, columnOpts, settings);
    +219            Type innerType = fieldPointer.getField().isRepeated() ?
    +220                    fieldType.getArrayElementType() :
    +221                    fieldType;
    +222            var fieldSize = innerType.getCode() == Type.Code.STRING || innerType.getCode() == Type.Code.BYTES ?
    +223                    resolveColumnSize(fieldPointer.getField(), spannerOpts, columnOpts, settings) :
    +224                    -1;
    +225
    +226            // resolve spanner opts or defaults
    +227            var resolvedSpannerOpts = spannerOpts.orElse(SpannerFieldOptions.getDefaultInstance());
    +228            var expression = resolvedSpannerOpts.getExpression().length() > 0 ?
    +229                    Optional.of(resolvedSpannerOpts.getExpression()) : Optional.<String>empty();
    +230
    +231            var commitUpdate = false;
    +232            var protoType = fieldPointer.getField().getType();
    +233            if (fieldOpts.isPresent() && fieldOpts.get().getStampUpdate()) {
    +234                switch (protoType) {
    +235                    case STRING:
    +236                    case UINT64:
    +237                    case FIXED64:
    +238                        commitUpdate = true;
    +239                        break;
    +240
    +241                    case MESSAGE:
    +242                        if (fieldPointer.getField().getMessageType().getFullName().equals(
    +243                            Timestamp.getDescriptor().getFullName())) {
    +244                            commitUpdate = true;  // we can decode from a `Timestamp` record
    +245                        }
    +246                        break;
    +247
    +248                    default:
    +249                        // any other field type represents an illegal state
    +250                        throw new IllegalStateException(format(
    +251                            "Cannot place `commit_timestamp` in field of type '%s'", protoType.name()));
    +252                }
    +253            }
    +254
    +255            return new ColumnSpec(
    +256                fieldName,
    +257                fieldType,
    +258                fieldSize,
    +259                Optional.of(resolvedSpannerOpts.getNonnull()),
    +260                expression,
    +261                Optional.of(resolvedSpannerOpts.getStored()),
    +262                Optional.of(commitUpdate),
    +263                fieldPointer.getField()
    +264            );
    +265        }
    +266
    +267        /**
    +268         * Create a column spec for the provided model key field, considering any active driver settings.
    +269         *
    +270         * @param model Model schema for the object or key record.
    +271         * @param keyField Primary key field pre-resolved for a given Spanner table.
    +272         * @param settings Active Spanner driver settings.
    +273         * @return Spawned primary key column corresponding to the provided model key.
    +274         */
    +275        static @Nonnull ColumnSpec columnSpecForKey(@Nonnull Descriptor model,
    +276                                                    @Nonnull FieldPointer keyField,
    +277                                                    @Nonnull SpannerDriverSettings settings) {
    +278            var idField = idField(model).orElseThrow();
    +279            var keyName = resolveKeyColumn(idField, settings);
    +280            var keyType = resolveKeyType(idField);
    +281            var spannerOpts = spannerOpts(idField);
    +282            var columnOpts = columnOpts(idField);
    +283            int columnSize = -1;
    +284            if (keyType.getCode() == Type.Code.STRING ||
    +285                keyType.getCode() == Type.Code.BYTES) {
    +286                columnSize = resolveColumnSize(keyField.getField(), spannerOpts, columnOpts, settings);
    +287            }
    +288
    +289            return new ColumnSpec(
    +290                keyName,
    +291                keyType,
    +292                columnSize,
    +293                Optional.of(true),  // primary keys are always set to `NOT NULL`.
    +294                Optional.empty(),  // primary keys do not support expressions
    +295                Optional.empty(),
    +296                Optional.empty(),  // primary keys cannot be set to the commit timestamp
    +297                keyField.getField()
    +298            );
    +299        }
    +300
    +301        /**
    +302         * Render this column spec into a definition statement, suitable for use when creating a table.
    +303         *
    +304         * @return Rendered column spec statement.
    +305         */
    +306        @Override
    +307        public @Nonnull StringBuilder render() {
    +308            // prepare field statement
    +309            var buf = new StringBuilder();
    +310
    +311            // calculate field type designation first
    +312            String fieldType;
    +313            Type.Code innerType = this.field.isRepeated() ?
    +314                    this.type.getArrayElementType().getCode() :
    +315                    this.type.getCode();
    +316
    +317            String innerTypeSpec;
    +318            if (innerType == Type.Code.STRING || innerType == Type.Code.BYTES) {
    +319                innerTypeSpec = format(
    +320                    "%s(%s)",
    +321                    innerType.name(),
    +322                    this.length
    +323                );
    +324            } else {
    +325                innerTypeSpec = innerType.name();
    +326            }
    +327            if (this.type.getCode() == Type.Code.ARRAY) {
    +328                // it's a repeated field
    +329                fieldType = format(
    +330                    "ARRAY<%s>",
    +331                    innerTypeSpec
    +332                );
    +333            } else {
    +334                // it's a singular field. make sure to cover the special case for strings.
    +335                fieldType = innerTypeSpec;
    +336            }
    +337
    +338            buf.append(format(
    +339                "%s %s",
    +340                this.name,
    +341                fieldType
    +342            ));
    +343
    +344            // prepare field options
    +345            var optionsBuffer = new ArrayList<String>();
    +346
    +347            // consider NONNULL
    +348            if (this.nonnull) {
    +349                optionsBuffer.add("NOT NULL");
    +350            }
    +351
    +352            // consider expressions
    +353            if (this.expression.isPresent()) {
    +354                optionsBuffer.add(format("AS ( %s )", this.expression.get()));
    +355                if (this.expressionStored)
    +356                    optionsBuffer.add("STORED");
    +357            }
    +358
    +359            // consider options
    +360            if (this.allowCommitTimestamp) {
    +361                optionsBuffer.add("OPTIONS allow_commit_timestamp = true");
    +362            }
    +363            if (!optionsBuffer.isEmpty()) {
    +364                buf.append(" ");
    +365                buf.append(join(" ", optionsBuffer));
    +366            }
    +367            return buf;
    +368        }
    +369    }
    +370
    +371    /**
    +372     * Build properties for a generated Spanner table DDL statement, based on a given model instance as a base for
    +373     * configuring the table name (via annotations / calculated defaults) and set of typed Spanner value columns.
    +374     *
    +375     * <p>To build the actual DDL statement, fill out the builder, build it, and then ask the resulting object for the
    +376     * DDL as a string.</p>
    +377     */
    +378    @SuppressWarnings("unused")
    +379    public static final class Builder {
    +380        /** Base model on which this builder will operate. Immutable. */
    +381        final @Nonnull Descriptor model;
    +382
    +383        /** Active set of driver settings. Immutable. */
    +384        final @Nonnull SpannerDriverSettings settings;
    +385
    +386        /** Immutable: Name of the table in Spanner. */
    +387        final @Nonnull String tableName;
    +388
    +389        /** Immutable: Name of the primary key column. */
    +390        final @Nonnull String primaryKey;
    +391
    +392        /** Immutable: Generated columns in Spanner. */
    +393        final @Nonnull List<ColumnSpec> columns;
    +394
    +395        /** Mutable: Key column sort direction. */
    +396        @Nonnull SortDirection keySortDirection;
    +397
    +398        /** Mutable: List of table constraints. */
    +399        @Nonnull Optional<List<TableConstraint>> tableConstraints;
    +400
    +401        /** Mutable: Optimizer version to apply. */
    +402        @Nonnull Optional<Integer> optimizerVersion;
    +403
    +404        /** Mutable: Version retention period. */
    +405        @Nonnull Optional<String> versionRetentionPeriod;
    +406
    +407        /** Mutable: Table interleave target. */
    +408        @Nonnull Optional<InterleaveTarget> interleaveTarget;
    +409
    +410        /**
    +411         * Package-private constructor for a builder.
    +412         *
    +413         * @see SpannerGeneratedDDL#generateTableDDL(Descriptor, SpannerDriverSettings) to spawn one of
    +414         *      these from regular library or application code.
    +415         * @param model Descriptor for the model we are building against.
    +416         * @param primaryKey Primary key field name to use for this table by default.
    +417         * @param tableName Resolved table name to use for this table.
    +418         * @param defaultColumns Default set of columns to use for this table.
    +419         * @param settings Active driver settings to apply/consider.
    +420         */
    +421        Builder(@Nonnull Descriptor model,
    +422                @Nonnull String tableName,
    +423                @Nonnull String primaryKey,
    +424                @Nonnull List<ColumnSpec> defaultColumns,
    +425                @Nonnull SpannerDriverSettings settings) {
    +426            this.model = model;
    +427            this.tableName = tableName;
    +428            this.settings = settings;
    +429            this.columns = defaultColumns;
    +430            this.primaryKey = primaryKey;
    +431            this.keySortDirection = SortDirection.ASC;
    +432            this.tableConstraints = Optional.empty();
    +433            this.optimizerVersion = Optional.empty();
    +434            this.versionRetentionPeriod = Optional.empty();
    +435            this.interleaveTarget = Optional.empty();
    +436        }
    +437
    +438        /**
    +439         * Render column definition statements for a final DDL table create statement.
    +440         *
    +441         * @return Column definition statements, stacked in a buffer.
    +442         */
    +443        @Nonnull String renderColumnStatements() {
    +444            return this.columns.stream()
    +445                    .map(ColumnSpec::render)
    +446                    .collect(Collectors.joining(", "));
    +447        }
    +448
    +449        /**
    +450         * Render table-level constraint statements for a final DDL table create statement.
    +451         *
    +452         * @return Any applicable rendered table constraints.
    +453         */
    +454        @Nonnull String renderConstraintStatements() {
    +455            return this.tableConstraints.map(constraints -> constraints
    +456                    .stream()
    +457                    .map(TableConstraint::render)
    +458                    .collect(Collectors.joining(", ")))
    +459                    .orElse("");
    +460        }
    +461
    +462        /**
    +463         * Render inner statements in the CREATE TABLE DDL statement, including columns and constraints, as applicable.
    +464         * If no constraints are present, we simply return the column definitions alone.
    +465         *
    +466         * @return Rendered definitions of columns and table constraints.
    +467         */
    +468        @Nonnull String renderColumnStatementsAndConstraints() {
    +469            var columnList = renderColumnStatements();
    +470            if (this.tableConstraints.isPresent()) {
    +471                var constraints = renderConstraintStatements();
    +472                return format("%s, %s", columnList, constraints);
    +473            }
    +474            return columnList;
    +475        }
    +476
    +477        /**
    +478         * Render the primary key specification for a final DDL table create statement.
    +479         *
    +480         * @return Rendered primary key specification.
    +481         */
    +482        @Nonnull String renderPrimaryKey() {
    +483            return format(
    +484                "%s %s",
    +485                this.primaryKey,
    +486                this.keySortDirection.name()
    +487            );
    +488        }
    +489
    +490        /**
    +491         * Render the prepared DDL statement details into a statement string which can be passed to Spanner.
    +492         *
    +493         * @return Rendered DDL statement, according to local object settings.
    +494         */
    +495        @Nonnull StringBuilder renderCreateDDLStatement() {
    +496            var builder = new StringBuilder();
    +497            var buf = new ArrayList<StringBuilder>();
    +498            buf.add(new StringBuilder(format(
    +499                "CREATE TABLE %s (%s) PRIMARY KEY (%s)",
    +500                this.tableName,
    +501                this.renderColumnStatementsAndConstraints(),
    +502                this.renderPrimaryKey()
    +503            )));
    +504
    +505            // add interleave target statement, if specified
    +506            this.interleaveTarget.ifPresent(target -> buf.add(target.render()));
    +507
    +508            builder.append(join(", ", buf));
    +509            return builder;
    +510        }
    +511
    +512        /**
    +513         * Collapse the builder into an immutable DDL statement container
    +514         *
    +515         * @return Immutable DDL statement container.
    +516         */
    +517        public @Nonnull SpannerGeneratedDDL build() {
    +518            var fields = forEachField(
    +519                model,
    +520                Optional.of(onlySpannerEligibleFields(settings))
    +521            ).map((fieldPointer) ->
    +522                    ColumnSpec.columnSpecForField(fieldPointer, settings)
    +523            ).collect(Collectors.toUnmodifiableList());
    +524
    +525            return new SpannerGeneratedDDL(
    +526                tableName,
    +527                fields,
    +528                model,
    +529                renderCreateDDLStatement()
    +530            );
    +531        }
    +532
    +533        // -- Builder API: Getters -- //
    +534
    +535        /** @return Model descriptor this builder wraps. */
    +536        public @Nonnull Descriptor getModel() {
    +537            return model;
    +538        }
    +539
    +540        /** @return Active Spanner driver settings. */
    +541        public @Nonnull SpannerDriverSettings getSettings() {
    +542            return settings;
    +543        }
    +544
    +545        /** @return Generated or resolved Spanner table name. */
    +546        public @Nonnull String getTableName() {
    +547            return tableName;
    +548        }
    +549
    +550        /** @return Primary key column for this model/table. */
    +551        public @Nonnull String getPrimaryKey() {
    +552            return primaryKey;
    +553        }
    +554
    +555        /** @return Set of generated columns for this model in Spanner. */
    +556        public @Nonnull List<ColumnSpec> getColumns() {
    +557            return columns;
    +558        }
    +559
    +560        /** @return Primary key column sort direction. */
    +561        public @Nonnull SortDirection getKeySortDirection() {
    +562            return keySortDirection;
    +563        }
    +564
    +565        /** @return Set of constraints to apply to this table. */
    +566        public @Nonnull Optional<List<TableConstraint>> getTableConstraints() {
    +567            return tableConstraints;
    +568        }
    +569
    +570        /** @return Optimizer version to set for this table. */
    +571        public @Nonnull Optional<Integer> getOptimizerVersion() {
    +572            return optimizerVersion;
    +573        }
    +574
    +575        /** @return Data versioning retention period to set for this table. */
    +576        public @Nonnull Optional<String> getVersionRetentionPeriod() {
    +577            return versionRetentionPeriod;
    +578        }
    +579
    +580        /** @return Parent interleaving target for this table. */
    +581        public @Nonnull Optional<InterleaveTarget> getInterleaveTarget() {
    +582            return interleaveTarget;
    +583        }
    +584
    +585        // -- Builder API: Setters -- //
    +586
    +587        /**
    +588         * Set the sort direction for the primary key column in this table.
    +589         *
    +590         * @param keySortDirection Key column sort direction.
    +591         * @return Self, for chained calls to the builder.
    +592         */
    +593        public @Nonnull Builder setKeySortDirection(@Nonnull SortDirection keySortDirection) {
    +594            this.keySortDirection = keySortDirection;
    +595            return this;
    +596        }
    +597
    +598        /**
    +599         * Set, or clear, the set of table constraints added to this table.
    +600         *
    +601         * @param tableConstraints Desired table constraints to set or clear, as applicable.
    +602         * @return Self, for chained calls to the builder.
    +603         */
    +604        public @Nonnull Builder setTableConstraints(@Nonnull Optional<List<TableConstraint>> tableConstraints) {
    +605            this.tableConstraints = tableConstraints;
    +606            return this;
    +607        }
    +608
    +609        /**
    +610         * Set, or clear, the optimizer version to apply when creating this table.
    +611         *
    +612         * @param optimizerVersion Desired optimizer version to apply, as applicable.
    +613         * @return Self, for chained calls to the builder.
    +614         */
    +615        public @Nonnull Builder setOptimizerVersion(@Nonnull Optional<Integer> optimizerVersion) {
    +616            this.optimizerVersion = optimizerVersion;
    +617            return this;
    +618        }
    +619
    +620        /**
    +621         * Set, or clear, the data versioning retention period for this table.
    +622         *
    +623         * @param versionRetentionPeriod Desired data versioning retention period, as applicable.
    +624         * @return Self, for chained calls to the builder.
    +625         */
    +626        public @Nonnull Builder setVersionRetentionPeriod(@Nonnull Optional<String> versionRetentionPeriod) {
    +627            this.versionRetentionPeriod = versionRetentionPeriod;
    +628            return this;
    +629        }
    +630
    +631        /**
    +632         * Set, or clear, the parent interleave target for this table.
    +633         *
    +634         * @param interleaveTarget Desired parent interleave target, as applicable.
    +635         * @return Self, for chained calls to the builder.
    +636         */
    +637        public @Nonnull Builder setInterleaveTarget(@Nonnull Optional<InterleaveTarget> interleaveTarget) {
    +638            this.interleaveTarget = interleaveTarget;
    +639            return this;
    +640        }
    +641    }
    +642
    +643    /** Model that relates to this generated statement. */
    +644    private final @Nonnull Descriptor model;
    +645
    +646    /** Resolved name of the table. */
    +647    private final @Nonnull String tableName;
    +648
    +649    /** Set of generated columns determined to be part of this table. */
    +650    private final @Nonnull List<ColumnSpec> columns;
    +651
    +652    /** Holds the generated query in a string buffer. */
    +653    private final @Nonnull StringBuilder generatedStatement;
    +654
    +655    /**
    +656     * Private constructor.
    +657     *
    +658     * @param tableName Name of the generated table.
    +659     * @param columns Generated set of columns.
    +660     * @param model Model this table corresponds to.
    +661     * @param generatedStatement Rendered DDL statement.
    +662     */
    +663    private SpannerGeneratedDDL(@Nonnull String tableName,
    +664                                @Nonnull List<ColumnSpec> columns,
    +665                                @Nonnull Descriptor model,
    +666                                @Nonnull StringBuilder generatedStatement) {
    +667        this.tableName = tableName;
    +668        this.columns = columns;
    +669        this.generatedStatement = generatedStatement;
    +670        this.model = model;
    +671    }
    +672
    +673    /**
    +674     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +675     * that model's properties. This method variant operates from a full model instance.
    +676     *
    +677     * <p>This method offers no ability to control driver settings. See below if you need alternatives.</p>
    +678     *
    +679     * @see #generateTableDDL(Message, Optional) For control over driver settings, optionally.
    +680     * @param instance Model instance to generate a table statement for.
    +681     * @return Generated DDL statement object.
    +682     */
    +683    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(@Nonnull Message instance) {
    +684        return generateTableDDL(instance, Optional.of(SpannerDriverSettings.DEFAULTS));
    +685    }
    +686
    +687    /**
    +688     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +689     * that model's properties. This method variant operates from a full model instance.
    +690     *
    +691     * @param instance Model instance to generate a table statement for.
    +692     * @param settings Settings to employ for the driver. These must align at runtime.
    +693     * @return Generated DDL statement object.
    +694     */
    +695    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(
    +696            @Nonnull Message instance,
    +697            @Nonnull Optional<SpannerDriverSettings> settings) {
    +698        return generateTableDDL(
    +699            instance.getDescriptorForType(),
    +700            settings.orElse(SpannerDriverSettings.DEFAULTS)
    +701        );
    +702    }
    +703
    +704    /**
    +705     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +706     * that model's properties.
    +707     *
    +708     * @param model Model schema to generate a table statement for.
    +709     * @return Generated DDL statement object.
    +710     */
    +711    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(
    +712            @Nonnull Descriptor model,
    +713            @Nonnull SpannerDriverSettings settings) {
    +714        return new SpannerGeneratedDDL.Builder(
    +715            model,
    +716            resolveTableName(model),
    +717            resolveKeyColumn(idField(model).orElseThrow(), settings),
    +718            resolveDefaultColumns(model, settings),
    +719            settings
    +720        );
    +721    }
    +722
    +723    /**
    +724     * Resolve the default calculated set of Spanner columns for a given model structure.
    +725     *
    +726     * @param model Model to traverse and generate columns for.
    +727     * @return Set of generated and type-resolved columns.
    +728     */
    +729    public static @Nonnull List<ColumnSpec> resolveDefaultColumns(@Nonnull Descriptor model,
    +730                                                                  @Nonnull SpannerDriverSettings settings) {
    +731        var keyField = keyField(model).orElseThrow();
    +732        var fieldSet = new LinkedList<ColumnSpec>();
    +733
    +734        // first up: generate the column which implements the model's primary key
    +735        fieldSet.add(ColumnSpec.columnSpecForKey(
    +736            model,
    +737            keyField,
    +738            settings
    +739        ));
    +740
    +741        // next: generate all remaining data columns
    +742        forEachField(
    +743            model,
    +744            Optional.of(onlySpannerEligibleFields(settings))
    +745        ).filter((fieldPointer) ->
    +746            // filter out key fields: we'll handle those separately
    +747            !keyField.getField().getFullName().equals(fieldPointer.getField().getFullName())
    +748        ).map((fieldPointer) ->
    +749            ColumnSpec.columnSpecForField(fieldPointer, settings)
    +750        ).forEach(fieldSet::add);
    +751
    +752        return Collections.unmodifiableList(fieldSet);
    +753    }
    +754
    +755    // -- Accessors -- //
    +756
    +757    /** @return Model for which this object generates a table create statement. */
    +758    public @Nonnull Descriptor getModel() {
    +759        return model;
    +760    }
    +761
    +762    /** @return Resolved name of the table to be created. */
    +763    public @Nonnull String getTableName() {
    +764        return tableName;
    +765    }
    +766
    +767    /** @return Resolved set of Spanner columns. */
    +768    public @Nonnull List<ColumnSpec> getColumns() {
    +769        return columns;
    +770    }
    +771
    +772    /** @return Rendered generated DDL statement. */
    +773    public @Nonnull StringBuilder getGeneratedStatement() {
    +774        return generatedStatement;
    +775    }
    +776
    +777    @Override
    +778    public String toString() {
    +779        return "SpannerDDL{" +
    +780            "model=" + model.getFullName() +
    +781            ", tableName='" + tableName + '\'' +
    +782            ", statement=\"" + generatedStatement.toString() +
    +783        "\"}";
    +784    }
    +785}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.html new file mode 100644 index 000000000..df55f2dd9 --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerGeneratedDDL.html @@ -0,0 +1,859 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import com.google.cloud.spanner.Type;
    +016import com.google.protobuf.Message;
    +017import com.google.protobuf.Timestamp;
    +018import tools.elide.core.SpannerFieldOptions;
    +019
    +020import javax.annotation.Nonnull;
    +021import javax.annotation.concurrent.Immutable;
    +022import javax.annotation.concurrent.ThreadSafe;
    +023import java.util.*;
    +024import java.util.stream.Collectors;
    +025
    +026import static com.google.protobuf.Descriptors.Descriptor;
    +027import static com.google.protobuf.Descriptors.FieldDescriptor;
    +028import static gust.backend.driver.spanner.SpannerUtil.*;
    +029import static gust.backend.model.ModelMetadata.*;
    +030import static java.lang.String.format;
    +031import static java.lang.String.join;
    +032
    +033
    +034/** Container for generated schema-driven Spanner DDL. */
    +035@Immutable
    +036@ThreadSafe
    +037public final class SpannerGeneratedDDL {
    +038    /** Represents a DDL statement structure in code that can be rendered down to a string. */
    +039    public interface RenderableStatement {
    +040        /**
    +041         * Render this statement into a String buffer.
    +042         *
    +043         * @return Rendered statement.
    +044         */
    +045        @Nonnull StringBuilder render();
    +046    }
    +047
    +048    /** Sort direction settings which can apply to columns. */
    +049    public enum SortDirection {
    +050        /** Sort values in the column in ascending order. This is the default value. */
    +051        ASC,
    +052
    +053        /** Sort values in the column in descending order. */
    +054        DESC
    +055    }
    +056
    +057    /** Specifies options for reference action propagation (i.e. on-delete or on-update). */
    +058    public enum PropagatedAction {
    +059        /** Take no action. This is the default value. */
    +060        NO_ACTION,
    +061
    +062        /** Cascade changes on delete or update. */
    +063        CASCADE
    +064    }
    +065
    +066    /** Specifies a generic table constraint to include in a DDL statement. */
    +067    public static final class TableConstraint implements RenderableStatement {
    +068        final @Nonnull String name;
    +069        final @Nonnull String expression;
    +070
    +071        /**
    +072         * Private constructor for a table constraint specification.
    +073         *
    +074         * @param name Name of the table constraint.
    +075         * @param expression Expression to use as a constraint.
    +076         */
    +077        private TableConstraint(@Nonnull String name, @Nonnull String expression) {
    +078            this.name = name;
    +079            this.expression = expression;
    +080        }
    +081
    +082        /**
    +083         * Spawn a table constraint at the provided name, enforcing the provided expression.
    +084         *
    +085         * @param name Name of the constraint to enclose in the DDL statement.
    +086         * @param expression Expression to enforce as a table constraint.
    +087         * @return Table constraint specification.
    +088         */
    +089        public static @Nonnull TableConstraint named(@Nonnull String name,
    +090                                                     @Nonnull String expression) {
    +091            return new TableConstraint(name, expression);
    +092        }
    +093
    +094        /** @inheritDoc */
    +095        @Override
    +096        public @Nonnull StringBuilder render() {
    +097            return (new StringBuilder()).append(format(
    +098                "CONSTRAINT %s CHECK ( %s )",
    +099                this.name,
    +100                this.expression
    +101            ));
    +102        }
    +103    }
    +104
    +105    /** Specify a parent table against which this table is interleaved. */
    +106    public static final class InterleaveTarget implements RenderableStatement {
    +107        final @Nonnull String parent;
    +108        final @Nonnull Optional<PropagatedAction> action;
    +109
    +110        /**
    +111         * Private constructor for an interleave target for a Spanner table.
    +112         *
    +113         * @param parent Parent table where we should interleave this table.
    +114         * @param action Action to take, if any, when deletes or changes happen in the parent table.
    +115         */
    +116        private InterleaveTarget(@Nonnull String parent,
    +117                                 @Nonnull Optional<PropagatedAction> action) {
    +118            this.parent = parent;
    +119            this.action = action;
    +120        }
    +121
    +122        /**
    +123         * Generate an interleave target specification for the provided parent table name.
    +124         *
    +125         * @see #forParent(String, Optional) To pass a propagation action.
    +126         * @param parent Parent table where we should interleave a given table.
    +127         * @return Interleave target specification.
    +128         */
    +129        public static @Nonnull InterleaveTarget forParent(@Nonnull String parent) {
    +130            return forParent(parent, Optional.empty());
    +131        }
    +132
    +133        /**
    +134         * Generate an interleave target specification for the provided parent table name, optionally applying the
    +135         * provided propagation action.
    +136         *
    +137         * @param parent Parent table where we should interleave a given table.
    +138         * @param action Action to take, if any, when parent rows change that should affect the child table.
    +139         * @return Interleave target specification.
    +140         */
    +141        public static @Nonnull InterleaveTarget forParent(@Nonnull String parent,
    +142                                                          @Nonnull Optional<PropagatedAction> action) {
    +143            return new InterleaveTarget(parent, action);
    +144        }
    +145
    +146        /** @inheritDoc */
    +147        @Override
    +148        public @Nonnull StringBuilder render() {
    +149            var buf = new StringBuilder(format(
    +150                "INTERLEAVE IN PARENT %s",
    +151                this.parent
    +152            ));
    +153
    +154            if (this.action.isPresent()) {
    +155                buf.append(" ");
    +156                buf.append(format(
    +157                    "ON DELETE %s",
    +158                    this.action.get().name()
    +159                ));
    +160            }
    +161
    +162            return buf;
    +163        }
    +164    }
    +165
    +166    /** Specifies an individual field as part of a DDL statement. */
    +167    private static final class ColumnSpec implements RenderableStatement {
    +168        final @Nonnull String name;
    +169        final @Nonnull Type type;
    +170        final @Nonnull Integer length;
    +171        final @Nonnull FieldDescriptor field;
    +172        final @Nonnull Boolean nonnull;
    +173        final @Nonnull Optional<String> expression;
    +174        final @Nonnull Boolean expressionStored;
    +175        final @Nonnull Boolean allowCommitTimestamp;
    +176
    +177        /**
    +178         * Private constructor.
    +179         *
    +180         * @param name Column name in Spanner.
    +181         * @param type Type specification in Spanner.
    +182         * @param length Length for string fields, or `0`.
    +183         * @param nonnull Whether to mark the field as non-null.
    +184         * @param allowCommitTimestamp Whether to fill this column with the commit timestamp.
    +185         * @param field Model field that spawned this column specification.
    +186         */
    +187        ColumnSpec(@Nonnull String name,
    +188                   @Nonnull Type type,
    +189                   @Nonnull Integer length,
    +190                   @Nonnull Optional<Boolean> nonnull,
    +191                   @Nonnull Optional<String> expression,
    +192                   @Nonnull Optional<Boolean> expressionStored,
    +193                   @Nonnull Optional<Boolean> allowCommitTimestamp,
    +194                   @Nonnull FieldDescriptor field) {
    +195            this.name = name;
    +196            this.type = type;
    +197            this.length = length;
    +198            this.nonnull = nonnull.orElse(false);
    +199            this.expression = expression;
    +200            this.expressionStored = expressionStored.orElse(false);
    +201            this.allowCommitTimestamp = allowCommitTimestamp.orElse(false);
    +202            this.field = field;
    +203        }
    +204
    +205        /**
    +206         * Create a column spec for the provided field information, considering any active driver settings.
    +207         *
    +208         * @param fieldPointer Pointer to the field we should consider.
    +209         * @param settings Active set of Spanner driver settings.
    +210         * @return Spawned column corresponding to the provided field.
    +211         */
    +212        static @Nonnull ColumnSpec columnSpecForField(@Nonnull FieldPointer fieldPointer,
    +213                                                      @Nonnull SpannerDriverSettings settings) {
    +214            var columnOpts = columnOpts(fieldPointer);
    +215            var spannerOpts = spannerOpts(fieldPointer);
    +216            var fieldOpts = fieldOpts(fieldPointer);
    +217            var fieldName = resolveColumnName(fieldPointer, spannerOpts, columnOpts, settings);
    +218            var fieldType = resolveColumnType(fieldPointer, spannerOpts, columnOpts, settings);
    +219            Type innerType = fieldPointer.getField().isRepeated() ?
    +220                    fieldType.getArrayElementType() :
    +221                    fieldType;
    +222            var fieldSize = innerType.getCode() == Type.Code.STRING || innerType.getCode() == Type.Code.BYTES ?
    +223                    resolveColumnSize(fieldPointer.getField(), spannerOpts, columnOpts, settings) :
    +224                    -1;
    +225
    +226            // resolve spanner opts or defaults
    +227            var resolvedSpannerOpts = spannerOpts.orElse(SpannerFieldOptions.getDefaultInstance());
    +228            var expression = resolvedSpannerOpts.getExpression().length() > 0 ?
    +229                    Optional.of(resolvedSpannerOpts.getExpression()) : Optional.<String>empty();
    +230
    +231            var commitUpdate = false;
    +232            var protoType = fieldPointer.getField().getType();
    +233            if (fieldOpts.isPresent() && fieldOpts.get().getStampUpdate()) {
    +234                switch (protoType) {
    +235                    case STRING:
    +236                    case UINT64:
    +237                    case FIXED64:
    +238                        commitUpdate = true;
    +239                        break;
    +240
    +241                    case MESSAGE:
    +242                        if (fieldPointer.getField().getMessageType().getFullName().equals(
    +243                            Timestamp.getDescriptor().getFullName())) {
    +244                            commitUpdate = true;  // we can decode from a `Timestamp` record
    +245                        }
    +246                        break;
    +247
    +248                    default:
    +249                        // any other field type represents an illegal state
    +250                        throw new IllegalStateException(format(
    +251                            "Cannot place `commit_timestamp` in field of type '%s'", protoType.name()));
    +252                }
    +253            }
    +254
    +255            return new ColumnSpec(
    +256                fieldName,
    +257                fieldType,
    +258                fieldSize,
    +259                Optional.of(resolvedSpannerOpts.getNonnull()),
    +260                expression,
    +261                Optional.of(resolvedSpannerOpts.getStored()),
    +262                Optional.of(commitUpdate),
    +263                fieldPointer.getField()
    +264            );
    +265        }
    +266
    +267        /**
    +268         * Create a column spec for the provided model key field, considering any active driver settings.
    +269         *
    +270         * @param model Model schema for the object or key record.
    +271         * @param keyField Primary key field pre-resolved for a given Spanner table.
    +272         * @param settings Active Spanner driver settings.
    +273         * @return Spawned primary key column corresponding to the provided model key.
    +274         */
    +275        static @Nonnull ColumnSpec columnSpecForKey(@Nonnull Descriptor model,
    +276                                                    @Nonnull FieldPointer keyField,
    +277                                                    @Nonnull SpannerDriverSettings settings) {
    +278            var idField = idField(model).orElseThrow();
    +279            var keyName = resolveKeyColumn(idField, settings);
    +280            var keyType = resolveKeyType(idField);
    +281            var spannerOpts = spannerOpts(idField);
    +282            var columnOpts = columnOpts(idField);
    +283            int columnSize = -1;
    +284            if (keyType.getCode() == Type.Code.STRING ||
    +285                keyType.getCode() == Type.Code.BYTES) {
    +286                columnSize = resolveColumnSize(keyField.getField(), spannerOpts, columnOpts, settings);
    +287            }
    +288
    +289            return new ColumnSpec(
    +290                keyName,
    +291                keyType,
    +292                columnSize,
    +293                Optional.of(true),  // primary keys are always set to `NOT NULL`.
    +294                Optional.empty(),  // primary keys do not support expressions
    +295                Optional.empty(),
    +296                Optional.empty(),  // primary keys cannot be set to the commit timestamp
    +297                keyField.getField()
    +298            );
    +299        }
    +300
    +301        /**
    +302         * Render this column spec into a definition statement, suitable for use when creating a table.
    +303         *
    +304         * @return Rendered column spec statement.
    +305         */
    +306        @Override
    +307        public @Nonnull StringBuilder render() {
    +308            // prepare field statement
    +309            var buf = new StringBuilder();
    +310
    +311            // calculate field type designation first
    +312            String fieldType;
    +313            Type.Code innerType = this.field.isRepeated() ?
    +314                    this.type.getArrayElementType().getCode() :
    +315                    this.type.getCode();
    +316
    +317            String innerTypeSpec;
    +318            if (innerType == Type.Code.STRING || innerType == Type.Code.BYTES) {
    +319                innerTypeSpec = format(
    +320                    "%s(%s)",
    +321                    innerType.name(),
    +322                    this.length
    +323                );
    +324            } else {
    +325                innerTypeSpec = innerType.name();
    +326            }
    +327            if (this.type.getCode() == Type.Code.ARRAY) {
    +328                // it's a repeated field
    +329                fieldType = format(
    +330                    "ARRAY<%s>",
    +331                    innerTypeSpec
    +332                );
    +333            } else {
    +334                // it's a singular field. make sure to cover the special case for strings.
    +335                fieldType = innerTypeSpec;
    +336            }
    +337
    +338            buf.append(format(
    +339                "%s %s",
    +340                this.name,
    +341                fieldType
    +342            ));
    +343
    +344            // prepare field options
    +345            var optionsBuffer = new ArrayList<String>();
    +346
    +347            // consider NONNULL
    +348            if (this.nonnull) {
    +349                optionsBuffer.add("NOT NULL");
    +350            }
    +351
    +352            // consider expressions
    +353            if (this.expression.isPresent()) {
    +354                optionsBuffer.add(format("AS ( %s )", this.expression.get()));
    +355                if (this.expressionStored)
    +356                    optionsBuffer.add("STORED");
    +357            }
    +358
    +359            // consider options
    +360            if (this.allowCommitTimestamp) {
    +361                optionsBuffer.add("OPTIONS allow_commit_timestamp = true");
    +362            }
    +363            if (!optionsBuffer.isEmpty()) {
    +364                buf.append(" ");
    +365                buf.append(join(" ", optionsBuffer));
    +366            }
    +367            return buf;
    +368        }
    +369    }
    +370
    +371    /**
    +372     * Build properties for a generated Spanner table DDL statement, based on a given model instance as a base for
    +373     * configuring the table name (via annotations / calculated defaults) and set of typed Spanner value columns.
    +374     *
    +375     * <p>To build the actual DDL statement, fill out the builder, build it, and then ask the resulting object for the
    +376     * DDL as a string.</p>
    +377     */
    +378    @SuppressWarnings("unused")
    +379    public static final class Builder {
    +380        /** Base model on which this builder will operate. Immutable. */
    +381        final @Nonnull Descriptor model;
    +382
    +383        /** Active set of driver settings. Immutable. */
    +384        final @Nonnull SpannerDriverSettings settings;
    +385
    +386        /** Immutable: Name of the table in Spanner. */
    +387        final @Nonnull String tableName;
    +388
    +389        /** Immutable: Name of the primary key column. */
    +390        final @Nonnull String primaryKey;
    +391
    +392        /** Immutable: Generated columns in Spanner. */
    +393        final @Nonnull List<ColumnSpec> columns;
    +394
    +395        /** Mutable: Key column sort direction. */
    +396        @Nonnull SortDirection keySortDirection;
    +397
    +398        /** Mutable: List of table constraints. */
    +399        @Nonnull Optional<List<TableConstraint>> tableConstraints;
    +400
    +401        /** Mutable: Optimizer version to apply. */
    +402        @Nonnull Optional<Integer> optimizerVersion;
    +403
    +404        /** Mutable: Version retention period. */
    +405        @Nonnull Optional<String> versionRetentionPeriod;
    +406
    +407        /** Mutable: Table interleave target. */
    +408        @Nonnull Optional<InterleaveTarget> interleaveTarget;
    +409
    +410        /**
    +411         * Package-private constructor for a builder.
    +412         *
    +413         * @see SpannerGeneratedDDL#generateTableDDL(Descriptor, SpannerDriverSettings) to spawn one of
    +414         *      these from regular library or application code.
    +415         * @param model Descriptor for the model we are building against.
    +416         * @param primaryKey Primary key field name to use for this table by default.
    +417         * @param tableName Resolved table name to use for this table.
    +418         * @param defaultColumns Default set of columns to use for this table.
    +419         * @param settings Active driver settings to apply/consider.
    +420         */
    +421        Builder(@Nonnull Descriptor model,
    +422                @Nonnull String tableName,
    +423                @Nonnull String primaryKey,
    +424                @Nonnull List<ColumnSpec> defaultColumns,
    +425                @Nonnull SpannerDriverSettings settings) {
    +426            this.model = model;
    +427            this.tableName = tableName;
    +428            this.settings = settings;
    +429            this.columns = defaultColumns;
    +430            this.primaryKey = primaryKey;
    +431            this.keySortDirection = SortDirection.ASC;
    +432            this.tableConstraints = Optional.empty();
    +433            this.optimizerVersion = Optional.empty();
    +434            this.versionRetentionPeriod = Optional.empty();
    +435            this.interleaveTarget = Optional.empty();
    +436        }
    +437
    +438        /**
    +439         * Render column definition statements for a final DDL table create statement.
    +440         *
    +441         * @return Column definition statements, stacked in a buffer.
    +442         */
    +443        @Nonnull String renderColumnStatements() {
    +444            return this.columns.stream()
    +445                    .map(ColumnSpec::render)
    +446                    .collect(Collectors.joining(", "));
    +447        }
    +448
    +449        /**
    +450         * Render table-level constraint statements for a final DDL table create statement.
    +451         *
    +452         * @return Any applicable rendered table constraints.
    +453         */
    +454        @Nonnull String renderConstraintStatements() {
    +455            return this.tableConstraints.map(constraints -> constraints
    +456                    .stream()
    +457                    .map(TableConstraint::render)
    +458                    .collect(Collectors.joining(", ")))
    +459                    .orElse("");
    +460        }
    +461
    +462        /**
    +463         * Render inner statements in the CREATE TABLE DDL statement, including columns and constraints, as applicable.
    +464         * If no constraints are present, we simply return the column definitions alone.
    +465         *
    +466         * @return Rendered definitions of columns and table constraints.
    +467         */
    +468        @Nonnull String renderColumnStatementsAndConstraints() {
    +469            var columnList = renderColumnStatements();
    +470            if (this.tableConstraints.isPresent()) {
    +471                var constraints = renderConstraintStatements();
    +472                return format("%s, %s", columnList, constraints);
    +473            }
    +474            return columnList;
    +475        }
    +476
    +477        /**
    +478         * Render the primary key specification for a final DDL table create statement.
    +479         *
    +480         * @return Rendered primary key specification.
    +481         */
    +482        @Nonnull String renderPrimaryKey() {
    +483            return format(
    +484                "%s %s",
    +485                this.primaryKey,
    +486                this.keySortDirection.name()
    +487            );
    +488        }
    +489
    +490        /**
    +491         * Render the prepared DDL statement details into a statement string which can be passed to Spanner.
    +492         *
    +493         * @return Rendered DDL statement, according to local object settings.
    +494         */
    +495        @Nonnull StringBuilder renderCreateDDLStatement() {
    +496            var builder = new StringBuilder();
    +497            var buf = new ArrayList<StringBuilder>();
    +498            buf.add(new StringBuilder(format(
    +499                "CREATE TABLE %s (%s) PRIMARY KEY (%s)",
    +500                this.tableName,
    +501                this.renderColumnStatementsAndConstraints(),
    +502                this.renderPrimaryKey()
    +503            )));
    +504
    +505            // add interleave target statement, if specified
    +506            this.interleaveTarget.ifPresent(target -> buf.add(target.render()));
    +507
    +508            builder.append(join(", ", buf));
    +509            return builder;
    +510        }
    +511
    +512        /**
    +513         * Collapse the builder into an immutable DDL statement container
    +514         *
    +515         * @return Immutable DDL statement container.
    +516         */
    +517        public @Nonnull SpannerGeneratedDDL build() {
    +518            var fields = forEachField(
    +519                model,
    +520                Optional.of(onlySpannerEligibleFields(settings))
    +521            ).map((fieldPointer) ->
    +522                    ColumnSpec.columnSpecForField(fieldPointer, settings)
    +523            ).collect(Collectors.toUnmodifiableList());
    +524
    +525            return new SpannerGeneratedDDL(
    +526                tableName,
    +527                fields,
    +528                model,
    +529                renderCreateDDLStatement()
    +530            );
    +531        }
    +532
    +533        // -- Builder API: Getters -- //
    +534
    +535        /** @return Model descriptor this builder wraps. */
    +536        public @Nonnull Descriptor getModel() {
    +537            return model;
    +538        }
    +539
    +540        /** @return Active Spanner driver settings. */
    +541        public @Nonnull SpannerDriverSettings getSettings() {
    +542            return settings;
    +543        }
    +544
    +545        /** @return Generated or resolved Spanner table name. */
    +546        public @Nonnull String getTableName() {
    +547            return tableName;
    +548        }
    +549
    +550        /** @return Primary key column for this model/table. */
    +551        public @Nonnull String getPrimaryKey() {
    +552            return primaryKey;
    +553        }
    +554
    +555        /** @return Set of generated columns for this model in Spanner. */
    +556        public @Nonnull List<ColumnSpec> getColumns() {
    +557            return columns;
    +558        }
    +559
    +560        /** @return Primary key column sort direction. */
    +561        public @Nonnull SortDirection getKeySortDirection() {
    +562            return keySortDirection;
    +563        }
    +564
    +565        /** @return Set of constraints to apply to this table. */
    +566        public @Nonnull Optional<List<TableConstraint>> getTableConstraints() {
    +567            return tableConstraints;
    +568        }
    +569
    +570        /** @return Optimizer version to set for this table. */
    +571        public @Nonnull Optional<Integer> getOptimizerVersion() {
    +572            return optimizerVersion;
    +573        }
    +574
    +575        /** @return Data versioning retention period to set for this table. */
    +576        public @Nonnull Optional<String> getVersionRetentionPeriod() {
    +577            return versionRetentionPeriod;
    +578        }
    +579
    +580        /** @return Parent interleaving target for this table. */
    +581        public @Nonnull Optional<InterleaveTarget> getInterleaveTarget() {
    +582            return interleaveTarget;
    +583        }
    +584
    +585        // -- Builder API: Setters -- //
    +586
    +587        /**
    +588         * Set the sort direction for the primary key column in this table.
    +589         *
    +590         * @param keySortDirection Key column sort direction.
    +591         * @return Self, for chained calls to the builder.
    +592         */
    +593        public @Nonnull Builder setKeySortDirection(@Nonnull SortDirection keySortDirection) {
    +594            this.keySortDirection = keySortDirection;
    +595            return this;
    +596        }
    +597
    +598        /**
    +599         * Set, or clear, the set of table constraints added to this table.
    +600         *
    +601         * @param tableConstraints Desired table constraints to set or clear, as applicable.
    +602         * @return Self, for chained calls to the builder.
    +603         */
    +604        public @Nonnull Builder setTableConstraints(@Nonnull Optional<List<TableConstraint>> tableConstraints) {
    +605            this.tableConstraints = tableConstraints;
    +606            return this;
    +607        }
    +608
    +609        /**
    +610         * Set, or clear, the optimizer version to apply when creating this table.
    +611         *
    +612         * @param optimizerVersion Desired optimizer version to apply, as applicable.
    +613         * @return Self, for chained calls to the builder.
    +614         */
    +615        public @Nonnull Builder setOptimizerVersion(@Nonnull Optional<Integer> optimizerVersion) {
    +616            this.optimizerVersion = optimizerVersion;
    +617            return this;
    +618        }
    +619
    +620        /**
    +621         * Set, or clear, the data versioning retention period for this table.
    +622         *
    +623         * @param versionRetentionPeriod Desired data versioning retention period, as applicable.
    +624         * @return Self, for chained calls to the builder.
    +625         */
    +626        public @Nonnull Builder setVersionRetentionPeriod(@Nonnull Optional<String> versionRetentionPeriod) {
    +627            this.versionRetentionPeriod = versionRetentionPeriod;
    +628            return this;
    +629        }
    +630
    +631        /**
    +632         * Set, or clear, the parent interleave target for this table.
    +633         *
    +634         * @param interleaveTarget Desired parent interleave target, as applicable.
    +635         * @return Self, for chained calls to the builder.
    +636         */
    +637        public @Nonnull Builder setInterleaveTarget(@Nonnull Optional<InterleaveTarget> interleaveTarget) {
    +638            this.interleaveTarget = interleaveTarget;
    +639            return this;
    +640        }
    +641    }
    +642
    +643    /** Model that relates to this generated statement. */
    +644    private final @Nonnull Descriptor model;
    +645
    +646    /** Resolved name of the table. */
    +647    private final @Nonnull String tableName;
    +648
    +649    /** Set of generated columns determined to be part of this table. */
    +650    private final @Nonnull List<ColumnSpec> columns;
    +651
    +652    /** Holds the generated query in a string buffer. */
    +653    private final @Nonnull StringBuilder generatedStatement;
    +654
    +655    /**
    +656     * Private constructor.
    +657     *
    +658     * @param tableName Name of the generated table.
    +659     * @param columns Generated set of columns.
    +660     * @param model Model this table corresponds to.
    +661     * @param generatedStatement Rendered DDL statement.
    +662     */
    +663    private SpannerGeneratedDDL(@Nonnull String tableName,
    +664                                @Nonnull List<ColumnSpec> columns,
    +665                                @Nonnull Descriptor model,
    +666                                @Nonnull StringBuilder generatedStatement) {
    +667        this.tableName = tableName;
    +668        this.columns = columns;
    +669        this.generatedStatement = generatedStatement;
    +670        this.model = model;
    +671    }
    +672
    +673    /**
    +674     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +675     * that model's properties. This method variant operates from a full model instance.
    +676     *
    +677     * <p>This method offers no ability to control driver settings. See below if you need alternatives.</p>
    +678     *
    +679     * @see #generateTableDDL(Message, Optional) For control over driver settings, optionally.
    +680     * @param instance Model instance to generate a table statement for.
    +681     * @return Generated DDL statement object.
    +682     */
    +683    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(@Nonnull Message instance) {
    +684        return generateTableDDL(instance, Optional.of(SpannerDriverSettings.DEFAULTS));
    +685    }
    +686
    +687    /**
    +688     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +689     * that model's properties. This method variant operates from a full model instance.
    +690     *
    +691     * @param instance Model instance to generate a table statement for.
    +692     * @param settings Settings to employ for the driver. These must align at runtime.
    +693     * @return Generated DDL statement object.
    +694     */
    +695    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(
    +696            @Nonnull Message instance,
    +697            @Nonnull Optional<SpannerDriverSettings> settings) {
    +698        return generateTableDDL(
    +699            instance.getDescriptorForType(),
    +700            settings.orElse(SpannerDriverSettings.DEFAULTS)
    +701        );
    +702    }
    +703
    +704    /**
    +705     * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing
    +706     * that model's properties.
    +707     *
    +708     * @param model Model schema to generate a table statement for.
    +709     * @return Generated DDL statement object.
    +710     */
    +711    public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(
    +712            @Nonnull Descriptor model,
    +713            @Nonnull SpannerDriverSettings settings) {
    +714        return new SpannerGeneratedDDL.Builder(
    +715            model,
    +716            resolveTableName(model),
    +717            resolveKeyColumn(idField(model).orElseThrow(), settings),
    +718            resolveDefaultColumns(model, settings),
    +719            settings
    +720        );
    +721    }
    +722
    +723    /**
    +724     * Resolve the default calculated set of Spanner columns for a given model structure.
    +725     *
    +726     * @param model Model to traverse and generate columns for.
    +727     * @return Set of generated and type-resolved columns.
    +728     */
    +729    public static @Nonnull List<ColumnSpec> resolveDefaultColumns(@Nonnull Descriptor model,
    +730                                                                  @Nonnull SpannerDriverSettings settings) {
    +731        var keyField = keyField(model).orElseThrow();
    +732        var fieldSet = new LinkedList<ColumnSpec>();
    +733
    +734        // first up: generate the column which implements the model's primary key
    +735        fieldSet.add(ColumnSpec.columnSpecForKey(
    +736            model,
    +737            keyField,
    +738            settings
    +739        ));
    +740
    +741        // next: generate all remaining data columns
    +742        forEachField(
    +743            model,
    +744            Optional.of(onlySpannerEligibleFields(settings))
    +745        ).filter((fieldPointer) ->
    +746            // filter out key fields: we'll handle those separately
    +747            !keyField.getField().getFullName().equals(fieldPointer.getField().getFullName())
    +748        ).map((fieldPointer) ->
    +749            ColumnSpec.columnSpecForField(fieldPointer, settings)
    +750        ).forEach(fieldSet::add);
    +751
    +752        return Collections.unmodifiableList(fieldSet);
    +753    }
    +754
    +755    // -- Accessors -- //
    +756
    +757    /** @return Model for which this object generates a table create statement. */
    +758    public @Nonnull Descriptor getModel() {
    +759        return model;
    +760    }
    +761
    +762    /** @return Resolved name of the table to be created. */
    +763    public @Nonnull String getTableName() {
    +764        return tableName;
    +765    }
    +766
    +767    /** @return Resolved set of Spanner columns. */
    +768    public @Nonnull List<ColumnSpec> getColumns() {
    +769        return columns;
    +770    }
    +771
    +772    /** @return Rendered generated DDL statement. */
    +773    public @Nonnull StringBuilder getGeneratedStatement() {
    +774        return generatedStatement;
    +775    }
    +776
    +777    @Override
    +778    public String toString() {
    +779        return "SpannerDDL{" +
    +780            "model=" + model.getFullName() +
    +781            ", tableName='" + tableName + '\'' +
    +782            ", statement=\"" + generatedStatement.toString() +
    +783        "\"}";
    +784    }
    +785}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerManager.Builder.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerManager.Builder.html new file mode 100644 index 000000000..460625de9 --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerManager.Builder.html @@ -0,0 +1,533 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import com.google.cloud.spanner.DatabaseId;
    +016import com.google.cloud.spanner.SpannerOptions;
    +017import com.google.common.util.concurrent.ListeningScheduledExecutorService;
    +018import com.google.protobuf.Empty;
    +019import com.google.protobuf.Message;
    +020import gust.backend.model.CacheDriver;
    +021import gust.backend.model.DatabaseManager;
    +022import gust.backend.runtime.Logging;
    +023import io.micronaut.context.annotation.Factory;
    +024import io.micronaut.runtime.context.scope.Refreshable;
    +025import org.slf4j.Logger;
    +026
    +027import javax.annotation.Nonnull;
    +028import javax.annotation.Nullable;
    +029import javax.annotation.concurrent.Immutable;
    +030import javax.annotation.concurrent.ThreadSafe;
    +031import java.io.Closeable;
    +032import java.lang.ref.WeakReference;
    +033import java.util.*;
    +034import java.util.concurrent.ConcurrentMap;
    +035import java.util.concurrent.ConcurrentSkipListMap;
    +036import java.util.concurrent.atomic.AtomicBoolean;
    +037
    +038
    +039/**
    +040 * Main adapter manager interface for interaction between Gust/Elide apps and Google Cloud Spanner, enabling seamless
    +041 * persistence for generated {@link Message}-driven models.
    +042 *
    +043 * <p>This {@link DatabaseManager} implementation is backed by a customized {@link SpannerDriver} and
    +044 * {@link SpannerAdapter} which manage remote database interactions and cache/transaction state, respectively. While
    +045 * these objects may be acquired directly, `SpannerManager` has the added benefit of a generic singleton pattern which
    +046 * saves re-cycling of the adapter and driver objects.</p>
    +047 *
    +048 * @see SpannerDriver `SpannerDriver`, the main driver for interacting with Cloud Spanner
    +049 * @see SpannerAdapter `SpannerAdapter`, which manages cache/transaction state
    +050 */
    +051@Immutable @ThreadSafe @Refreshable
    +052@SuppressWarnings({"UnstableApiUsage", "rawtypes"})
    +053public final class SpannerManager
    +054        implements DatabaseManager<SpannerAdapter, SpannerDriver>, Closeable, AutoCloseable {
    +055    private static final @Nonnull Logger logging = Logging.logger(SpannerManager.class);
    +056
    +057    /** Keeps track of configured managers spawned by this manager. */
    +058    private final @Nonnull ConcurrentMap<Integer, WeakReference<ConfiguredSpannerManager>> configuredManagers;
    +059
    +060    /** Spanner manager singleton container. */
    +061    private static final class SpannerManagerSingleton {
    +062        private SpannerManagerSingleton() { /* Disallow construction. */ }
    +063
    +064        // Global singleton.
    +065        static volatile @Nullable SpannerManager __singleton = null;
    +066    }
    +067
    +068    /**
    +069     * Default constructor.
    +070     */
    +071    private SpannerManager() {
    +072        configuredManagers = new ConcurrentSkipListMap<>();
    +073    }
    +074
    +075    /**
    +076     * Returns the full set of known configured Spanner managers, JVM-wide. This is mostly useful as a utility to shut
    +077     * down connections globally when needed (for instance, during testing).
    +078     *
    +079     * @return Unmodifiable list of weak references to all known-active managers.
    +080     */
    +081    public static @Nonnull Collection<WeakReference<ConfiguredSpannerManager>> allManagers() {
    +082        var target = acquire();
    +083        if (logging.isTraceEnabled())
    +084            logging.trace(
    +085                "All `SpannerManager` instances requested. Total to return: {}.",
    +086                target.configuredManagers.size());
    +087        return Collections.unmodifiableCollection(target.configuredManagers.values());
    +088    }
    +089
    +090    /**
    +091     * Acquire a singleton instance of the Spanner manager, which can be used safely across threads to interact with
    +092     * Google Cloud Spanner, driven by {@link Message}-generated models.
    +093     *
    +094     * <p>The manager can then be used to request instances of {@link SpannerAdapter} specialized to a given model.
    +095     * Adapter instances acquired in this way are not guaranteed to be new, and are safe to use across threads.</p>
    +096     *
    +097     * @return Singleton instance of the Spanner manager.
    +098     */
    +099    @Factory
    +100    public static @Nonnull SpannerManager acquire() {
    +101        var singleton = SpannerManagerSingleton.__singleton;
    +102        if (singleton == null) {
    +103            singleton = new SpannerManager();
    +104            SpannerManagerSingleton.__singleton = singleton;
    +105            if (logging.isTraceEnabled())
    +106                logging.trace("Acquired fresh singleton for `SpannerManager` request.");
    +107        }
    +108        if (logging.isTraceEnabled())
    +109            logging.trace("Acquired recycled singleton for `SpannerManager` request.");
    +110        return singleton;
    +111    }
    +112
    +113    /**
    +114     * Close all active Spanner connections tracked or controlled by this manager.
    +115     *
    +116     * @throws RuntimeException If the underlying connections raise IO exceptions.
    +117     */
    +118    @Override
    +119    public void close() {
    +120        if (logging.isInfoEnabled())
    +121            logging.info("Shutting down all active `SpannerManager` instances...");
    +122        allManagers().forEach((manager) -> {
    +123            var target = manager.get();
    +124            if (target != null) {
    +125                target.close();
    +126            }
    +127        });
    +128    }
    +129
    +130    /** Intermediate builder which can gather settings for an eventually-immutable {@link ConfiguredSpannerManager}. */
    +131    public final class Builder {
    +132        /** Required/immutable: Main Spanner database this manager will interact with. */
    +133        private final @Nonnull DatabaseId database;
    +134
    +135        /** Optional cache driver to use when caching reads. */
    +136        private @Nonnull Optional<CacheDriver<Message, Message>> cache = Optional.empty();
    +137
    +138        /** Default set of driver settings to use when spawning adapters. */
    +139        private @Nonnull Optional<SpannerDriverSettings> settings = Optional.empty();
    +140
    +141        /** Base set of options to use when spawning Spanner clients via this manager. */
    +142        private @Nonnull Optional<SpannerOptions.Builder> options = Optional.empty();
    +143
    +144        /** Custom executor to use for adapters spawned via this builder. */
    +145        private @Nonnull Optional<ListeningScheduledExecutorService> executor = Optional.empty();
    +146
    +147        /** Private constructor. */
    +148        Builder(@Nonnull DatabaseId database) {
    +149            this.database = database;
    +150        }
    +151
    +152        // -- Builder Getters -- //
    +153
    +154        /** @return Spanner database which the target manager will be bound to. */
    +155        public @Nonnull DatabaseId getDatabase() {
    +156            return database;
    +157        }
    +158
    +159        /** @return Cache adapter, if any, to apply when acquiring adapters/drivers through the target manager. */
    +160        public @Nonnull Optional<CacheDriver<Message, Message>> getCache() {
    +161            return cache;
    +162        }
    +163
    +164        /** @return Custom driver settings to apply when acquiring adapters/drivers through the target manager. */
    +165        public @Nonnull Optional<SpannerDriverSettings> getSettings() {
    +166            return settings;
    +167        }
    +168
    +169        /** @return Base set of Spanner options to apply when spawning Spanner clients from this manager. */
    +170        public @Nonnull Optional<SpannerOptions.Builder> getOptions() {
    +171            return options;
    +172        }
    +173
    +174        /** @return Custom executor to apply when acquiring adapters/drivers through the target manager. */
    +175        public @Nonnull Optional<ListeningScheduledExecutorService> getExecutor() {
    +176            return executor;
    +177        }
    +178
    +179        // -- Builder Setters -- //
    +180
    +181        /**
    +182         * Set the cache for the target configured Spanner manager.
    +183         *
    +184         * @param cache Cache to employ, or none if {@link Optional#empty()}.
    +185         * @return Self, for chainability.
    +186         */
    +187        public @Nonnull Builder setCache(@Nonnull Optional<CacheDriver<Message, Message>> cache) {
    +188            this.cache = cache;
    +189            return this;
    +190        }
    +191
    +192        /**
    +193         * Set the main driver settings to use when spawning adapters in the target configured Spanner manager.
    +194         *
    +195         * @param settings Driver settings to apply.
    +196         * @return Self, for chainability.
    +197         */
    +198        public @Nonnull Builder setSettings(@Nonnull Optional<SpannerDriverSettings> settings) {
    +199            this.settings = settings;
    +200            return this;
    +201        }
    +202
    +203        /**
    +204         * Set the base package of Spanner client options to use when spawning new clients via the target configured
    +205         * Spanner manager.
    +206         *
    +207         * @param options Spanner client options to apply, or none if {@link Optional#empty()}.
    +208         * @return Self, for chainability.
    +209         */
    +210        public @Nonnull Builder setOptions(@Nonnull Optional<SpannerOptions.Builder> options) {
    +211            this.options = options;
    +212            return this;
    +213        }
    +214
    +215        /**
    +216         * Set the executor used by adapters and drivers spawned by this manager.
    +217         *
    +218         * @param executor Executor to use when spawning adapters and drivers.
    +219         * @return Self, for chainability.
    +220         */
    +221        public @Nonnull Builder setExecutor(@Nonnull Optional<ListeningScheduledExecutorService> executor) {
    +222            this.executor = executor;
    +223            return this;
    +224        }
    +225
    +226        /**
    +227         * Build this builder into a configured and immutable {@link ConfiguredSpannerManager} instance, capable of
    +228         * producing managed {@link SpannerAdapter}s specialized to {@link Message} instances.
    +229         *
    +230         * @return Configured and immutable Spanner manager.
    +231         */
    +232        public @Nonnull ConfiguredSpannerManager build() {
    +233            var assignedId = configuredManagers.size();
    +234            var manager = new ConfiguredSpannerManager(
    +235                assignedId,
    +236                database,
    +237                cache,
    +238                options,
    +239                executor,
    +240                settings
    +241            );
    +242
    +243            try {
    +244                configuredManagers.put(
    +245                    assignedId,
    +246                    new WeakReference<>(manager)
    +247                );
    +248                return manager;
    +249            } finally {
    +250                WeakReference.reachabilityFence(manager);
    +251            }
    +252        }
    +253    }
    +254
    +255    /**
    +256     * Configure a vanilla Spanner manager instance for a given database.
    +257     *
    +258     * @param database Spanner database.
    +259     * @return Spanner manager builder.
    +260     */
    +261    public @Nonnull Builder configureForDatabase(@Nonnull DatabaseId database) {
    +262        return new Builder(database);
    +263    }
    +264
    +265    /**
    +266     * Represents a configured version of a central {@link SpannerManager}, which has been sealed for immutable use at
    +267     * runtime. Once built via {@link Builder}, fields on a configured manager cannot change.
    +268     */
    +269    @Immutable @ThreadSafe @Refreshable
    +270    public final class ConfiguredSpannerManager implements
    +271            DatabaseManager<SpannerAdapter, SpannerDriver>, Closeable, AutoCloseable {
    +272        /** Main cache of adapters generated for concrete models. */
    +273        private final @Nonnull ConcurrentMap<Integer,
    +274                SpannerAdapter<? extends Message, ? extends Message>> adapterCache;
    +275
    +276        /** Database we should interact with. */
    +277        private final @Nonnull DatabaseId database;
    +278
    +279        /** Settings to apply to Spanner clients derived from this manager. */
    +280        private final @Nonnull Optional<SpannerOptions.Builder> baseOptions;
    +281
    +282        /** Settings to apply to spawned adapters/drivers. */
    +283        private final @Nonnull Optional<SpannerDriverSettings> settings;
    +284
    +285        /** Custom executor service to apply, if any, to spawned adapters/drivers from this manager. */
    +286        private final @Nonnull Optional<ListeningScheduledExecutorService> executorService;
    +287
    +288        /** Cache to apply, if any, when reading cacheable models. */
    +289        private final @Nonnull Optional<CacheDriver<Message, Message>> cache;
    +290
    +291        /** Whether this configured manager has closed, in which case it cannot spawn adapters or operations. */
    +292        private final @Nonnull AtomicBoolean closed = new AtomicBoolean(false);
    +293
    +294        /** ID assigned to this configured Spanner manager. */
    +295        private final int id;
    +296
    +297        /**
    +298         * Package-private constructor.
    +299         *
    +300         * @param id Assigned ID for this manager.
    +301         * @param database Database we should bind the resulting Spanner manager to.
    +302         * @param cache Optional cache adapter to apply to this one.
    +303         * @param baseOptions Base Spanner driver option set to apply.
    +304         * @param executorService Optional custom executor service to use.
    +305         * @param settings Settings to apply when spawning adapters with this manager.
    +306         */
    +307        ConfiguredSpannerManager(int id,
    +308                                 @Nonnull DatabaseId database,
    +309                                 @Nonnull Optional<CacheDriver<Message, Message>> cache,
    +310                                 @Nonnull Optional<SpannerOptions.Builder> baseOptions,
    +311                                 @Nonnull Optional<ListeningScheduledExecutorService> executorService,
    +312                                 @Nonnull Optional<SpannerDriverSettings> settings) {
    +313            this.database = Objects.requireNonNull(database);
    +314            this.adapterCache = new ConcurrentSkipListMap<>();
    +315            this.executorService = executorService;
    +316            this.baseOptions = baseOptions;
    +317            this.settings = settings;
    +318            this.cache = cache;
    +319            this.id = id;
    +320        }
    +321
    +322        /**
    +323         * Acquire a generic adapter instance designed to work with all {@link Message}-inheriting model types.
    +324         *
    +325         * <p>Adapter instances and backing drivers acquired via this route are not guaranteed to be new, which in most
    +326         * a performance benefit with negligible costs. Since adapters and drivers are required to be threadsafe, they
    +327         * can be re-used safely with no internal state involved.</p>
    +328         *
    +329         * @see #adapter(Message, Message)
    +330         * @return Generic Spanner adapter instance.
    +331         */
    +332        @Factory
    +333        public @Nonnull SpannerAdapter<Message, Message> generic() {
    +334            return adapter(Empty.getDefaultInstance(), Empty.getDefaultInstance());
    +335        }
    +336
    +337        /**
    +338         * Acquire a typed adapter instance specialized to the provided key and model types, which should derive from
    +339         * schema-driven {@link Message} classes.
    +340         *
    +341         * <p>Adapters and backing drivers acquired via this route are not guaranteed to be new, which in most cases is
    +342         * a performance benefit with negligible costs. Since adapters and drivers are required to be threadsafe, they
    +343         * can be re-used safely with no internal state involved.</p>
    +344         *
    +345         * <p>Alternatively, drivers/adapters can also be acquired directly, via methods like
    +346         * {@link SpannerAdapter#acquire(Message, Message, DatabaseId)} and friends.</p>
    +347         *
    +348         * @param keyInstance Model key instance for which a specialized adapter should be returned.
    +349         * @param modelInstance Model object instance for which a specialized adapter should be returned.
    +350         * @param <Key> Key type to which the adapter will be specialized.
    +351         * @param <Model> Model type to which the adapter will be specialized.
    +352         * @throws IllegalArgumentException If the provided key or model instance is not duly marked as a key.
    +353         * @throws IllegalStateException If the provided key or model instance is not duly marked with a table name.
    +354         * @return New or recycled model adapter instance for the provided key and model types.
    +355         */
    +356        @Factory
    +357        @SuppressWarnings("unchecked")
    +358        public @Nonnull <Key extends Message, Model extends Message> SpannerAdapter<Key, Model> adapter(
    +359                @Nonnull Key keyInstance,
    +360                @Nonnull Model modelInstance) {
    +361            Objects.requireNonNull(keyInstance);
    +362            Objects.requireNonNull(modelInstance);
    +363            if (this.getClosed())
    +364                throw new IllegalStateException("Cannot spawn adapters with a closed manager.");
    +365            if (logging.isDebugEnabled())
    +366                logging.info("Acquiring `SpannerAdapter` for model '{}' (key: '{}').",
    +367                        modelInstance.getDescriptorForType().getFullName(),
    +368                        keyInstance.getDescriptorForType().getFullName());
    +369
    +370            var modelFingerprint = keyInstance.getDescriptorForType().getFullName().concat(
    +371                modelInstance.getDescriptorForType().getFullName()
    +372            ).hashCode();
    +373            if (logging.isTraceEnabled())
    +374                logging.info("Model fingerprint for desired adapter: {}.", modelFingerprint);
    +375
    +376            if (!adapterCache.containsKey(modelFingerprint)) {
    +377                if (logging.isTraceEnabled())
    +378                    logging.info("No cached adapter. Spawning new one for fingerprint '{}'...", modelFingerprint);
    +379
    +380                // spawn a new adapter, place it in the cache
    +381                SpannerAdapter<Key, Model> adapter = (SpannerAdapter<Key, Model>)SpannerAdapter.acquire(
    +382                    keyInstance,
    +383                    modelInstance,
    +384                    database,
    +385                    executorService,
    +386                    settings,
    +387                    baseOptions,
    +388                    cache
    +389                );
    +390                adapterCache.put(modelFingerprint, adapter);
    +391                return adapter;
    +392            } else if (logging.isTraceEnabled()) {
    +393                logging.info("Cached adapter found for fingerprint '{}'. Returning.", modelFingerprint);
    +394            }
    +395            return (SpannerAdapter<Key, Model>)adapterCache.get(modelFingerprint);
    +396        }
    +397
    +398        /** @return Database bound to this manager. */
    +399        public @Nonnull DatabaseId getDatabase() {
    +400            return database;
    +401        }
    +402
    +403        /** @return Closed/open state of this manager. */
    +404        public boolean getClosed() {
    +405            return closed.get();
    +406        }
    +407
    +408        /**
    +409         * Returns the full set of known configured Spanner managers, JVM-wide. This is mostly useful as a utility to
    +410         * shut down connections globally when needed (for instance, during testing).
    +411         *
    +412         * @return Unmodifiable list of weak references to all known-active managers.
    +413         */
    +414        public @Nonnull Collection<SpannerAdapter> allAdapters() {
    +415            if (logging.isTraceEnabled())
    +416                logging.trace("All adapters requested from 'SpannerManager' at ID '{}'. Total: {}.",
    +417                        this.id,
    +418                        this.adapterCache.size());
    +419            return Collections.unmodifiableCollection(this.adapterCache.values());
    +420        }
    +421
    +422        /**
    +423         * Close all active Spanner connections tracked or controlled by this configured manager.
    +424         *
    +425         * @throws RuntimeException If the underlying connections raise IO exceptions.
    +426         */
    +427        @Override
    +428        public void close() {
    +429            if (logging.isTraceEnabled())
    +430                logging.trace("Close requested for `SpannerManager` at ID '{}'.", this.id);
    +431            if (this.getClosed()) {
    +432                if (logging.isDebugEnabled())
    +433                    logging.debug("Close requested, but but `SpannerManager` at ID '{}' is already closed.", this.id);
    +434                return;
    +435            }
    +436            try {
    +437                if (logging.isInfoEnabled())
    +438                    logging.info("Closing `SpannerManager` at ID '{}'.", this.id);
    +439                closed.compareAndSet(false, true);
    +440                allAdapters().forEach(SpannerAdapter::close);
    +441            } finally {
    +442                adapterCache.clear();
    +443                configuredManagers.remove(this.id);  // deregister self
    +444            }
    +445        }
    +446
    +447        // -- Configured Manager: Getters -- //
    +448
    +449        /** @return Settings for this configured manager. */
    +450        public @Nonnull SpannerDriverSettings getSettings() {
    +451            return settings.orElse(SpannerDriverSettings.DEFAULTS);
    +452        }
    +453
    +454        /** @return Cache applied to reads, if any. */
    +455        public @Nonnull Optional<CacheDriver<Message, Message>> getCache() {
    +456            return cache;
    +457        }
    +458    }
    +459}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerManager.ConfiguredSpannerManager.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerManager.ConfiguredSpannerManager.html new file mode 100644 index 000000000..460625de9 --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerManager.ConfiguredSpannerManager.html @@ -0,0 +1,533 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import com.google.cloud.spanner.DatabaseId;
    +016import com.google.cloud.spanner.SpannerOptions;
    +017import com.google.common.util.concurrent.ListeningScheduledExecutorService;
    +018import com.google.protobuf.Empty;
    +019import com.google.protobuf.Message;
    +020import gust.backend.model.CacheDriver;
    +021import gust.backend.model.DatabaseManager;
    +022import gust.backend.runtime.Logging;
    +023import io.micronaut.context.annotation.Factory;
    +024import io.micronaut.runtime.context.scope.Refreshable;
    +025import org.slf4j.Logger;
    +026
    +027import javax.annotation.Nonnull;
    +028import javax.annotation.Nullable;
    +029import javax.annotation.concurrent.Immutable;
    +030import javax.annotation.concurrent.ThreadSafe;
    +031import java.io.Closeable;
    +032import java.lang.ref.WeakReference;
    +033import java.util.*;
    +034import java.util.concurrent.ConcurrentMap;
    +035import java.util.concurrent.ConcurrentSkipListMap;
    +036import java.util.concurrent.atomic.AtomicBoolean;
    +037
    +038
    +039/**
    +040 * Main adapter manager interface for interaction between Gust/Elide apps and Google Cloud Spanner, enabling seamless
    +041 * persistence for generated {@link Message}-driven models.
    +042 *
    +043 * <p>This {@link DatabaseManager} implementation is backed by a customized {@link SpannerDriver} and
    +044 * {@link SpannerAdapter} which manage remote database interactions and cache/transaction state, respectively. While
    +045 * these objects may be acquired directly, `SpannerManager` has the added benefit of a generic singleton pattern which
    +046 * saves re-cycling of the adapter and driver objects.</p>
    +047 *
    +048 * @see SpannerDriver `SpannerDriver`, the main driver for interacting with Cloud Spanner
    +049 * @see SpannerAdapter `SpannerAdapter`, which manages cache/transaction state
    +050 */
    +051@Immutable @ThreadSafe @Refreshable
    +052@SuppressWarnings({"UnstableApiUsage", "rawtypes"})
    +053public final class SpannerManager
    +054        implements DatabaseManager<SpannerAdapter, SpannerDriver>, Closeable, AutoCloseable {
    +055    private static final @Nonnull Logger logging = Logging.logger(SpannerManager.class);
    +056
    +057    /** Keeps track of configured managers spawned by this manager. */
    +058    private final @Nonnull ConcurrentMap<Integer, WeakReference<ConfiguredSpannerManager>> configuredManagers;
    +059
    +060    /** Spanner manager singleton container. */
    +061    private static final class SpannerManagerSingleton {
    +062        private SpannerManagerSingleton() { /* Disallow construction. */ }
    +063
    +064        // Global singleton.
    +065        static volatile @Nullable SpannerManager __singleton = null;
    +066    }
    +067
    +068    /**
    +069     * Default constructor.
    +070     */
    +071    private SpannerManager() {
    +072        configuredManagers = new ConcurrentSkipListMap<>();
    +073    }
    +074
    +075    /**
    +076     * Returns the full set of known configured Spanner managers, JVM-wide. This is mostly useful as a utility to shut
    +077     * down connections globally when needed (for instance, during testing).
    +078     *
    +079     * @return Unmodifiable list of weak references to all known-active managers.
    +080     */
    +081    public static @Nonnull Collection<WeakReference<ConfiguredSpannerManager>> allManagers() {
    +082        var target = acquire();
    +083        if (logging.isTraceEnabled())
    +084            logging.trace(
    +085                "All `SpannerManager` instances requested. Total to return: {}.",
    +086                target.configuredManagers.size());
    +087        return Collections.unmodifiableCollection(target.configuredManagers.values());
    +088    }
    +089
    +090    /**
    +091     * Acquire a singleton instance of the Spanner manager, which can be used safely across threads to interact with
    +092     * Google Cloud Spanner, driven by {@link Message}-generated models.
    +093     *
    +094     * <p>The manager can then be used to request instances of {@link SpannerAdapter} specialized to a given model.
    +095     * Adapter instances acquired in this way are not guaranteed to be new, and are safe to use across threads.</p>
    +096     *
    +097     * @return Singleton instance of the Spanner manager.
    +098     */
    +099    @Factory
    +100    public static @Nonnull SpannerManager acquire() {
    +101        var singleton = SpannerManagerSingleton.__singleton;
    +102        if (singleton == null) {
    +103            singleton = new SpannerManager();
    +104            SpannerManagerSingleton.__singleton = singleton;
    +105            if (logging.isTraceEnabled())
    +106                logging.trace("Acquired fresh singleton for `SpannerManager` request.");
    +107        }
    +108        if (logging.isTraceEnabled())
    +109            logging.trace("Acquired recycled singleton for `SpannerManager` request.");
    +110        return singleton;
    +111    }
    +112
    +113    /**
    +114     * Close all active Spanner connections tracked or controlled by this manager.
    +115     *
    +116     * @throws RuntimeException If the underlying connections raise IO exceptions.
    +117     */
    +118    @Override
    +119    public void close() {
    +120        if (logging.isInfoEnabled())
    +121            logging.info("Shutting down all active `SpannerManager` instances...");
    +122        allManagers().forEach((manager) -> {
    +123            var target = manager.get();
    +124            if (target != null) {
    +125                target.close();
    +126            }
    +127        });
    +128    }
    +129
    +130    /** Intermediate builder which can gather settings for an eventually-immutable {@link ConfiguredSpannerManager}. */
    +131    public final class Builder {
    +132        /** Required/immutable: Main Spanner database this manager will interact with. */
    +133        private final @Nonnull DatabaseId database;
    +134
    +135        /** Optional cache driver to use when caching reads. */
    +136        private @Nonnull Optional<CacheDriver<Message, Message>> cache = Optional.empty();
    +137
    +138        /** Default set of driver settings to use when spawning adapters. */
    +139        private @Nonnull Optional<SpannerDriverSettings> settings = Optional.empty();
    +140
    +141        /** Base set of options to use when spawning Spanner clients via this manager. */
    +142        private @Nonnull Optional<SpannerOptions.Builder> options = Optional.empty();
    +143
    +144        /** Custom executor to use for adapters spawned via this builder. */
    +145        private @Nonnull Optional<ListeningScheduledExecutorService> executor = Optional.empty();
    +146
    +147        /** Private constructor. */
    +148        Builder(@Nonnull DatabaseId database) {
    +149            this.database = database;
    +150        }
    +151
    +152        // -- Builder Getters -- //
    +153
    +154        /** @return Spanner database which the target manager will be bound to. */
    +155        public @Nonnull DatabaseId getDatabase() {
    +156            return database;
    +157        }
    +158
    +159        /** @return Cache adapter, if any, to apply when acquiring adapters/drivers through the target manager. */
    +160        public @Nonnull Optional<CacheDriver<Message, Message>> getCache() {
    +161            return cache;
    +162        }
    +163
    +164        /** @return Custom driver settings to apply when acquiring adapters/drivers through the target manager. */
    +165        public @Nonnull Optional<SpannerDriverSettings> getSettings() {
    +166            return settings;
    +167        }
    +168
    +169        /** @return Base set of Spanner options to apply when spawning Spanner clients from this manager. */
    +170        public @Nonnull Optional<SpannerOptions.Builder> getOptions() {
    +171            return options;
    +172        }
    +173
    +174        /** @return Custom executor to apply when acquiring adapters/drivers through the target manager. */
    +175        public @Nonnull Optional<ListeningScheduledExecutorService> getExecutor() {
    +176            return executor;
    +177        }
    +178
    +179        // -- Builder Setters -- //
    +180
    +181        /**
    +182         * Set the cache for the target configured Spanner manager.
    +183         *
    +184         * @param cache Cache to employ, or none if {@link Optional#empty()}.
    +185         * @return Self, for chainability.
    +186         */
    +187        public @Nonnull Builder setCache(@Nonnull Optional<CacheDriver<Message, Message>> cache) {
    +188            this.cache = cache;
    +189            return this;
    +190        }
    +191
    +192        /**
    +193         * Set the main driver settings to use when spawning adapters in the target configured Spanner manager.
    +194         *
    +195         * @param settings Driver settings to apply.
    +196         * @return Self, for chainability.
    +197         */
    +198        public @Nonnull Builder setSettings(@Nonnull Optional<SpannerDriverSettings> settings) {
    +199            this.settings = settings;
    +200            return this;
    +201        }
    +202
    +203        /**
    +204         * Set the base package of Spanner client options to use when spawning new clients via the target configured
    +205         * Spanner manager.
    +206         *
    +207         * @param options Spanner client options to apply, or none if {@link Optional#empty()}.
    +208         * @return Self, for chainability.
    +209         */
    +210        public @Nonnull Builder setOptions(@Nonnull Optional<SpannerOptions.Builder> options) {
    +211            this.options = options;
    +212            return this;
    +213        }
    +214
    +215        /**
    +216         * Set the executor used by adapters and drivers spawned by this manager.
    +217         *
    +218         * @param executor Executor to use when spawning adapters and drivers.
    +219         * @return Self, for chainability.
    +220         */
    +221        public @Nonnull Builder setExecutor(@Nonnull Optional<ListeningScheduledExecutorService> executor) {
    +222            this.executor = executor;
    +223            return this;
    +224        }
    +225
    +226        /**
    +227         * Build this builder into a configured and immutable {@link ConfiguredSpannerManager} instance, capable of
    +228         * producing managed {@link SpannerAdapter}s specialized to {@link Message} instances.
    +229         *
    +230         * @return Configured and immutable Spanner manager.
    +231         */
    +232        public @Nonnull ConfiguredSpannerManager build() {
    +233            var assignedId = configuredManagers.size();
    +234            var manager = new ConfiguredSpannerManager(
    +235                assignedId,
    +236                database,
    +237                cache,
    +238                options,
    +239                executor,
    +240                settings
    +241            );
    +242
    +243            try {
    +244                configuredManagers.put(
    +245                    assignedId,
    +246                    new WeakReference<>(manager)
    +247                );
    +248                return manager;
    +249            } finally {
    +250                WeakReference.reachabilityFence(manager);
    +251            }
    +252        }
    +253    }
    +254
    +255    /**
    +256     * Configure a vanilla Spanner manager instance for a given database.
    +257     *
    +258     * @param database Spanner database.
    +259     * @return Spanner manager builder.
    +260     */
    +261    public @Nonnull Builder configureForDatabase(@Nonnull DatabaseId database) {
    +262        return new Builder(database);
    +263    }
    +264
    +265    /**
    +266     * Represents a configured version of a central {@link SpannerManager}, which has been sealed for immutable use at
    +267     * runtime. Once built via {@link Builder}, fields on a configured manager cannot change.
    +268     */
    +269    @Immutable @ThreadSafe @Refreshable
    +270    public final class ConfiguredSpannerManager implements
    +271            DatabaseManager<SpannerAdapter, SpannerDriver>, Closeable, AutoCloseable {
    +272        /** Main cache of adapters generated for concrete models. */
    +273        private final @Nonnull ConcurrentMap<Integer,
    +274                SpannerAdapter<? extends Message, ? extends Message>> adapterCache;
    +275
    +276        /** Database we should interact with. */
    +277        private final @Nonnull DatabaseId database;
    +278
    +279        /** Settings to apply to Spanner clients derived from this manager. */
    +280        private final @Nonnull Optional<SpannerOptions.Builder> baseOptions;
    +281
    +282        /** Settings to apply to spawned adapters/drivers. */
    +283        private final @Nonnull Optional<SpannerDriverSettings> settings;
    +284
    +285        /** Custom executor service to apply, if any, to spawned adapters/drivers from this manager. */
    +286        private final @Nonnull Optional<ListeningScheduledExecutorService> executorService;
    +287
    +288        /** Cache to apply, if any, when reading cacheable models. */
    +289        private final @Nonnull Optional<CacheDriver<Message, Message>> cache;
    +290
    +291        /** Whether this configured manager has closed, in which case it cannot spawn adapters or operations. */
    +292        private final @Nonnull AtomicBoolean closed = new AtomicBoolean(false);
    +293
    +294        /** ID assigned to this configured Spanner manager. */
    +295        private final int id;
    +296
    +297        /**
    +298         * Package-private constructor.
    +299         *
    +300         * @param id Assigned ID for this manager.
    +301         * @param database Database we should bind the resulting Spanner manager to.
    +302         * @param cache Optional cache adapter to apply to this one.
    +303         * @param baseOptions Base Spanner driver option set to apply.
    +304         * @param executorService Optional custom executor service to use.
    +305         * @param settings Settings to apply when spawning adapters with this manager.
    +306         */
    +307        ConfiguredSpannerManager(int id,
    +308                                 @Nonnull DatabaseId database,
    +309                                 @Nonnull Optional<CacheDriver<Message, Message>> cache,
    +310                                 @Nonnull Optional<SpannerOptions.Builder> baseOptions,
    +311                                 @Nonnull Optional<ListeningScheduledExecutorService> executorService,
    +312                                 @Nonnull Optional<SpannerDriverSettings> settings) {
    +313            this.database = Objects.requireNonNull(database);
    +314            this.adapterCache = new ConcurrentSkipListMap<>();
    +315            this.executorService = executorService;
    +316            this.baseOptions = baseOptions;
    +317            this.settings = settings;
    +318            this.cache = cache;
    +319            this.id = id;
    +320        }
    +321
    +322        /**
    +323         * Acquire a generic adapter instance designed to work with all {@link Message}-inheriting model types.
    +324         *
    +325         * <p>Adapter instances and backing drivers acquired via this route are not guaranteed to be new, which in most
    +326         * a performance benefit with negligible costs. Since adapters and drivers are required to be threadsafe, they
    +327         * can be re-used safely with no internal state involved.</p>
    +328         *
    +329         * @see #adapter(Message, Message)
    +330         * @return Generic Spanner adapter instance.
    +331         */
    +332        @Factory
    +333        public @Nonnull SpannerAdapter<Message, Message> generic() {
    +334            return adapter(Empty.getDefaultInstance(), Empty.getDefaultInstance());
    +335        }
    +336
    +337        /**
    +338         * Acquire a typed adapter instance specialized to the provided key and model types, which should derive from
    +339         * schema-driven {@link Message} classes.
    +340         *
    +341         * <p>Adapters and backing drivers acquired via this route are not guaranteed to be new, which in most cases is
    +342         * a performance benefit with negligible costs. Since adapters and drivers are required to be threadsafe, they
    +343         * can be re-used safely with no internal state involved.</p>
    +344         *
    +345         * <p>Alternatively, drivers/adapters can also be acquired directly, via methods like
    +346         * {@link SpannerAdapter#acquire(Message, Message, DatabaseId)} and friends.</p>
    +347         *
    +348         * @param keyInstance Model key instance for which a specialized adapter should be returned.
    +349         * @param modelInstance Model object instance for which a specialized adapter should be returned.
    +350         * @param <Key> Key type to which the adapter will be specialized.
    +351         * @param <Model> Model type to which the adapter will be specialized.
    +352         * @throws IllegalArgumentException If the provided key or model instance is not duly marked as a key.
    +353         * @throws IllegalStateException If the provided key or model instance is not duly marked with a table name.
    +354         * @return New or recycled model adapter instance for the provided key and model types.
    +355         */
    +356        @Factory
    +357        @SuppressWarnings("unchecked")
    +358        public @Nonnull <Key extends Message, Model extends Message> SpannerAdapter<Key, Model> adapter(
    +359                @Nonnull Key keyInstance,
    +360                @Nonnull Model modelInstance) {
    +361            Objects.requireNonNull(keyInstance);
    +362            Objects.requireNonNull(modelInstance);
    +363            if (this.getClosed())
    +364                throw new IllegalStateException("Cannot spawn adapters with a closed manager.");
    +365            if (logging.isDebugEnabled())
    +366                logging.info("Acquiring `SpannerAdapter` for model '{}' (key: '{}').",
    +367                        modelInstance.getDescriptorForType().getFullName(),
    +368                        keyInstance.getDescriptorForType().getFullName());
    +369
    +370            var modelFingerprint = keyInstance.getDescriptorForType().getFullName().concat(
    +371                modelInstance.getDescriptorForType().getFullName()
    +372            ).hashCode();
    +373            if (logging.isTraceEnabled())
    +374                logging.info("Model fingerprint for desired adapter: {}.", modelFingerprint);
    +375
    +376            if (!adapterCache.containsKey(modelFingerprint)) {
    +377                if (logging.isTraceEnabled())
    +378                    logging.info("No cached adapter. Spawning new one for fingerprint '{}'...", modelFingerprint);
    +379
    +380                // spawn a new adapter, place it in the cache
    +381                SpannerAdapter<Key, Model> adapter = (SpannerAdapter<Key, Model>)SpannerAdapter.acquire(
    +382                    keyInstance,
    +383                    modelInstance,
    +384                    database,
    +385                    executorService,
    +386                    settings,
    +387                    baseOptions,
    +388                    cache
    +389                );
    +390                adapterCache.put(modelFingerprint, adapter);
    +391                return adapter;
    +392            } else if (logging.isTraceEnabled()) {
    +393                logging.info("Cached adapter found for fingerprint '{}'. Returning.", modelFingerprint);
    +394            }
    +395            return (SpannerAdapter<Key, Model>)adapterCache.get(modelFingerprint);
    +396        }
    +397
    +398        /** @return Database bound to this manager. */
    +399        public @Nonnull DatabaseId getDatabase() {
    +400            return database;
    +401        }
    +402
    +403        /** @return Closed/open state of this manager. */
    +404        public boolean getClosed() {
    +405            return closed.get();
    +406        }
    +407
    +408        /**
    +409         * Returns the full set of known configured Spanner managers, JVM-wide. This is mostly useful as a utility to
    +410         * shut down connections globally when needed (for instance, during testing).
    +411         *
    +412         * @return Unmodifiable list of weak references to all known-active managers.
    +413         */
    +414        public @Nonnull Collection<SpannerAdapter> allAdapters() {
    +415            if (logging.isTraceEnabled())
    +416                logging.trace("All adapters requested from 'SpannerManager' at ID '{}'. Total: {}.",
    +417                        this.id,
    +418                        this.adapterCache.size());
    +419            return Collections.unmodifiableCollection(this.adapterCache.values());
    +420        }
    +421
    +422        /**
    +423         * Close all active Spanner connections tracked or controlled by this configured manager.
    +424         *
    +425         * @throws RuntimeException If the underlying connections raise IO exceptions.
    +426         */
    +427        @Override
    +428        public void close() {
    +429            if (logging.isTraceEnabled())
    +430                logging.trace("Close requested for `SpannerManager` at ID '{}'.", this.id);
    +431            if (this.getClosed()) {
    +432                if (logging.isDebugEnabled())
    +433                    logging.debug("Close requested, but but `SpannerManager` at ID '{}' is already closed.", this.id);
    +434                return;
    +435            }
    +436            try {
    +437                if (logging.isInfoEnabled())
    +438                    logging.info("Closing `SpannerManager` at ID '{}'.", this.id);
    +439                closed.compareAndSet(false, true);
    +440                allAdapters().forEach(SpannerAdapter::close);
    +441            } finally {
    +442                adapterCache.clear();
    +443                configuredManagers.remove(this.id);  // deregister self
    +444            }
    +445        }
    +446
    +447        // -- Configured Manager: Getters -- //
    +448
    +449        /** @return Settings for this configured manager. */
    +450        public @Nonnull SpannerDriverSettings getSettings() {
    +451            return settings.orElse(SpannerDriverSettings.DEFAULTS);
    +452        }
    +453
    +454        /** @return Cache applied to reads, if any. */
    +455        public @Nonnull Optional<CacheDriver<Message, Message>> getCache() {
    +456            return cache;
    +457        }
    +458    }
    +459}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerManager.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerManager.html new file mode 100644 index 000000000..460625de9 --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerManager.html @@ -0,0 +1,533 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import com.google.cloud.spanner.DatabaseId;
    +016import com.google.cloud.spanner.SpannerOptions;
    +017import com.google.common.util.concurrent.ListeningScheduledExecutorService;
    +018import com.google.protobuf.Empty;
    +019import com.google.protobuf.Message;
    +020import gust.backend.model.CacheDriver;
    +021import gust.backend.model.DatabaseManager;
    +022import gust.backend.runtime.Logging;
    +023import io.micronaut.context.annotation.Factory;
    +024import io.micronaut.runtime.context.scope.Refreshable;
    +025import org.slf4j.Logger;
    +026
    +027import javax.annotation.Nonnull;
    +028import javax.annotation.Nullable;
    +029import javax.annotation.concurrent.Immutable;
    +030import javax.annotation.concurrent.ThreadSafe;
    +031import java.io.Closeable;
    +032import java.lang.ref.WeakReference;
    +033import java.util.*;
    +034import java.util.concurrent.ConcurrentMap;
    +035import java.util.concurrent.ConcurrentSkipListMap;
    +036import java.util.concurrent.atomic.AtomicBoolean;
    +037
    +038
    +039/**
    +040 * Main adapter manager interface for interaction between Gust/Elide apps and Google Cloud Spanner, enabling seamless
    +041 * persistence for generated {@link Message}-driven models.
    +042 *
    +043 * <p>This {@link DatabaseManager} implementation is backed by a customized {@link SpannerDriver} and
    +044 * {@link SpannerAdapter} which manage remote database interactions and cache/transaction state, respectively. While
    +045 * these objects may be acquired directly, `SpannerManager` has the added benefit of a generic singleton pattern which
    +046 * saves re-cycling of the adapter and driver objects.</p>
    +047 *
    +048 * @see SpannerDriver `SpannerDriver`, the main driver for interacting with Cloud Spanner
    +049 * @see SpannerAdapter `SpannerAdapter`, which manages cache/transaction state
    +050 */
    +051@Immutable @ThreadSafe @Refreshable
    +052@SuppressWarnings({"UnstableApiUsage", "rawtypes"})
    +053public final class SpannerManager
    +054        implements DatabaseManager<SpannerAdapter, SpannerDriver>, Closeable, AutoCloseable {
    +055    private static final @Nonnull Logger logging = Logging.logger(SpannerManager.class);
    +056
    +057    /** Keeps track of configured managers spawned by this manager. */
    +058    private final @Nonnull ConcurrentMap<Integer, WeakReference<ConfiguredSpannerManager>> configuredManagers;
    +059
    +060    /** Spanner manager singleton container. */
    +061    private static final class SpannerManagerSingleton {
    +062        private SpannerManagerSingleton() { /* Disallow construction. */ }
    +063
    +064        // Global singleton.
    +065        static volatile @Nullable SpannerManager __singleton = null;
    +066    }
    +067
    +068    /**
    +069     * Default constructor.
    +070     */
    +071    private SpannerManager() {
    +072        configuredManagers = new ConcurrentSkipListMap<>();
    +073    }
    +074
    +075    /**
    +076     * Returns the full set of known configured Spanner managers, JVM-wide. This is mostly useful as a utility to shut
    +077     * down connections globally when needed (for instance, during testing).
    +078     *
    +079     * @return Unmodifiable list of weak references to all known-active managers.
    +080     */
    +081    public static @Nonnull Collection<WeakReference<ConfiguredSpannerManager>> allManagers() {
    +082        var target = acquire();
    +083        if (logging.isTraceEnabled())
    +084            logging.trace(
    +085                "All `SpannerManager` instances requested. Total to return: {}.",
    +086                target.configuredManagers.size());
    +087        return Collections.unmodifiableCollection(target.configuredManagers.values());
    +088    }
    +089
    +090    /**
    +091     * Acquire a singleton instance of the Spanner manager, which can be used safely across threads to interact with
    +092     * Google Cloud Spanner, driven by {@link Message}-generated models.
    +093     *
    +094     * <p>The manager can then be used to request instances of {@link SpannerAdapter} specialized to a given model.
    +095     * Adapter instances acquired in this way are not guaranteed to be new, and are safe to use across threads.</p>
    +096     *
    +097     * @return Singleton instance of the Spanner manager.
    +098     */
    +099    @Factory
    +100    public static @Nonnull SpannerManager acquire() {
    +101        var singleton = SpannerManagerSingleton.__singleton;
    +102        if (singleton == null) {
    +103            singleton = new SpannerManager();
    +104            SpannerManagerSingleton.__singleton = singleton;
    +105            if (logging.isTraceEnabled())
    +106                logging.trace("Acquired fresh singleton for `SpannerManager` request.");
    +107        }
    +108        if (logging.isTraceEnabled())
    +109            logging.trace("Acquired recycled singleton for `SpannerManager` request.");
    +110        return singleton;
    +111    }
    +112
    +113    /**
    +114     * Close all active Spanner connections tracked or controlled by this manager.
    +115     *
    +116     * @throws RuntimeException If the underlying connections raise IO exceptions.
    +117     */
    +118    @Override
    +119    public void close() {
    +120        if (logging.isInfoEnabled())
    +121            logging.info("Shutting down all active `SpannerManager` instances...");
    +122        allManagers().forEach((manager) -> {
    +123            var target = manager.get();
    +124            if (target != null) {
    +125                target.close();
    +126            }
    +127        });
    +128    }
    +129
    +130    /** Intermediate builder which can gather settings for an eventually-immutable {@link ConfiguredSpannerManager}. */
    +131    public final class Builder {
    +132        /** Required/immutable: Main Spanner database this manager will interact with. */
    +133        private final @Nonnull DatabaseId database;
    +134
    +135        /** Optional cache driver to use when caching reads. */
    +136        private @Nonnull Optional<CacheDriver<Message, Message>> cache = Optional.empty();
    +137
    +138        /** Default set of driver settings to use when spawning adapters. */
    +139        private @Nonnull Optional<SpannerDriverSettings> settings = Optional.empty();
    +140
    +141        /** Base set of options to use when spawning Spanner clients via this manager. */
    +142        private @Nonnull Optional<SpannerOptions.Builder> options = Optional.empty();
    +143
    +144        /** Custom executor to use for adapters spawned via this builder. */
    +145        private @Nonnull Optional<ListeningScheduledExecutorService> executor = Optional.empty();
    +146
    +147        /** Private constructor. */
    +148        Builder(@Nonnull DatabaseId database) {
    +149            this.database = database;
    +150        }
    +151
    +152        // -- Builder Getters -- //
    +153
    +154        /** @return Spanner database which the target manager will be bound to. */
    +155        public @Nonnull DatabaseId getDatabase() {
    +156            return database;
    +157        }
    +158
    +159        /** @return Cache adapter, if any, to apply when acquiring adapters/drivers through the target manager. */
    +160        public @Nonnull Optional<CacheDriver<Message, Message>> getCache() {
    +161            return cache;
    +162        }
    +163
    +164        /** @return Custom driver settings to apply when acquiring adapters/drivers through the target manager. */
    +165        public @Nonnull Optional<SpannerDriverSettings> getSettings() {
    +166            return settings;
    +167        }
    +168
    +169        /** @return Base set of Spanner options to apply when spawning Spanner clients from this manager. */
    +170        public @Nonnull Optional<SpannerOptions.Builder> getOptions() {
    +171            return options;
    +172        }
    +173
    +174        /** @return Custom executor to apply when acquiring adapters/drivers through the target manager. */
    +175        public @Nonnull Optional<ListeningScheduledExecutorService> getExecutor() {
    +176            return executor;
    +177        }
    +178
    +179        // -- Builder Setters -- //
    +180
    +181        /**
    +182         * Set the cache for the target configured Spanner manager.
    +183         *
    +184         * @param cache Cache to employ, or none if {@link Optional#empty()}.
    +185         * @return Self, for chainability.
    +186         */
    +187        public @Nonnull Builder setCache(@Nonnull Optional<CacheDriver<Message, Message>> cache) {
    +188            this.cache = cache;
    +189            return this;
    +190        }
    +191
    +192        /**
    +193         * Set the main driver settings to use when spawning adapters in the target configured Spanner manager.
    +194         *
    +195         * @param settings Driver settings to apply.
    +196         * @return Self, for chainability.
    +197         */
    +198        public @Nonnull Builder setSettings(@Nonnull Optional<SpannerDriverSettings> settings) {
    +199            this.settings = settings;
    +200            return this;
    +201        }
    +202
    +203        /**
    +204         * Set the base package of Spanner client options to use when spawning new clients via the target configured
    +205         * Spanner manager.
    +206         *
    +207         * @param options Spanner client options to apply, or none if {@link Optional#empty()}.
    +208         * @return Self, for chainability.
    +209         */
    +210        public @Nonnull Builder setOptions(@Nonnull Optional<SpannerOptions.Builder> options) {
    +211            this.options = options;
    +212            return this;
    +213        }
    +214
    +215        /**
    +216         * Set the executor used by adapters and drivers spawned by this manager.
    +217         *
    +218         * @param executor Executor to use when spawning adapters and drivers.
    +219         * @return Self, for chainability.
    +220         */
    +221        public @Nonnull Builder setExecutor(@Nonnull Optional<ListeningScheduledExecutorService> executor) {
    +222            this.executor = executor;
    +223            return this;
    +224        }
    +225
    +226        /**
    +227         * Build this builder into a configured and immutable {@link ConfiguredSpannerManager} instance, capable of
    +228         * producing managed {@link SpannerAdapter}s specialized to {@link Message} instances.
    +229         *
    +230         * @return Configured and immutable Spanner manager.
    +231         */
    +232        public @Nonnull ConfiguredSpannerManager build() {
    +233            var assignedId = configuredManagers.size();
    +234            var manager = new ConfiguredSpannerManager(
    +235                assignedId,
    +236                database,
    +237                cache,
    +238                options,
    +239                executor,
    +240                settings
    +241            );
    +242
    +243            try {
    +244                configuredManagers.put(
    +245                    assignedId,
    +246                    new WeakReference<>(manager)
    +247                );
    +248                return manager;
    +249            } finally {
    +250                WeakReference.reachabilityFence(manager);
    +251            }
    +252        }
    +253    }
    +254
    +255    /**
    +256     * Configure a vanilla Spanner manager instance for a given database.
    +257     *
    +258     * @param database Spanner database.
    +259     * @return Spanner manager builder.
    +260     */
    +261    public @Nonnull Builder configureForDatabase(@Nonnull DatabaseId database) {
    +262        return new Builder(database);
    +263    }
    +264
    +265    /**
    +266     * Represents a configured version of a central {@link SpannerManager}, which has been sealed for immutable use at
    +267     * runtime. Once built via {@link Builder}, fields on a configured manager cannot change.
    +268     */
    +269    @Immutable @ThreadSafe @Refreshable
    +270    public final class ConfiguredSpannerManager implements
    +271            DatabaseManager<SpannerAdapter, SpannerDriver>, Closeable, AutoCloseable {
    +272        /** Main cache of adapters generated for concrete models. */
    +273        private final @Nonnull ConcurrentMap<Integer,
    +274                SpannerAdapter<? extends Message, ? extends Message>> adapterCache;
    +275
    +276        /** Database we should interact with. */
    +277        private final @Nonnull DatabaseId database;
    +278
    +279        /** Settings to apply to Spanner clients derived from this manager. */
    +280        private final @Nonnull Optional<SpannerOptions.Builder> baseOptions;
    +281
    +282        /** Settings to apply to spawned adapters/drivers. */
    +283        private final @Nonnull Optional<SpannerDriverSettings> settings;
    +284
    +285        /** Custom executor service to apply, if any, to spawned adapters/drivers from this manager. */
    +286        private final @Nonnull Optional<ListeningScheduledExecutorService> executorService;
    +287
    +288        /** Cache to apply, if any, when reading cacheable models. */
    +289        private final @Nonnull Optional<CacheDriver<Message, Message>> cache;
    +290
    +291        /** Whether this configured manager has closed, in which case it cannot spawn adapters or operations. */
    +292        private final @Nonnull AtomicBoolean closed = new AtomicBoolean(false);
    +293
    +294        /** ID assigned to this configured Spanner manager. */
    +295        private final int id;
    +296
    +297        /**
    +298         * Package-private constructor.
    +299         *
    +300         * @param id Assigned ID for this manager.
    +301         * @param database Database we should bind the resulting Spanner manager to.
    +302         * @param cache Optional cache adapter to apply to this one.
    +303         * @param baseOptions Base Spanner driver option set to apply.
    +304         * @param executorService Optional custom executor service to use.
    +305         * @param settings Settings to apply when spawning adapters with this manager.
    +306         */
    +307        ConfiguredSpannerManager(int id,
    +308                                 @Nonnull DatabaseId database,
    +309                                 @Nonnull Optional<CacheDriver<Message, Message>> cache,
    +310                                 @Nonnull Optional<SpannerOptions.Builder> baseOptions,
    +311                                 @Nonnull Optional<ListeningScheduledExecutorService> executorService,
    +312                                 @Nonnull Optional<SpannerDriverSettings> settings) {
    +313            this.database = Objects.requireNonNull(database);
    +314            this.adapterCache = new ConcurrentSkipListMap<>();
    +315            this.executorService = executorService;
    +316            this.baseOptions = baseOptions;
    +317            this.settings = settings;
    +318            this.cache = cache;
    +319            this.id = id;
    +320        }
    +321
    +322        /**
    +323         * Acquire a generic adapter instance designed to work with all {@link Message}-inheriting model types.
    +324         *
    +325         * <p>Adapter instances and backing drivers acquired via this route are not guaranteed to be new, which in most
    +326         * a performance benefit with negligible costs. Since adapters and drivers are required to be threadsafe, they
    +327         * can be re-used safely with no internal state involved.</p>
    +328         *
    +329         * @see #adapter(Message, Message)
    +330         * @return Generic Spanner adapter instance.
    +331         */
    +332        @Factory
    +333        public @Nonnull SpannerAdapter<Message, Message> generic() {
    +334            return adapter(Empty.getDefaultInstance(), Empty.getDefaultInstance());
    +335        }
    +336
    +337        /**
    +338         * Acquire a typed adapter instance specialized to the provided key and model types, which should derive from
    +339         * schema-driven {@link Message} classes.
    +340         *
    +341         * <p>Adapters and backing drivers acquired via this route are not guaranteed to be new, which in most cases is
    +342         * a performance benefit with negligible costs. Since adapters and drivers are required to be threadsafe, they
    +343         * can be re-used safely with no internal state involved.</p>
    +344         *
    +345         * <p>Alternatively, drivers/adapters can also be acquired directly, via methods like
    +346         * {@link SpannerAdapter#acquire(Message, Message, DatabaseId)} and friends.</p>
    +347         *
    +348         * @param keyInstance Model key instance for which a specialized adapter should be returned.
    +349         * @param modelInstance Model object instance for which a specialized adapter should be returned.
    +350         * @param <Key> Key type to which the adapter will be specialized.
    +351         * @param <Model> Model type to which the adapter will be specialized.
    +352         * @throws IllegalArgumentException If the provided key or model instance is not duly marked as a key.
    +353         * @throws IllegalStateException If the provided key or model instance is not duly marked with a table name.
    +354         * @return New or recycled model adapter instance for the provided key and model types.
    +355         */
    +356        @Factory
    +357        @SuppressWarnings("unchecked")
    +358        public @Nonnull <Key extends Message, Model extends Message> SpannerAdapter<Key, Model> adapter(
    +359                @Nonnull Key keyInstance,
    +360                @Nonnull Model modelInstance) {
    +361            Objects.requireNonNull(keyInstance);
    +362            Objects.requireNonNull(modelInstance);
    +363            if (this.getClosed())
    +364                throw new IllegalStateException("Cannot spawn adapters with a closed manager.");
    +365            if (logging.isDebugEnabled())
    +366                logging.info("Acquiring `SpannerAdapter` for model '{}' (key: '{}').",
    +367                        modelInstance.getDescriptorForType().getFullName(),
    +368                        keyInstance.getDescriptorForType().getFullName());
    +369
    +370            var modelFingerprint = keyInstance.getDescriptorForType().getFullName().concat(
    +371                modelInstance.getDescriptorForType().getFullName()
    +372            ).hashCode();
    +373            if (logging.isTraceEnabled())
    +374                logging.info("Model fingerprint for desired adapter: {}.", modelFingerprint);
    +375
    +376            if (!adapterCache.containsKey(modelFingerprint)) {
    +377                if (logging.isTraceEnabled())
    +378                    logging.info("No cached adapter. Spawning new one for fingerprint '{}'...", modelFingerprint);
    +379
    +380                // spawn a new adapter, place it in the cache
    +381                SpannerAdapter<Key, Model> adapter = (SpannerAdapter<Key, Model>)SpannerAdapter.acquire(
    +382                    keyInstance,
    +383                    modelInstance,
    +384                    database,
    +385                    executorService,
    +386                    settings,
    +387                    baseOptions,
    +388                    cache
    +389                );
    +390                adapterCache.put(modelFingerprint, adapter);
    +391                return adapter;
    +392            } else if (logging.isTraceEnabled()) {
    +393                logging.info("Cached adapter found for fingerprint '{}'. Returning.", modelFingerprint);
    +394            }
    +395            return (SpannerAdapter<Key, Model>)adapterCache.get(modelFingerprint);
    +396        }
    +397
    +398        /** @return Database bound to this manager. */
    +399        public @Nonnull DatabaseId getDatabase() {
    +400            return database;
    +401        }
    +402
    +403        /** @return Closed/open state of this manager. */
    +404        public boolean getClosed() {
    +405            return closed.get();
    +406        }
    +407
    +408        /**
    +409         * Returns the full set of known configured Spanner managers, JVM-wide. This is mostly useful as a utility to
    +410         * shut down connections globally when needed (for instance, during testing).
    +411         *
    +412         * @return Unmodifiable list of weak references to all known-active managers.
    +413         */
    +414        public @Nonnull Collection<SpannerAdapter> allAdapters() {
    +415            if (logging.isTraceEnabled())
    +416                logging.trace("All adapters requested from 'SpannerManager' at ID '{}'. Total: {}.",
    +417                        this.id,
    +418                        this.adapterCache.size());
    +419            return Collections.unmodifiableCollection(this.adapterCache.values());
    +420        }
    +421
    +422        /**
    +423         * Close all active Spanner connections tracked or controlled by this configured manager.
    +424         *
    +425         * @throws RuntimeException If the underlying connections raise IO exceptions.
    +426         */
    +427        @Override
    +428        public void close() {
    +429            if (logging.isTraceEnabled())
    +430                logging.trace("Close requested for `SpannerManager` at ID '{}'.", this.id);
    +431            if (this.getClosed()) {
    +432                if (logging.isDebugEnabled())
    +433                    logging.debug("Close requested, but but `SpannerManager` at ID '{}' is already closed.", this.id);
    +434                return;
    +435            }
    +436            try {
    +437                if (logging.isInfoEnabled())
    +438                    logging.info("Closing `SpannerManager` at ID '{}'.", this.id);
    +439                closed.compareAndSet(false, true);
    +440                allAdapters().forEach(SpannerAdapter::close);
    +441            } finally {
    +442                adapterCache.clear();
    +443                configuredManagers.remove(this.id);  // deregister self
    +444            }
    +445        }
    +446
    +447        // -- Configured Manager: Getters -- //
    +448
    +449        /** @return Settings for this configured manager. */
    +450        public @Nonnull SpannerDriverSettings getSettings() {
    +451            return settings.orElse(SpannerDriverSettings.DEFAULTS);
    +452        }
    +453
    +454        /** @return Cache applied to reads, if any. */
    +455        public @Nonnull Optional<CacheDriver<Message, Message>> getCache() {
    +456            return cache;
    +457        }
    +458    }
    +459}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerMutationSerializer.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerMutationSerializer.html new file mode 100644 index 000000000..08c19a28e --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerMutationSerializer.html @@ -0,0 +1,495 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import com.google.cloud.ByteArray;
    +016import com.google.cloud.Date;
    +017import com.google.cloud.Timestamp;
    +018import com.google.cloud.spanner.*;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.protobuf.ByteString;
    +021import com.google.protobuf.Descriptors;
    +022import com.google.protobuf.InvalidProtocolBufferException;
    +023import com.google.protobuf.Message;
    +024import com.google.protobuf.util.JsonFormat;
    +025import gust.backend.model.ModelDeflateException;
    +026import gust.backend.model.ModelSerializer;
    +027import gust.backend.runtime.Logging;
    +028import org.slf4j.Logger;
    +029import tools.elide.core.FieldType;
    +030import tools.elide.core.SpannerFieldOptions;
    +031import tools.elide.core.SpannerOptions;
    +032import tools.elide.core.TableFieldOptions;
    +033
    +034import javax.annotation.Nonnull;
    +035import javax.annotation.Nullable;
    +036import java.util.*;
    +037
    +038import static gust.backend.driver.spanner.SpannerUtil.*;
    +039import static gust.backend.model.ModelMetadata.*;
    +040
    +041
    +042/**
    +043 * Implements a specialized serializer, capable of converting generated {@link Message}-derived objects into Spanner
    +044 * {@link Mutation} records during write operations.
    +045 *
    +046 * @see SpannerStructDeserializer For an equivalent specialized de-serializer, working atop Spanner {@link Struct}s.
    +047 * @param <Model> Typed {@link Message} which implements a concrete model object structure, as defined and annotated by
    +048 *                the core Gust annotations.
    +049 */
    +050@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
    +051public final class SpannerMutationSerializer<Model extends Message> implements ModelSerializer<Model, Mutation> {
    +052    private static final Logger logging = Logging.logger(SpannerMutationSerializer.class);
    +053    private static final JsonFormat.Printer defaultJsonPrinter = JsonFormat
    +054            .printer()
    +055            .includingDefaultValueFields()
    +056            .omittingInsignificantWhitespace()
    +057            .sortingMapKeys();
    +058
    +059    /** Model structure for the backing instance. */
    +060    private final @Nonnull Descriptors.Descriptor model;
    +061
    +062    /** Settings for the Spanner driver itself. */
    +063    private final @Nonnull SpannerDriverSettings driverSettings;
    +064
    +065    /** Target mutation we intend to initialize. */
    +066    private volatile @Nullable Mutation.WriteBuilder target = null;
    +067
    +068    /**
    +069     * Private constructor.
    +070     *
    +071     * @param instance Default instance for the model we intend to serialize.
    +072     * @param driverSettings Settings for the Spanner driver itself.
    +073     */
    +074    SpannerMutationSerializer(@Nonnull Model instance,
    +075                              @Nonnull SpannerDriverSettings driverSettings) {
    +076        this.driverSettings = driverSettings;
    +077        this.model = instance.getDescriptorForType();
    +078    }
    +079
    +080    /**
    +081     * Initialize a new mutation cycle for the provided mutation builder object. We hold this object and fill it in when
    +082     * the next serialization call occurs. After serialization, the value is cleared for later use.
    +083     *
    +084     * @param initial In-progress mutation to initialize.
    +085     */
    +086    @Nonnull SpannerMutationSerializer<Model> initializeMutation(@Nonnull Mutation.WriteBuilder initial) {
    +087        target = initial;
    +088        return this;
    +089    }
    +090
    +091    /**
    +092     * Inject a model instance's key ID at the expected place in a given Spanner {@link Mutation} target, given any
    +093     * annotations present on the key model field.
    +094     *
    +095     * <p>Keys in Gust are implemented as objects. Typically these keys have a property present on them which is itself
    +096     * annotated as an ID property. This method makes sure that we collapse that ID into the key's column in the table,
    +097     * which should store a native value (i.e. an ID string or number) rather than an encoded message or sub-collection
    +098     * entry, which is the norm for sub-messages outside of special cases like keys, timestamps, and dates.</p>
    +099     *
    +100     * @param keyField Pointer to the key field on the model.
    +101     * @param instance Message instance where we should pluck the key/ID from.
    +102     * @param target Mutation target where we should write the resulting ID value.
    +103     * @throws IllegalStateException For invalid key types. Only `STRING` and `INT64` are supported as column types in
    +104     *         Spanner for primary keys.
    +105     */
    +106    @VisibleForTesting
    +107    void collapseRowKey(@Nonnull FieldPointer keyField,
    +108                        @Nonnull Message instance,
    +109                        @Nonnull Mutation.WriteBuilder target) {
    +110        var idField = idField(instance).orElseThrow();
    +111        var id = id(instance).orElseThrow();
    +112        var column = resolveKeyColumn(idField, driverSettings);
    +113        var valueBinder = target.set(column);
    +114        var type = resolveKeyType(idField);
    +115
    +116        if (type.getCode() == Type.Code.STRING) {
    +117            valueBinder.to((String)id);
    +118        } else if (type.getCode() == Type.Code.INT64) {
    +119            valueBinder.to((Long)id);
    +120        } else {
    +121            throw new IllegalStateException(
    +122                String.format("Unsupported key field type: '%s'.", keyField.getField().getType().name()));
    +123        }
    +124    }
    +125
    +126    @SuppressWarnings("unchecked")
    +127    <Primitive> void bindValueTyped(@Nonnull Descriptors.FieldDescriptor field,
    +128                                    @Nonnull ValueBinder<Primitive> valueBinder,
    +129                                    @Nonnull Type columnType,
    +130                                    @Nonnull Object rawValue,
    +131                                    @Nonnull Optional<SpannerFieldOptions> spannerOpts,
    +132                                    @Nonnull Optional<TableFieldOptions> columnOpts) {
    +133        boolean repeated = columnType.getCode() == Type.Code.ARRAY;
    +134        Objects.requireNonNull(rawValue, "should never get `NULL` for present protocol buffer value");
    +135
    +136        Type innerType = repeated ?
    +137                columnType.getArrayElementType() :
    +138                columnType;
    +139
    +140        switch (innerType.getCode()) {
    +141            case BOOL:
    +142                if (repeated) {
    +143                    valueBinder.toBoolArray((Iterable<Boolean>)rawValue);
    +144                } else {
    +145                    valueBinder.to((Boolean)rawValue);
    +146                }
    +147                break;
    +148
    +149            case INT64:
    +150                // we need to check if it's an ENUM, and if ENUMs-as-strings is off. if both of these conditions
    +151                // match, we need to resolve the enumerated instance and assign that instead.
    +152                if (field.getType() == Descriptors.FieldDescriptor.Type.ENUM &&
    +153                    driverSettings.enumsAsNumbers()) {
    +154                    var descriptor = (Descriptors.EnumValueDescriptor)rawValue;
    +155                    valueBinder.to(descriptor.getNumber());
    +156
    +157                } else {
    +158                    // otherwise, we should treat it as a native numeric type, repeated or singular.
    +159                    if (repeated) {
    +160                        valueBinder.toInt64Array((Iterable<Long>) rawValue);
    +161                    } else {
    +162                        if (rawValue instanceof Long) {
    +163                            valueBinder.to((Long) rawValue);
    +164                        } else {
    +165                            valueBinder.to((Integer) rawValue);
    +166                        }
    +167                    }
    +168                }
    +169                break;
    +170
    +171            case FLOAT64:
    +172                if (repeated) {
    +173                    valueBinder.toFloat64Array((Iterable<Double>)rawValue);
    +174                } else {
    +175                    valueBinder.to((Double)rawValue);
    +176                }
    +177                break;
    +178
    +179            case STRING:
    +180                // special case: JSON fields
    +181                if (field.getType() == Descriptors.FieldDescriptor.Type.MESSAGE &&
    +182                    (spannerOpts.isPresent() && spannerOpts.get().getType() == SpannerOptions.SpannerType.JSON) ||
    +183                    (columnOpts.isPresent() && columnOpts.get().getSptype() == SpannerOptions.SpannerType.JSON)) {
    +184                    // it's a repeated JSON field, so serialize it as an array of sub-messages instead of strings.
    +185                    try {
    +186                        if (repeated) {
    +187                            var arr = new LinkedList<String>();
    +188                            for (var encodableModel : (Iterable<Message>) rawValue) {
    +189                                arr.add(defaultJsonPrinter.print(encodableModel));
    +190                            }
    +191                            valueBinder.toStringArray(arr);
    +192                        } else {
    +193                            // it's a singular JSON field, so serialize it as a sub-message instead of a string.
    +194                            valueBinder.to(defaultJsonPrinter.print((Message)rawValue));
    +195                        }
    +196                    } catch (InvalidProtocolBufferException ipbe) {
    +197                        logging.error("!! Invalid protocol buffer for JSON encoding.", ipbe);
    +198                        throw new IllegalStateException(ipbe);
    +199                    }
    +200                } else {
    +201                    // we need to check if it's an ENUM, and if ENUMs-as-strings is on. if both of these conditions
    +202                    // match, we need to resolve the enumerated instance and assign that instead.
    +203                    if (field.getType() == Descriptors.FieldDescriptor.Type.ENUM &&
    +204                        !driverSettings.enumsAsNumbers()) {
    +205                        var descriptor = (Descriptors.EnumValueDescriptor)rawValue;
    +206                        valueBinder.to(descriptor.getName());
    +207
    +208                    } else {
    +209                        // it's not a JSON field or an enum, or enum serialization as strings isn't on, so serialize it
    +210                        // as a regular string value (either singular or repeated).
    +211                        if (repeated) {
    +212                            valueBinder.toStringArray((Iterable<String>)rawValue);
    +213                        } else {
    +214                            valueBinder.to((String)rawValue);
    +215                        }
    +216                    }
    +217                }
    +218                break;
    +219
    +220            case BYTES:
    +221                if (repeated) {
    +222                    var arr = new LinkedList<ByteArray>();
    +223                    for (var bytes : (Iterable<ByteString>) rawValue) {
    +224                        arr.add(ByteArray.copyFrom(bytes.asReadOnlyByteBuffer()));
    +225                    }
    +226                    valueBinder.toBytesArray(arr);
    +227                } else {
    +228                    valueBinder.to(ByteArray.copyFrom(((ByteString)rawValue).asReadOnlyByteBuffer()));
    +229                }
    +230                break;
    +231
    +232            case STRUCT:
    +233                throw new IllegalArgumentException(String.format(
    +234                    "STRUCT types are expressions and are not valid for storage. Please use either a `JSON` field or " +
    +235                    "valid sub-collection binding, at field path '%s'.",
    +236                    field.getFullName()
    +237                ));
    +238
    +239            case TIMESTAMP:
    +240                if (com.google.protobuf.Timestamp.getDescriptor()
    +241                        .getFullName()
    +242                        .equals(field.getMessageType().getFullName())) {
    +243                    if (repeated) {
    +244                        var arr = new LinkedList<Timestamp>();
    +245                        for (var ts : (Iterable<com.google.protobuf.Timestamp>) rawValue) {
    +246                            arr.add(SpannerTemporalConverter.cloudTimestampFromProto(ts));
    +247                        }
    +248                        valueBinder.toTimestampArray(arr);
    +249                    } else {
    +250                        valueBinder.to(SpannerTemporalConverter.cloudTimestampFromProto(
    +251                                ((com.google.protobuf.Timestamp)rawValue)));
    +252                    }
    +253                    break;
    +254                }
    +255
    +256                // any sub-message type other than `Timestamp` is invalid.
    +257                throw new IllegalStateException(
    +258                        "Type 'TIMESTAMP' is not yet supported for use with record " +
    +259                        "'" + field.getMessageType().getFullName() + "'.");
    +260
    +261            case DATE:
    +262                if (com.google.type.Date.getDescriptor()
    +263                    .getFullName()
    +264                    .equals(field.getMessageType().getFullName())) {
    +265                    if (repeated) {
    +266                        var arr = new LinkedList<Date>();
    +267                        for (var date : (Iterable<com.google.type.Date>) rawValue) {
    +268                            arr.add(SpannerTemporalConverter.cloudDateFromProto(date));
    +269                        }
    +270                        valueBinder.toDateArray(arr);
    +271                    } else {
    +272                        valueBinder.to(SpannerTemporalConverter.cloudDateFromProto(
    +273                                ((com.google.type.Date)rawValue)));
    +274                    }
    +275                    break;
    +276                }
    +277
    +278                // any sub-message type other than `Date` is invalid.
    +279                throw new IllegalStateException(
    +280                        "Type 'DATE' is not yet supported for use with record " +
    +281                        "'" + field.getMessageType().getFullName() + "'.");
    +282
    +283            case NUMERIC:
    +284                // source fields for `NUMERIC` columns can only be strings. while we could safely store 32-bit/64-bit
    +285                // signed or unsigned (fixed or unfixed) integer types, and float/double types, safely in this field,
    +286                // we cannot safely decode them from this field, which may break the `long` and `short` ceilings. so,
    +287                // following Google's advice on the matter (https://cloud.google.com/spanner/docs/working-with-numerics)
    +288                // they are implemented as strings.
    +289                if (field.getType() == Descriptors.FieldDescriptor.Type.STRING) {
    +290                    valueBinder.to(Value.string((String)rawValue));
    +291                    break;
    +292                }
    +293
    +294                // throw illegal state
    +295                throw new IllegalStateException(
    +296                    "NUMERIC fields must be expressed as proto-strings, in exponent notation if necessary. For " +
    +297                    "more information, please see the Cloud Spanner documentation regarding NUMERIC types: " +
    +298                    "https://cloud.google.com/spanner/docs/working-with-numerics");
    +299
    +300            case ARRAY: throw new IllegalStateException("Illegal array received for flattened serialization.");
    +301        }
    +302    }
    +303
    +304    /**
    +305     * Collapse an individual {@link Message} field into the expected column slow against a given Spanner
    +306     * {@link Mutation} record, which is in the process of being assembled.
    +307     *
    +308     * <p>Each individual model field which is eligible for storage in Spanner must be resolvable to a valid column name
    +309     * and type. This process is usually conducted via model annotations, with sensible defaults built in. Before a
    +310     * value can be duly bound, this method resolves such ancillary values and feeds them into
    +311     * {@link #bindValueTyped(Descriptors.FieldDescriptor, ValueBinder, Type, Object, Optional, Optional)}, where the
    +312     * binding itself takes place.</p>
    +313     *
    +314     * @see #bindValueTyped(Descriptors.FieldDescriptor, ValueBinder, Type, Object, Optional, Optional) Inner post-check
    +315     *      typed value injection.
    +316     * @param instance Model instance we should pluck the field value from.
    +317     * @param fieldPointer Resolved pointer to the model field we are collapsing into a column value.
    +318     * @param target Mutation target we should write the resulting value to, as applicable.
    +319     */
    +320    @VisibleForTesting
    +321    void collapseColumnField(@Nonnull Model instance,
    +322                             @Nonnull FieldPointer fieldPointer,
    +323                             @Nonnull Mutation.WriteBuilder target) {
    +324        var field = fieldPointer.getField();
    +325        var fieldValue = pluck(instance, fieldPointer.getName());
    +326
    +327        if (!field.isRepeated() && !instance.hasField(field) || fieldValue.getValue().isEmpty() ||
    +328            field.isRepeated() && instance.getRepeatedFieldCount(field) < 1) {
    +329            // field has no value. skip, but log about it.
    +330            if (logging.isTraceEnabled())
    +331                logging.trace(
    +332                    "Field '{}' on model '{}' had no value. Skipping.",
    +333                    field,
    +334                    model.getFullName()
    +335                );
    +336            return;
    +337        }
    +338
    +339        // virtualize the key property, when encountered
    +340        if (!field.isRepeated() && matchFieldAnnotation(field, FieldType.KEY)) {
    +341            this.collapseRowKey(fieldPointer, instance, target);
    +342            return;
    +343        } else if (field.isRepeated() && matchFieldAnnotation(field, FieldType.KEY)) {
    +344            throw new IllegalStateException(
    +345                "Cannot make `KEY` field repeated (on model '" + field.getMessageType().getFullName() + "'."
    +346            );
    +347        }
    +348
    +349        // resolve any column or spanner options
    +350        var columnOpts = columnOpts(fieldPointer);
    +351        var spannerOpts = spannerOpts(fieldPointer);
    +352
    +353        // resolve column name...
    +354        var columnName = resolveColumnName(
    +355            fieldPointer,
    +356            spannerOpts,
    +357            columnOpts,
    +358            driverSettings
    +359        );
    +360
    +361        // then type...
    +362        var columnType = resolveColumnType(
    +363            fieldPointer,
    +364            spannerOpts,
    +365            columnOpts,
    +366            driverSettings
    +367        );
    +368
    +369        // then raw value...
    +370        var valueBinder = target.set(columnName);
    +371        var rawValue = fieldValue.getValue().orElseThrow();
    +372
    +373        bindValueTyped(
    +374            field,
    +375            valueBinder,
    +376            columnType,
    +377            rawValue,
    +378            spannerOpts,
    +379            columnOpts
    +380        );
    +381    }
    +382
    +383    /**
    +384     * Construct a {@link Mutation} serializer for the provided <b>instance</b>.
    +385     *
    +386     * @param instance Model instance to acquire a mutation serializer for.
    +387     * @param driverSettings Settings for the Spanner driver itself.
    +388     * @param <M> Model type to deserialize.
    +389     * @return Snapshot deserializer instance.
    +390     */
    +391    @SuppressWarnings("SameParameterValue")
    +392    static <M extends Message> SpannerMutationSerializer<M> forModel(@Nonnull M instance,
    +393                                                                     @Nonnull SpannerDriverSettings driverSettings) {
    +394        return new SpannerMutationSerializer<>(
    +395            instance,
    +396            driverSettings
    +397        );
    +398    }
    +399
    +400    /** @inheritDoc */
    +401    @Override
    +402    public @Nonnull Mutation deflate(@Nonnull Model input) throws ModelDeflateException {
    +403        // `initializeMutation` must be called before `deflate`. it is force-emptied each cycle to prevent
    +404        var writeBuilder = this.target;
    +405        Objects.requireNonNull(input, "cannot deflate `null` input for Spanner mutation");
    +406        Objects.requireNonNull(writeBuilder, "cannot deflate model with no initialized write target.");
    +407
    +408        // stream all non-recursive model fields, filtering down only to fields which are eligible for storage in
    +409        // Spanner. for each field, invoke `collapseColumnField`, which mutates the held builder in-place.
    +410        forEachField(
    +411          model,
    +412          Optional.of(onlySpannerEligibleFields(driverSettings))
    +413        ).forEach((field) ->
    +414            this.collapseColumnField(input, field, writeBuilder)
    +415        );
    +416
    +417        final Mutation mutation = writeBuilder.build();
    +418        this.target = null;
    +419        return mutation;
    +420    }
    +421}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerStructDeserializer.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerStructDeserializer.html new file mode 100644 index 000000000..26e988197 --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerStructDeserializer.html @@ -0,0 +1,599 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import com.google.cloud.spanner.Mutation;
    +016import com.google.cloud.spanner.Struct;
    +017import com.google.cloud.spanner.Type;
    +018import com.google.cloud.spanner.Value;
    +019import com.google.protobuf.Descriptors;
    +020import com.google.protobuf.InvalidProtocolBufferException;
    +021import com.google.protobuf.Message;
    +022import com.google.protobuf.Timestamp;
    +023import com.google.protobuf.util.JsonFormat;
    +024import com.google.type.Date;
    +025import gust.backend.model.ModelDeserializer;
    +026import gust.backend.model.ModelInflateException;
    +027import gust.backend.model.ModelMetadata;
    +028import gust.backend.runtime.Logging;
    +029import org.slf4j.Logger;
    +030import tools.elide.core.FieldType;
    +031import tools.elide.core.SpannerOptions;
    +032
    +033import javax.annotation.Nonnull;
    +034import javax.annotation.concurrent.Immutable;
    +035import javax.annotation.concurrent.ThreadSafe;
    +036import java.util.*;
    +037import java.util.stream.Collectors;
    +038
    +039import static gust.backend.driver.spanner.SpannerTemporalConverter.*;
    +040import static gust.backend.driver.spanner.SpannerUtil.*;
    +041import static gust.backend.model.ModelMetadata.*;
    +042
    +043import static java.lang.String.format;
    +044
    +045
    +046/**
    +047 * Implements a specialized de-serializer, capable of converting runtime-inhabited Spanner {@link Struct}-records into
    +048 * typed {@link Message}-derived objects.
    +049 *
    +050 * @see SpannerMutationSerializer For an equivalent specialized serializer, working atop Spanner {@link Mutation}s.
    +051 * @param <Model> Typed {@link Message} which implements a concrete model object structure, as defined and annotated by
    +052 *                the core Gust annotations.
    +053 */
    +054@Immutable
    +055@ThreadSafe
    +056public final class SpannerStructDeserializer<Model extends Message> implements ModelDeserializer<Struct, Model> {
    +057    private static final Logger logging = Logging.logger(SpannerStructDeserializer.class);
    +058    private static final @Nonnull JsonFormat.Parser defaultJsonParser = JsonFormat.parser()
    +059            .ignoringUnknownFields();
    +060
    +061    /** Encapsulated object deserializer. */
    +062    private final Model defaultInstance;
    +063
    +064    /** Descriptor for the root model we're decoding. */
    +065    private final Descriptors.Descriptor modelDescriptor;
    +066
    +067    /** Settings for the Spanner driver. */
    +068    private final @Nonnull SpannerDriverSettings driverSettings;
    +069
    +070    /**
    +071     * Private constructor.
    +072     *
    +073     * @param instance Model instance to deserialize.
    +074     * @param driverSettings Settings for the Spanner driver.
    +075     */
    +076    private SpannerStructDeserializer(@Nonnull Model instance,
    +077                                      @Nonnull SpannerDriverSettings driverSettings) {
    +078        this.defaultInstance = instance;
    +079        this.driverSettings = driverSettings;
    +080        this.modelDescriptor = instance.getDescriptorForType();
    +081    }
    +082
    +083    /**
    +084     * Construct a {@link Struct} deserializer for the provided <b>instance</b>.
    +085     *
    +086     * @param instance Model instance to acquire a data deserializer for.
    +087     * @param driverSettings Settings for the Spanner driver itself.
    +088     * @param <M> Model type to deserialize.
    +089     * @return Snapshot deserializer instance.
    +090     */
    +091    @SuppressWarnings("SameParameterValue")
    +092    public static <M extends Message> SpannerStructDeserializer<M> forModel(
    +093            @Nonnull M instance,
    +094            @Nonnull SpannerDriverSettings driverSettings) {
    +095        return new SpannerStructDeserializer<>(
    +096            instance,
    +097            driverSettings
    +098        );
    +099    }
    +100
    +101    /**
    +102     * Inflate a field from a Spanner row ID into a message/model Key instance, with the provided value object.
    +103     *
    +104     * <p>The provided field pointer is used to fill in the primary key ID pulled from a given Spanner row result. That
    +105     * ID is spliced into the key record, which is then spliced into the target builder.</p>
    +106     *
    +107     * @param target Target message builder.
    +108     * @param value Resolved value for the ID.
    +109     */
    +110    private void inflateRowKey(@Nonnull Message.Builder target,
    +111                               @Nonnull Value value) {
    +112        Objects.requireNonNull(value, "cannot inflate Spanner key from NULL value");
    +113        var baseInstance = target.getDefaultInstanceForType();
    +114        var keyField = keyField(baseInstance).orElseThrow();
    +115        var idField = idField(baseInstance).orElseThrow();
    +116
    +117        var keyBuilder = target.newBuilderForField(keyField.getField());
    +118        var keyType = resolveKeyType(idField);
    +119
    +120        // splice the ID into the key
    +121        if (keyType.getCode() == Type.Code.STRING) {
    +122            spliceIdBuilder(keyBuilder, Optional.of(value.getString()));
    +123        } else if (keyType.getCode() == Type.Code.INT64) {
    +124            // special case: stringify if so instructed
    +125            if (idField.getField().getType().equals(Descriptors.FieldDescriptor.Type.STRING)) {
    +126                spliceIdBuilder(keyBuilder, Optional.of(String.valueOf(value.getInt64())));
    +127            } else {
    +128                spliceIdBuilder(keyBuilder, Optional.of(value.getInt64()));
    +129            }
    +130        } else {
    +131            throw new IllegalStateException(format("Unsupported key type: '%s'.", keyType.getCode().name()));
    +132        }
    +133
    +134        // splice the key into the model
    +135        target.setField(keyField.getField(), keyBuilder.build());
    +136    }
    +137
    +138    /**
    +139     * Resolve a Cloud Spanner column and value for the provided model field, from the source row structure, if one
    +140     * is present.
    +141     *
    +142     * <p>If the column has no value, it is skipped. If the column has a value present, it is decoded according to the
    +143     * Cloud Spanner {@link Type} specified for the column, each of which are mapped to primitive Protocol Buffer
    +144     * object field types.</p>
    +145     *
    +146     * @param target Target protocol buffer builder to fill in.
    +147     * @param source Row result from Spanner to fill from.
    +148     * @param fieldPointer Field we are resolving from the proto.
    +149     */
    +150    private void convergeColumnField(@Nonnull Message.Builder target,
    +151                                     @Nonnull Struct source,
    +152                                     @Nonnull ModelMetadata.FieldPointer fieldPointer) {
    +153        // resolve any generic column options and spanner extension options
    +154        var columnOpts = columnOpts(fieldPointer);
    +155        var spannerOpts = spannerOpts(fieldPointer);
    +156        var columnName = resolveColumnName(fieldPointer.getField(), driverSettings);
    +157
    +158        if (source.isNull(columnName)) {
    +159            if (logging.isTraceEnabled())
    +160                logging.trace("Resolved column value for field '{}' was NULL. Skipping.",
    +161                    fieldPointer.getName());
    +162        } else {
    +163            // resolve the expected column index
    +164            var columnValue = resolveColumnValue(
    +165                source,
    +166                fieldPointer,
    +167                spannerOpts,
    +168                columnOpts,
    +169                driverSettings
    +170            );
    +171
    +172            // first up, check to see if this is a key or ID field, and decode it properly if so
    +173            var field = fieldPointer.getField();
    +174            if (matchFieldAnnotation(field, FieldType.ID)) {
    +175                this.inflateRowKey(target, columnValue);
    +176                return;
    +177            } else if (matchFieldAnnotation(field, FieldType.KEY)) {
    +178                throw new IllegalStateException("Should not get KEY-type fields in convergence loop.");
    +179            }
    +180
    +181            // if so directed, check the expected type against the real type indicated by Spanner. if this
    +182            // feature is turned off, soft logging errors turn into value exceptions.
    +183            if (driverSettings.checkExpectedTypes()) {
    +184                // resolve the expected column type
    +185                var columnType = resolveColumnType(
    +186                    fieldPointer,
    +187                    spannerOpts,
    +188                    columnOpts,
    +189                    driverSettings
    +190                );
    +191
    +192                if (logging.isTraceEnabled())
    +193                    logging.trace("Resolved Spanner type for field '{}': '{}'",
    +194                        fieldPointer.getName(),
    +195                        columnType.toString());
    +196
    +197                if (!columnType.getCode().equals(columnValue.getType().getCode())) {
    +198                    logging.error(
    +199                            "Type mismatch: field '{}' expected type {}, but got {}.",
    +200                        fieldPointer.getField().getFullName(),
    +201                        columnType.getCode().name(),
    +202                        columnValue.getType().getCode().name()
    +203                    );
    +204                    return;
    +205                }
    +206            }
    +207
    +208            // if we make it this far, the following conditions are true, and we are ready to copy a value from
    +209            // the source struct into the proto:
    +210            //
    +211            // 1) we have a non-NULL value in Spanner for a given column, with a resolved name and type.
    +212            // 2) the name and type match the proto model, where we also have a resolved proto native type.
    +213            // 3) we are certainly operating on a leaf field.
    +214            boolean repeated = columnValue.getType().getCode() == Type.Code.ARRAY;
    +215            Type.Code innerType = repeated ?
    +216                columnValue.getType().getArrayElementType().getCode() :
    +217                columnValue.getType().getCode();
    +218
    +219            switch (innerType) {
    +220                case BOOL:
    +221                    spliceBuilder(
    +222                        target,
    +223                        fieldPointer,
    +224                        Optional.of(repeated ? columnValue.getBoolArray() : columnValue.getBool())
    +225                    );
    +226                    break;
    +227                case INT64:
    +228                    spliceBuilder(
    +229                        target,
    +230                        fieldPointer,
    +231                        Optional.of(repeated ? columnValue.getInt64Array() : columnValue.getInt64())
    +232                    );
    +233                    break;
    +234
    +235                case FLOAT64:
    +236                    spliceBuilder(
    +237                        target,
    +238                        fieldPointer,
    +239                        Optional.of(repeated ? columnValue.getFloat64Array() : columnValue.getFloat64())
    +240                    );
    +241                    break;
    +242
    +243                case STRING:
    +244                    // special case: string fields containing model-compliant JSON
    +245                    if (fieldPointer.getField().getType() == Descriptors.FieldDescriptor.Type.MESSAGE &&
    +246                        (spannerOpts.isPresent() && spannerOpts.get().getType() == SpannerOptions.SpannerType.JSON ||
    +247                         columnOpts.isPresent() && columnOpts.get().getSptype() == SpannerOptions.SpannerType.JSON)) {
    +248                        int modelIndex = 0;
    +249                        try {
    +250                            if (repeated) {
    +251                                // decode as a list of JSON-encoded model instances.
    +252                                var encodedModels = columnValue.getStringArray();
    +253                                var modelResults = new ArrayList<>(encodedModels.size());
    +254                                for (var encodedModel : encodedModels) {
    +255                                    var subBuilder = target.newBuilderForField(fieldPointer.getField());
    +256                                    defaultJsonParser.merge(encodedModel, subBuilder);
    +257                                    modelResults.add(subBuilder.build());
    +258                                    modelIndex++;
    +259                                }
    +260
    +261                                // mount set of models on the target proto
    +262                                spliceBuilder(
    +263                                        target,
    +264                                        fieldPointer,
    +265                                        Optional.of(modelResults)
    +266                                );
    +267
    +268                            } else {
    +269                                // decode as a singular JSON-encoded model instance.
    +270                                var encodedModel = columnValue.getString();
    +271                                var subBuilder = target.newBuilderForField(fieldPointer.getField());
    +272                                defaultJsonParser.merge(encodedModel, subBuilder);
    +273                                spliceBuilder(
    +274                                        target,
    +275                                        fieldPointer,
    +276                                        Optional.of(subBuilder.build())
    +277                                );
    +278                            }
    +279
    +280                        } catch (InvalidProtocolBufferException invalidProtoException) {
    +281                            if (repeated) {
    +282                                logging.error(format(
    +283                                        "Failed to deserialize JSON model at path '%s', at index %s.",
    +284                                        fieldPointer.getField().getFullName(),
    +285                                        modelIndex
    +286                                ), invalidProtoException);
    +287                            } else {
    +288                                logging.error(format(
    +289                                        "Failed to deserialize JSON model at path '%s'.",
    +290                                        fieldPointer.getField().getFullName()
    +291                                ), invalidProtoException);
    +292                            }
    +293
    +294                            throw new IllegalStateException(invalidProtoException);
    +295                        }
    +296                    } else {
    +297                        spliceBuilder(
    +298                            target,
    +299                            fieldPointer,
    +300                            Optional.of(repeated ? columnValue.getStringArray() : columnValue.getString())
    +301                        );
    +302                    }
    +303                    break;
    +304
    +305                case BYTES:
    +306                    spliceBuilder(
    +307                        target,
    +308                        fieldPointer,
    +309                        Optional.of(repeated ? columnValue.getBytesArray() : columnValue.getBytes())
    +310                    );
    +311                    break;
    +312
    +313                case NUMERIC:
    +314                    if (field.getType() == Descriptors.FieldDescriptor.Type.STRING) {
    +315                        // grab the string value and splice
    +316                        spliceBuilder(
    +317                            target,
    +318                            fieldPointer,
    +319                            Optional.of(repeated ? columnValue.getStringArray() : columnValue.getString())
    +320                        );
    +321                        break;
    +322                    }
    +323
    +324                    // throw illegal state
    +325                    throw new IllegalStateException(
    +326                        "NUMERIC fields must be expressed as proto-strings, in exponent notation if necessary. For " +
    +327                        "more information, please see the Cloud Spanner documentation regarding NUMERIC types: " +
    +328                        "https://cloud.google.com/spanner/docs/working-with-numerics");
    +329
    +330                case TIMESTAMP:
    +331                    // extract timestamp value
    +332                    var timestampValue = columnValue.getTimestamp();
    +333
    +334                    switch (field.getType()) {
    +335                        case UINT64:
    +336                        case FIXED64:
    +337                            // we're being asked to put a Google Cloud timestamp record into an unsigned integer field,
    +338                            // with a `long`-style size. this is the only possible safe native conversion.
    +339                            spliceBuilder(
    +340                                target,
    +341                                fieldPointer,
    +342                                Optional.of((timestampValue.getSeconds() * 1000) + timestampValue.getNanos())
    +343                            );
    +344                            break;
    +345
    +346                        case STRING:
    +347                            // we're being asked to put a Google Cloud timestamp record into a string field. in this
    +348                            // case, the adapter leverages any date options or otherwise defaults to ISO8601.
    +349                            spliceBuilder(
    +350                                target,
    +351                                fieldPointer,
    +352                                Optional.of(timestampValue.toString())
    +353                            );
    +354                            break;
    +355
    +356                        case MESSAGE:
    +357                            // if we have a sub-message in the same spot as a timestamp, it's worth checking to see if
    +358                            // it's a native Google Cloud timestamp, in which case we can just use it. otherwise, if we
    +359                            // encounter a standard PB timestamp, we need to convert.
    +360                            if (Timestamp.getDescriptor().getFullName().equals(field.getMessageType().getFullName())) {
    +361                                spliceBuilder(
    +362                                    target,
    +363                                    fieldPointer,
    +364                                    Optional.of(protoTimestampFromCloud(timestampValue))
    +365                                );
    +366                                break;
    +367                            }
    +368
    +369                            // any other sub-message type represents an illegal state.
    +370                            throw new IllegalStateException(
    +371                                "Cannot convert Spanner TIMESTAMP value to unsupported sub-message type " +
    +372                                "'" + field.getMessageType().getFullName() + "'."
    +373                            );
    +374
    +375                        default:
    +376                            // any other expressed field represents an illegal state.
    +377                            throw new IllegalStateException(
    +378                                "Cannot convert Spanner TIMESTAMP value to proto-type '" + field.getType().name() + "'."
    +379                            );
    +380                    }
    +381
    +382                case DATE:
    +383                    // extract date value
    +384                    var dateValue = columnValue.getDate();
    +385                    if (field.getType() == Descriptors.FieldDescriptor.Type.STRING) {
    +386                        // we're being asked to put a Google Cloud structured date record into a string field. in
    +387                        // this case, the adapter leverages any date options or otherwise defaults to ISO8601.
    +388                        spliceBuilder(
    +389                                target,
    +390                                fieldPointer,
    +391                                Optional.of(format(
    +392                                        "%s/%s/%s",
    +393                                        dateValue.getYear(),
    +394                                        dateValue.getMonth(),
    +395                                        dateValue.getDayOfMonth()
    +396                                ))
    +397                        );
    +398                        break;
    +399                    } else if (field.getType() == Descriptors.FieldDescriptor.Type.MESSAGE &&
    +400                            Date.getDescriptor().getFullName().equals(field.getMessageType().getFullName())) {
    +401                        // if we have a sub-message in the same spot as a date, we need to convert to a standard
    +402                        // proto date, which is the only supported target here.
    +403                        spliceBuilder(
    +404                            target,
    +405                            fieldPointer,
    +406                            Optional.of(protoDateFromCloud(dateValue))
    +407                        );
    +408                        break;
    +409                    } else if (field.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
    +410                        throw new IllegalStateException(
    +411                            "Cannot convert Spanner DATE value to unsupported sub-message type " +
    +412                            "'" + field.getMessageType().getFullName() + "'."
    +413                        );
    +414                    } else  {
    +415                        // any other expressed field represents an illegal state.
    +416                        throw new IllegalStateException(
    +417                            "Cannot convert Spanner DATE value to proto-type '" + field.getType().name() + "'."
    +418                        );
    +419                    }
    +420
    +421                case ARRAY:
    +422                    throw new IllegalStateException(
    +423                        "Should not receive `ARRAY` field types for concrete decoding."
    +424                    );
    +425
    +426                case STRUCT:
    +427                    convergeFields(
    +428                        target.newBuilderForField(fieldPointer.getField()),
    +429                        fieldPointer.getField().getMessageType(),
    +430                        columnValue.getStruct(),
    +431                        columnValue.getStruct().getType().getStructFields()
    +432                    );
    +433            }
    +434        }
    +435    }
    +436
    +437    /**
    +438     * Resolve all fields for the provided `target` `model`, from the provided row struct `source`. If a value is
    +439     * present, decode it according to the assigned Spanner {@link Type} and any present or implied model
    +440     * annotations.
    +441     *
    +442     * <p>This method performs recursion for nested `STRUCT` objects inside the row. Such structures are interpreted
    +443     * according to model annotations present or implied on the target builder. In such cases, `base` is set to the
    +444     * root builder being filled in. For the initial case, `base` is always {@link Optional#empty()}.</p>
    +445     *
    +446     * @param target Target builder which we intend to fill in with values.
    +447     * @param model Model descriptor for the object we are building.
    +448     * @param source Source row structure to pull data from.
    +449     * @param fields List of fields present in the row, for efficient model filtering.
    +450     */
    +451    private void convergeFields(@Nonnull Message.Builder target,
    +452                                @Nonnull Descriptors.Descriptor model,
    +453                                @Nonnull Struct source,
    +454                                @Nonnull List<Type.StructField> fields) {
    +455        if (logging.isDebugEnabled()) logging.trace(
    +456                "More than one column value present in row. Decoding as {}...",
    +457                modelDescriptor.getFullName());
    +458
    +459        // compute a set of projection fields
    +460        SortedSet<String> eligibleFields = fields.isEmpty() ? new TreeSet<>() : fields
    +461                .stream()
    +462                .map(Type.StructField::getName)
    +463                .collect(Collectors.toCollection(TreeSet::new));
    +464
    +465        forEachField(
    +466            model,
    +467            Optional.of(onlySpannerEligibleFields(eligibleFields, driverSettings)),
    +468            (pointer) -> {
    +469                // decide if we should recurse. we should never recurse for `JSON` fields, or for fields marked with
    +470                // `ignore` on the column or spanner options.
    +471                var spannerOpts = spannerOpts(pointer);
    +472                var columnOpts = columnOpts(pointer);
    +473                return !(
    +474                    (columnOpts.isPresent() && columnOpts.get().getIgnore()) ||
    +475                    (spannerOpts.isPresent() && spannerOpts.get().getIgnore()) ||
    +476                    (spannerOpts.isPresent() && spannerOpts.get().getType().equals(SpannerOptions.SpannerType.JSON))
    +477                );
    +478            }
    +479        ).forEach((fieldPointer) -> {
    +480            if (logging.isDebugEnabled()) logging.trace(
    +481                    "Converging eligible column field {}...",
    +482                    fieldPointer.getField().getFullName());
    +483
    +484            convergeColumnField(
    +485                target,
    +486                source,
    +487                fieldPointer
    +488            );
    +489        });
    +490    }
    +491
    +492    /** @inheritDoc */
    +493    @Override
    +494    public @Nonnull Model inflate(@Nonnull Struct rowStruct) throws ModelInflateException {
    +495        Objects.requireNonNull(rowStruct, "cannot inflate null row struct from spanner");
    +496
    +497        // grab field count and begin iterating over fields, and assigning by type
    +498        var fieldCount = rowStruct.getColumnCount();
    +499        if (fieldCount > 0 && logging.isDebugEnabled()) logging.debug(
    +500                "Inflating {} fields from Spanner row struct.",
    +501                fieldCount);
    +502
    +503        if (fieldCount < 1) {
    +504            logging.warn("Empty rowStruct. Discarding.");
    +505            //noinspection unchecked
    +506            return (Model)defaultInstance.newBuilderForType().build();
    +507        } else {
    +508            if (logging.isTraceEnabled()) logging.trace(
    +509                    "More than one column value present in row. Decoding as {}...",
    +510                    modelDescriptor.getFullName());
    +511
    +512            // create a new builder, converge against it from the source row structure.
    +513            var builder = defaultInstance.newBuilderForType();
    +514            convergeFields(
    +515                builder,
    +516                modelDescriptor,
    +517                rowStruct,
    +518                rowStruct.getType().getStructFields()
    +519            );
    +520
    +521            //noinspection unchecked
    +522            return (Model)builder.build();
    +523        }
    +524    }
    +525}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerTemporalConverter.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerTemporalConverter.html new file mode 100644 index 000000000..0d00933b7 --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerTemporalConverter.html @@ -0,0 +1,153 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import com.google.cloud.Date;
    +016import com.google.cloud.Timestamp;
    +017
    +018import javax.annotation.Nonnull;
    +019import javax.annotation.concurrent.ThreadSafe;
    +020
    +021
    +022/**
    +023 * Serialize back and forth between Google Cloud / Protocol Buffer standard TIMESTAMP and DATE records.
    +024 */
    +025@ThreadSafe
    +026public final class SpannerTemporalConverter {
    +027    private SpannerTemporalConverter() { /* Disallow construction. */ }
    +028
    +029    /**
    +030     * Convert a regular/standard Protocol Buffers timestamp into a Google Cloud timestamp without loss of resolution.
    +031     *
    +032     * @param timestamp Standard timestamp to convert.
    +033     * @return Cloud timestamp value.
    +034     */
    +035    public static @Nonnull Timestamp cloudTimestampFromProto(com.google.protobuf.Timestamp timestamp) {
    +036        return Timestamp.fromProto(timestamp);
    +037    }
    +038
    +039    /**
    +040     * Convert a Google Cloud timestamp into a standard Protocol Buffers timestamp without loss of resolution.
    +041     *
    +042     * @param timestamp Cloud timestamp to convert.
    +043     * @return Protocol buffers timestamp value.
    +044     */
    +045    public static @Nonnull com.google.protobuf.Timestamp protoTimestampFromCloud(Timestamp timestamp) {
    +046        return com.google.protobuf.Timestamp.newBuilder()
    +047            .setSeconds(timestamp.getSeconds())
    +048            .setNanos(timestamp.getNanos())
    +049            .build();
    +050    }
    +051
    +052    /**
    +053     * Convert a regular/standard Protocol Buffers date into a Google Cloud date without loss of resolution.
    +054     *
    +055     * @param date Standard date to convert.
    +056     * @return Cloud date value.
    +057     */
    +058    public static @Nonnull Date cloudDateFromProto(com.google.type.Date date) {
    +059        return Date.fromYearMonthDay(
    +060            date.getYear(),
    +061            date.getMonth(),
    +062            date.getDay()
    +063        );
    +064    }
    +065
    +066    /**
    +067     * Convert a Google Cloud date into a standard Protocol Buffers date without loss of resolution.
    +068     *
    +069     * @param date Cloud date to convert.
    +070     * @return Protocol buffers date value.
    +071     */
    +072    public static @Nonnull com.google.type.Date protoDateFromCloud(Date date) {
    +073        return com.google.type.Date.newBuilder()
    +074            .setYear(date.getYear())
    +075            .setMonth(date.getMonth())
    +076            .setDay(date.getDayOfMonth())
    +077            .build();
    +078    }
    +079}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerTransportConfig.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerTransportConfig.html new file mode 100644 index 000000000..bf376e7cd --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerTransportConfig.html @@ -0,0 +1,98 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015
    +016/**
    +017 * Configures gRPC transport channels for use with Google Cloud Spanner, either in production or emulated circumstances
    +018 * for unit and integration testing.
    +019 *
    +020 * @see SpannerDriverSettings Main settings object which provides driver access to this transport configuration.
    +021 */
    +022public final class SpannerTransportConfig {
    +023    private SpannerTransportConfig() { /* Disallow construction. */ }
    +024}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/driver/spanner/SpannerUtil.html b/docs/java/src-html/gust/backend/driver/spanner/SpannerUtil.html new file mode 100644 index 000000000..70c5916ba --- /dev/null +++ b/docs/java/src-html/gust/backend/driver/spanner/SpannerUtil.html @@ -0,0 +1,890 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.driver.spanner;
    +014
    +015import com.google.cloud.spanner.Struct;
    +016import com.google.cloud.spanner.Type;
    +017import com.google.cloud.spanner.Value;
    +018import com.google.protobuf.Descriptors;
    +019import com.google.protobuf.Message;
    +020import com.google.protobuf.Timestamp;
    +021import com.google.type.Date;
    +022import gust.backend.runtime.Logging;
    +023import gust.util.Pair;
    +024import org.slf4j.Logger;
    +025import tools.elide.core.*;
    +026
    +027import javax.annotation.Nonnull;
    +028import java.util.*;
    +029import java.util.function.Predicate;
    +030import java.util.stream.Collectors;
    +031import java.util.stream.Stream;
    +032
    +033import static gust.backend.model.ModelMetadata.*;
    +034
    +035
    +036/**
    +037 * Provides utilities related to operations with Spanner, including tools for resolving column names and types from
    +038 * models and annotations, and producing default sets of columns for DDL statements.
    +039 */
    +040@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
    +041public final class SpannerUtil {
    +042    private static final Logger logging = Logging.logger(SpannerUtil.class);
    +043
    +044    private SpannerUtil() { /* disallow construction */ }
    +045
    +046    /** Default predicate to use when filtering for eligible Spanner fields. */
    +047    private static final @Nonnull Predicate<FieldPointer> defaultFieldPredicate;
    +048
    +049    static {
    +050        defaultFieldPredicate = (fieldPointer) -> {
    +051            var field = fieldPointer.getField();
    +052            var fieldOpts = fieldAnnotation(field, Datamodel.field);
    +053            var columnOpts = fieldAnnotation(field, Datamodel.column);
    +054            var spannerOpts = fieldAnnotation(field, Datamodel.spanner);
    +055
    +056            return !(
    +057                // field cannot be present if skipped via `column.ignore`
    +058                (columnOpts.isPresent() && columnOpts.get().getIgnore()) ||
    +059
    +060                // field cannot be present if skipped via `spanner.ignore`
    +061                (spannerOpts.isPresent() && spannerOpts.get().getIgnore()) ||
    +062
    +063                // properties marked as `INTERNAL` should always be withheld
    +064                (fieldOpts.isPresent() && fieldOpts.get().getVisibility() == FieldVisibility.INTERNAL)
    +065            );
    +066        };
    +067    }
    +068
    +069    /**
    +070     * For a given key field pointer, resolve the column name which should be used for the primary key in Spanner,
    +071     * according to the annotation structure present on the key.
    +072     *
    +073     * @see #resolveKeyType(FieldPointer) To resolve the primary key column type.
    +074     * @param idField Resolved field pointer to a given model's ID field.
    +075     * @param driverSettings Settings for the Spanner driver.
    +076     * @return Name of the column we should use for the primary key.
    +077     */
    +078    public static @Nonnull String resolveKeyColumn(@Nonnull FieldPointer idField,
    +079                                                   @Nonnull SpannerDriverSettings driverSettings) {
    +080        var fieldAnnos = fieldAnnotation(idField.getField(), Datamodel.field);
    +081        if (fieldAnnos.orElseThrow().getType() != FieldType.ID) {
    +082            throw new IllegalStateException(
    +083                "Cannot use non-ID field as key column: '" + idField.getField().getFullName() + "'."
    +084            );
    +085        }
    +086
    +087        // resolve key field and column name corresponding to that key field
    +088        return resolveColumnName(
    +089            idField,
    +090            spannerOpts(idField),
    +091            columnOpts(idField),
    +092            driverSettings
    +093        );
    +094    }
    +095
    +096    /**
    +097     * Given a schema-driven model or key object, determine the table name that should be used in Spanner. All keys and
    +098     * objects used with Spanner must have such annotations or use generated defaults. This method variant operates from
    +099     * a full message instance.
    +100     *
    +101     * @see #resolveTableName(Descriptors.Descriptor) For the wrapped version of this message.
    +102     * @param message Message type to resolve a table name for.
    +103     * @return Resolved table name, from annotations, or calculated as a default.
    +104     */
    +105    public static @Nonnull String resolveTableName(@Nonnull Message message) {
    +106        return resolveTableName(
    +107            message.getDescriptorForType()
    +108        );
    +109    }
    +110
    +111    /**
    +112     * Given a schema-driven model or key object, determine the table name that should be used in Spanner. All keys and
    +113     * objects used with Spanner must have such annotations or use generated defaults.
    +114     *
    +115     * @param message Message type to resolve a table name for.
    +116     * @return Resolved table name, from annotations, or calculated as a default.
    +117     */
    +118    public static @Nonnull String resolveTableName(@Nonnull Descriptors.Descriptor message) {
    +119        return modelAnnotation(
    +120            message,
    +121            Datamodel.table,
    +122            true
    +123        ).orElseThrow(() -> new IllegalArgumentException(
    +124            "Must annotate key or object model '" + message.getFullName() + "' with table name to use with Spanner."
    +125        )).getName();
    +126    }
    +127
    +128    /**
    +129     * For a given key field pointer, resolve the column type which should be used for the primary key in Spanner,
    +130     * according to the annotation structure present on the key.
    +131     *
    +132     * @see #resolveKeyColumn(FieldPointer, SpannerDriverSettings) To resolve the primary key column name.
    +133     * @param idField Resolved field pointer to a given model key's ID field.
    +134     * @return Spanner column type to use for this model's ID.
    +135     */
    +136    public static @Nonnull Type resolveKeyType(@Nonnull FieldPointer idField) {
    +137        // resolve the expected key column type. validate it on the way.
    +138        if (idField.getField().isRepeated()) {
    +139            throw new IllegalStateException(
    +140                String.format(
    +141                    "Unsupported key field type: '%s'. Keys cannot be repeated.",
    +142                    idField.getField().getType().name()));
    +143        } else if (idField.getField().getType() == Descriptors.FieldDescriptor.Type.STRING) {
    +144            return Type.string();
    +145        } else if (
    +146            idField.getField().getType() == Descriptors.FieldDescriptor.Type.UINT64 ||
    +147            idField.getField().getType() == Descriptors.FieldDescriptor.Type.FIXED64) {
    +148            return Type.int64();
    +149        } else {
    +150            throw new IllegalStateException(
    +151                String.format("Unsupported key field type: '%s'.", idField.getField().getType().name()));
    +152        }
    +153    }
    +154
    +155    /**
    +156     * Given a model field pointer which translates to a `STRING` or `BYTES` column in Spanner, determine the size that
    +157     * should be used when declaring the string column.
    +158     *
    +159     * <p>If an explicit column size is specified via model annotations, that prevails. If not, the default size value
    +160     * is used.</p>
    +161     *
    +162     * @param spannerOpts Spanner-specific options on the field.
    +163     * @param columnOpts Column-generic options on the field.
    +164     * @param settings Settings for the Spanner driver.
    +165     * @return Expected name of the field when expressed as a column in Spanner.
    +166     */
    +167    public static int resolveColumnSize(@Nonnull Descriptors.FieldDescriptor field,
    +168                                        @Nonnull Optional<SpannerFieldOptions> spannerOpts,
    +169                                        @Nonnull Optional<TableFieldOptions> columnOpts,
    +170                                        @Nonnull SpannerDriverSettings settings) {
    +171        if (spannerOpts.isPresent()) {
    +172            var spannerOptsUnwrapped = spannerOpts.get();
    +173            if (spannerOptsUnwrapped.getSize() > 0) {
    +174                return spannerOptsUnwrapped.getSize();
    +175            }
    +176        }
    +177        if (columnOpts.isPresent()) {
    +178            var columnOptsUnwrapped = columnOpts.get();
    +179            if (columnOptsUnwrapped.getSize() > 0) {
    +180                return columnOptsUnwrapped.getSize();
    +181            }
    +182        }
    +183        if (field.getType() == Descriptors.FieldDescriptor.Type.ENUM) {
    +184            return 32;  // special case: string ENUM fields should have a sensible default
    +185        }
    +186        return settings.defaultColumnSize();
    +187    }
    +188
    +189    /**
    +190     * Given a resolved field pointer resolve the expected/configured column name in Spanner for a given typed model
    +191     * field. If no specialized Spanner or table column annotations are present, fallback to a calculated default name.
    +192     *
    +193     * @see #resolveColumnName(Descriptors.FieldDescriptor, Optional, Optional, SpannerDriverSettings) For the full
    +194     *      un-sugared version of this method.
    +195     * @param field Pre-resolved model field descriptor.
    +196     * @return Expected name of the field when expressed as a column in Spanner.
    +197     */
    +198    public static @Nonnull String resolveColumnName(@Nonnull Descriptors.FieldDescriptor field) {
    +199        return resolveColumnName(
    +200            field,
    +201            SpannerDriverSettings.DEFAULTS
    +202        );
    +203    }
    +204
    +205    /**
    +206     * Given a resolved field pointer resolve the expected/configured column name in Spanner for a given typed model
    +207     * field. If no specialized Spanner or table column annotations are present, fallback to a calculated default name.
    +208     * Apply any active driver settings as well.
    +209     *
    +210     * @see #resolveColumnName(Descriptors.FieldDescriptor, Optional, Optional, SpannerDriverSettings) For the full
    +211     *      un-sugared version of this method.
    +212     * @param field Pre-resolved model field descriptor.
    +213     * @param settings Settings for the Spanner driver.
    +214     * @return Expected name of the field when expressed as a column in Spanner.
    +215     */
    +216    public static @Nonnull String resolveColumnName(@Nonnull Descriptors.FieldDescriptor field,
    +217                                                    @Nonnull SpannerDriverSettings settings) {
    +218        return resolveColumnName(
    +219            field,
    +220            spannerOpts(field),
    +221            columnOpts(field),
    +222            settings
    +223        );
    +224    }
    +225
    +226    /**
    +227     * Given a resolved field pointer and set of annotations, resolve the expected/configured column name in Spanner for
    +228     * a given typed model field. If no specialized Spanner or table column annotations are present, fallback to a
    +229     * calculated default name.
    +230     *
    +231     * @see #resolveColumnName(Descriptors.FieldDescriptor, Optional, Optional, SpannerDriverSettings) For the full
    +232     *      un-sugared version of this method.
    +233     * @param fieldPointer Pre-resolved model field pointer.
    +234     * @param spannerOpts Spanner-specific options on the field.
    +235     * @param columnOpts Column-generic options on the field.
    +236     * @param settings Settings for the Spanner driver.
    +237     * @return Expected name of the field when expressed as a column in Spanner.
    +238     */
    +239    public static @Nonnull String resolveColumnName(@Nonnull FieldPointer fieldPointer,
    +240                                                    @Nonnull Optional<SpannerFieldOptions> spannerOpts,
    +241                                                    @Nonnull Optional<TableFieldOptions> columnOpts,
    +242                                                    @Nonnull SpannerDriverSettings settings) {
    +243        return resolveColumnName(
    +244            fieldPointer.getField(),
    +245            spannerOpts,
    +246            columnOpts,
    +247            settings
    +248        );
    +249    }
    +250
    +251    /**
    +252     * Given a resolved Protocol Buffer field descriptor and set of annotations, resolve the expected/configured column
    +253     * name in Spanner for a given typed model field. If no specialized Spanner or table column annotations are present,
    +254     * fallback to a calculated default name.
    +255     *
    +256     * <p>{@link SpannerFieldOptions} always outweigh {@link TableFieldOptions}. If two similar or congruent properties
    +257     * are set between options, generic options are applied first, and then specialized options override.</p>
    +258     *
    +259     * <p>If {@link SpannerDriverSettings#preserveFieldNames()} is activated when this method is called, default names
    +260     * will use the literal field name from the Protocol Buffer definition. Otherwise, JSON-style names are calculated
    +261     * and used as default names.</p>
    +262     *
    +263     * <p>Similarly, if {@link SpannerDriverSettings#defaultCapitalizedNames()} is activated when this method is called,
    +264     * default names will use JSON-style naming but with initial capitals. For example, `name` turns into `Name` and
    +265     * `contact_info` turns into `ContactInfo`. In all cases, explicit property names from specialized or generic
    +266     * annotations prevail, then {@link SpannerDriverSettings#preserveFieldNames()} prevails, then the default form of
    +267     * naming with Spanner capitalized names active.</p>
    +268     *
    +269     * @see #resolveColumnName(FieldPointer, Optional, Optional, SpannerDriverSettings) For a version of this method
    +270     *      which operates on {@link FieldPointer} objects.
    +271     * @param field Protocol Buffer field descriptor for which we should resolve a Spanner column name.
    +272     * @param spannerOpts Spanner options applied to this field as annotations.
    +273     * @param columnOpts Generic table column settings applied to this field as annotations.
    +274     * @param settings Settings for the Spanner driver.
    +275     * @return Resolved column name, from explicit annotations, or by way of default calculation, as described above.
    +276     */
    +277    public static @Nonnull String resolveColumnName(@Nonnull Descriptors.FieldDescriptor field,
    +278                                                    @Nonnull Optional<SpannerFieldOptions> spannerOpts,
    +279                                                    @Nonnull Optional<TableFieldOptions> columnOpts,
    +280                                                    @Nonnull SpannerDriverSettings settings) {
    +281        // resolve the expected column name in Spanner.
    +282        String columnName;
    +283        if (spannerOpts.isPresent() && !spannerOpts.get().getColumn().isBlank()) {
    +284            columnName = spannerOpts.get().getColumn();
    +285        } else if (columnOpts.isPresent() && !columnOpts.get().getName().isBlank()) {
    +286            columnName = columnOpts.get().getName();
    +287        } else {
    +288            // generate a default name
    +289            if (settings.preserveFieldNames()) {
    +290                columnName = field.getName();
    +291            } else {
    +292                // use JSON field names
    +293                if (settings.defaultCapitalizedNames()) {
    +294                    columnName = String.format(
    +295                        "%s%s",
    +296                        field.getJsonName().substring(0, 1).toUpperCase(),
    +297                        field.getJsonName().substring(1)
    +298                    );
    +299                } else {
    +300                    // use unmodified JSON names
    +301                    columnName = field.getJsonName();
    +302                }
    +303            }
    +304        }
    +305
    +306        if (logging.isTraceEnabled())
    +307            logging.trace("Resolved column name for field '{}': '{}'",
    +308                field.getName(),
    +309                columnName);
    +310        return columnName;
    +311    }
    +312
    +313    /**
    +314     * Given a Spanner row result expressed as a {@link Struct} and a {@link FieldPointer} which is expected to be
    +315     * present, with a pre-resolved column name, return the numeric column index.
    +316     *
    +317     * @param source Row result from Spanner which we should resolve the column index from.
    +318     * @param fieldPointer Pointer to the field for which we are resolving an index. Pre-resolved.
    +319     * @param name Translated name of the column for which we are resolving an index. Pre-resolved.
    +320     * @return Integer index for the column in the provided row result.
    +321     */
    +322    public static int resolveColumnIndex(@Nonnull Struct source,
    +323                                         @Nonnull FieldPointer fieldPointer,
    +324                                         @Nonnull String name) {
    +325        var columnIndex = source.getColumnIndex(name);
    +326        if (logging.isTraceEnabled())
    +327            logging.trace("Resolved column index for field '{}': '{}'",
    +328                fieldPointer.getName(),
    +329                columnIndex);
    +330        return columnIndex;
    +331    }
    +332
    +333    /**
    +334     * Given a Spanner row result expressed as a {@link Struct} and a {@link FieldPointer} which is expected to be
    +335     * present, resolve any present {@link Value}.
    +336     *
    +337     * <p>This method additionally resolves the expected column name for the provided field.</p>
    +338     *
    +339     * @see #resolveColumnName(FieldPointer, Optional, Optional, SpannerDriverSettings) For an explanation of model
    +340     *      field column name calculations and annotation behavior.
    +341     * @param source Row result from Spanner from which we should resolve any present value.
    +342     * @param fieldPointer Pointer to the model field for which we are resolving a value.
    +343     * @param spannerOpts Spanner-specific options and annotations present on the field.
    +344     * @param columnOpts Column-generic options and annotations present on the field.
    +345     * @param driverSettings Settings for the Spanner driver.
    +346     * @return Resolved Spanner value, as applicable.
    +347     */
    +348    public static @Nonnull Value resolveColumnValue(@Nonnull Struct source,
    +349                                                    @Nonnull FieldPointer fieldPointer,
    +350                                                    @Nonnull Optional<SpannerFieldOptions> spannerOpts,
    +351                                                    @Nonnull Optional<TableFieldOptions> columnOpts,
    +352                                                    @Nonnull SpannerDriverSettings driverSettings) {
    +353        var columnValue = source.getValue(resolveColumnIndex(
    +354            source,
    +355            fieldPointer,
    +356            resolveColumnName(
    +357                fieldPointer,
    +358                spannerOpts,
    +359                columnOpts,
    +360                driverSettings
    +361            )
    +362        ));
    +363        if (logging.isTraceEnabled())
    +364            logging.trace("Resolved column value for field '{}': '{}'",
    +365                fieldPointer.getName(),
    +366                columnValue.toString());
    +367        return columnValue;
    +368    }
    +369
    +370    /**
    +371     * Given a resolved and eligible {@link FieldPointer} for a model field which should interact with Spanner, resolve
    +372     * an expected Spanner {@link Type}, including any nested structure or complex objects, as mediated and regulated by
    +373     * annotations on the model field.
    +374     *
    +375     * <p>If {@link SpannerFieldOptions#getType()} returns a non-default value, it prevails first, with
    +376     * {@link TableFieldOptions#getSptype()} after that. If no explicit type is resolvable from the field definition,
    +377     * a default type is generated (see method references for more information).</p>
    +378     *
    +379     * @see #resolveDefaultType(FieldPointer, SpannerDriverSettings) Fallback behavior if no explicit type is specified.
    +380     * @param fieldPointer Pointer to the model field for which we should resolve a Spanner column type.
    +381     * @param spannerOpts Spanner-specific options or annotations present on the field definition, as applicable.
    +382     * @param columnOpts Column-generic options or annotations present on the field definition, as applicable.
    +383     * @param settings Active settings for the Spanner driver.
    +384     * @return Expected Spanner column type corresponding to the provided model field, considering all annotations.
    +385     */
    +386    public static @Nonnull Type resolveColumnType(@Nonnull FieldPointer fieldPointer,
    +387                                                  @Nonnull Optional<SpannerFieldOptions> spannerOpts,
    +388                                                  @Nonnull Optional<TableFieldOptions> columnOpts,
    +389                                                  @Nonnull SpannerDriverSettings settings) {
    +390        //noinspection deprecation
    +391        return spannerOpts.isPresent() && spannerOpts.get().getType() != SpannerOptions.SpannerType.UNSPECIFIED_TYPE ?
    +392                 resolveType(fieldPointer, spannerOpts.get().getType()) :
    +393               columnOpts.isPresent() && columnOpts.get().getSptype() != SpannerOptions.SpannerType.UNSPECIFIED_TYPE ?
    +394                 resolveType(fieldPointer, columnOpts.get().getSptype()) :
    +395               resolveDefaultType(fieldPointer, settings);
    +396    }
    +397
    +398    /**
    +399     * Calculate a default projection of Spanner columns, as configured on the provided default model instance. Results
    +400     * are returned as a stream of fields paired to their column counterparts.
    +401     *
    +402     * @param descriptor Default model schema to generate a default set of Spanner columns from.
    +403     * @param driverSettings Settings for the Spanner driver.
    +404     * @return Default list of Spanner columns.
    +405     */
    +406    public static @Nonnull Stream<Pair<String, String>> calculateDefaultFieldStream(
    +407            @Nonnull Descriptors.Descriptor descriptor,
    +408            @Nonnull SpannerDriverSettings driverSettings) {
    +409        // pluck the ID field first, and then concatenante it to a stream of all other fields.
    +410        return Stream.concat(Stream.of(idField(descriptor).orElseThrow()), forEachField(
    +411            descriptor,
    +412            Optional.of(onlySpannerEligibleFields(driverSettings))
    +413        )).map((fieldPointer) -> {
    +414            var fieldOpts = fieldAnnotation(fieldPointer.getField(), Datamodel.field);
    +415            if (fieldOpts.orElse(FieldPersistenceOptions.getDefaultInstance()).getType() == FieldType.ID) {
    +416                // this is an ID field, so skip it outright because the key will inject it.
    +417                return null;
    +418            } else if (fieldOpts.orElse(FieldPersistenceOptions.getDefaultInstance()).getType() == FieldType.KEY) {
    +419                // this is a key field, so skip it and instead inject the ID field.
    +420                return Pair.of(
    +421                    fieldPointer.getName(),
    +422                    resolveKeyColumn(idField(descriptor).orElseThrow(), driverSettings)
    +423                );
    +424            } else {
    +425                return Pair.of(
    +426                    fieldPointer.getName(),
    +427                    resolveColumnName(fieldPointer,
    +428                        fieldAnnotation(fieldPointer.getField(), Datamodel.spanner),
    +429                        fieldAnnotation(fieldPointer.getField(), Datamodel.column),
    +430                        driverSettings
    +431                    )
    +432                );
    +433            }
    +434        }).filter(Objects::nonNull);
    +435    }
    +436
    +437    /**
    +438     * Calculate a default projection of Spanner columns, as configured on the provided default model instance.
    +439     *
    +440     * @param descriptor Default model schema to generate a default set of Spanner columns from.
    +441     * @param driverSettings Settings for the Spanner driver.
    +442     * @return Default list of Spanner columns.
    +443     */
    +444    public static @Nonnull List<String> calculateDefaultFields(@Nonnull Descriptors.Descriptor descriptor,
    +445                                                               @Nonnull SpannerDriverSettings driverSettings) {
    +446        return calculateDefaultFieldStream(
    +447            descriptor,
    +448            driverSettings
    +449        ).map(Pair::getValue).collect(Collectors.toUnmodifiableList());
    +450    }
    +451
    +452    /**
    +453     * Calculate a default projection of Spanner columns, as configured on the provided default model instance. The
    +454     * default set of columns includes any columns considered "eligible" for storage in Spanner, each pre-resolved with
    +455     * a column name and type.
    +456     *
    +457     * <p>This method is designed to operate deterministically, by visiting each eligible model field in a predictable
    +458     * order and expressing that same order in the output collection.</p>
    +459     *
    +460     * @see #onlySpannerEligibleFields(SpannerDriverSettings) For an explanation of predicate behavior with regard to
    +461     *      eligibility for interaction with Spanner.
    +462     * @see #resolveColumnName(FieldPointer, Optional, Optional, SpannerDriverSettings) For an explanation of Spanner
    +463     *      column name resolution and default calculation behavior.
    +464     * @see #resolveColumnType(FieldPointer, Optional, Optional, SpannerDriverSettings) For an explanation of Spanner
    +465     *      column type resolution and default decision behavior.
    +466     * @param descriptor Default model schema to generate a default set of Spanner columns from.
    +467     * @param driverSettings Settings for the Spanner driver.
    +468     * @return Default list of Spanner columns.
    +469     */
    +470    public static @Nonnull Collection<Type.StructField> generateStruct(@Nonnull Descriptors.Descriptor descriptor,
    +471                                                                       @Nonnull SpannerDriverSettings driverSettings) {
    +472        return forEachField(
    +473            descriptor,
    +474            Optional.of(onlySpannerEligibleFields(driverSettings))
    +475        ).filter((fieldPointer) ->
    +476            // don't ever include `KEY` fields in structs
    +477            fieldAnnotation(fieldPointer.getField(), Datamodel.field).orElse(
    +478                FieldPersistenceOptions.getDefaultInstance()
    +479            ).getType() != FieldType.KEY
    +480        ).map((fieldPointer) -> {
    +481            var spannerOpts = fieldAnnotation(fieldPointer.getField(), Datamodel.spanner);
    +482            var columnOpts = fieldAnnotation(fieldPointer.getField(), Datamodel.column);
    +483            var name = resolveColumnName(
    +484                fieldPointer,
    +485                spannerOpts,
    +486                columnOpts,
    +487                driverSettings
    +488            );
    +489            var type = resolveColumnType(
    +490                fieldPointer,
    +491                spannerOpts,
    +492                columnOpts,
    +493                driverSettings
    +494            );
    +495            return Type.StructField.of(name, type);
    +496        }).collect(Collectors.toUnmodifiableList());
    +497    }
    +498
    +499    /**
    +500     * Return a {@link Predicate} implementation which determines field eligibility with regard to interaction with
    +501     * Cloud Spanner, optionally considering the provided set of circumstantial higher-order eligible fields (for
    +502     * instance, in the case of a known property projection).
    +503     *
    +504     * <p>Field eligibility is determined by the following criteria:
    +505     * <ul>
    +506     *     <li>Model fields <b>MUST</b> be present on the {@link Message} schema to interact with Spanner.</li>
    +507     *     <li>Model fields <b>MUST NOT</b> be annotated with {@link TableFieldOptions#getIgnore()}.</li>
    +508     *     <li>Model fields <b>MUST NOT</b> be annotated with {@link SpannerFieldOptions#getIgnore()}.</li>
    +509     *     <li>Model fields <b>MUST NOT</b> be annotated with {@link FieldVisibility#INTERNAL}.</li>
    +510     *     <li>If provided and non-empty, model fields <b>MUST</b> be present in the set of <pre>eligibleFields</pre>
    +511     *     provided to this method.</li>
    +512     * </ul></p>
    +513     *
    +514     * @see #onlySpannerEligibleFields(SpannerDriverSettings) For circumstances with no known eligible fields.
    +515     * @param eligibleFields Set of higher-order eligible fields, if applicable. Only considered if non-empty.
    +516     * @param settings Settings for the Spanner driver.
    +517     * @return Predicate which filters {@link FieldPointer} objects according to the provided settings.
    +518     */
    +519    public static @Nonnull Predicate<FieldPointer> onlySpannerEligibleFields(@Nonnull SortedSet<String> eligibleFields,
    +520                                                                             @Nonnull SpannerDriverSettings settings) {
    +521        if (eligibleFields.isEmpty()) {
    +522            return defaultFieldPredicate;
    +523        }
    +524        return defaultFieldPredicate.and((fieldPointer) -> {
    +525            var field = fieldPointer.getField();
    +526            var columnOpts = fieldAnnotation(field, Datamodel.column);
    +527            var spannerOpts = fieldAnnotation(field, Datamodel.spanner);
    +528
    +529            return !(
    +530                // field should be omitted if not present in list of row fields, if we have any
    +531                !eligibleFields.isEmpty() && !eligibleFields.contains(resolveColumnName(
    +532                    fieldPointer,
    +533                    spannerOpts,
    +534                    columnOpts,
    +535                    settings
    +536                ))
    +537            );
    +538        });
    +539    }
    +540
    +541    /**
    +542     * Return a {@link Predicate} implementation which operates on {@link FieldPointer} objects to determine eligibility
    +543     * for interaction with Spanner.
    +544     *
    +545     * <p>This method variant provides no opportunity to filter by higher-order circumstantial fields. Should invoking
    +546     * code have an opportunity to do so, it may dispatch the more customizable form of this method (see below).
    +547     * Additionally, that method may be referenced for a detailed explanation of field eligibility behavior.</p>
    +548     *
    +549     * @see #onlySpannerEligibleFields(SortedSet, SpannerDriverSettings) For the fully-controllable form of this method,
    +550     *      which can combine an additional {@link Predicate} to filter by a set of fields known at invocation time.
    +551     * @param settings Settings for the Spanner driver.
    +552     * @return Predicate to determine {@link FieldPointer} eligibility for interaction with Spanner.
    +553     */
    +554    public static @Nonnull Predicate<FieldPointer> onlySpannerEligibleFields(@Nonnull SpannerDriverSettings settings) {
    +555        return onlySpannerEligibleFields(Collections.emptySortedSet(), settings);
    +556    }
    +557
    +558    /**
    +559     * If a concrete type is marked as repeated, wrap it in an array type. Otherwise, just return the type.
    +560     *
    +561     * @param field Field pointer for the model field.
    +562     * @param inner Inner type for the maybe-repeated field.
    +563     * @return Either the array-wrapped type or the concrete individual type.
    +564     */
    +565    public static @Nonnull Type maybeWrapType(@Nonnull FieldPointer field,
    +566                                              @Nonnull Type inner) {
    +567        if (field.getField().isRepeated()) {
    +568            return Type.array(inner);
    +569        }
    +570        return inner;
    +571    }
    +572
    +573    /**
    +574     * Resolve a normalized Spanner type for the provided field `pointer`, with the explicit provided `spannerType`.
    +575     * With an explicit type, our job is just to make sure the model configuration is cohesive.
    +576     *
    +577     * @param pointer Resolved field pointer.
    +578     * @param spannerType Explicit Spanner type.
    +579     * @return Resolved normalized Spanner type.
    +580     */
    +581    public static @Nonnull Type resolveType(@Nonnull FieldPointer pointer,
    +582                                            @Nonnull tools.elide.core.SpannerOptions.SpannerType spannerType) {
    +583        switch (spannerType) {
    +584            case STRING:
    +585            case JSON: return maybeWrapType(pointer, Type.string());
    +586            case NUMERIC: return maybeWrapType(pointer, Type.numeric());
    +587            case FLOAT64: return maybeWrapType(pointer, Type.float64());
    +588            case INT64: return maybeWrapType(pointer, Type.int64());
    +589            case BYTES: return maybeWrapType(pointer, Type.bytes());
    +590            case BOOL: return maybeWrapType(pointer, Type.bool());
    +591            case DATE: return maybeWrapType(pointer, Type.date());
    +592            case TIMESTAMP: return maybeWrapType(pointer, Type.timestamp());
    +593            default: throw new IllegalArgumentException("Unrecognized Spanner type.");
    +594        }
    +595    }
    +596
    +597    /**
    +598     * Resolve a default Spanner type for the provided field `pointer`. This selects a sensible default when no
    +599     * explicit type annotations are present for a Spanner column's type.
    +600     *
    +601     * @param pointer Field pointer to resolve a Spanner type for.
    +602     * @param settings Settings for the Spanner driver.
    +603     * @return Resolved normalized Spanner type.
    +604     */
    +605    public static @Nonnull Type resolveDefaultType(@Nonnull FieldPointer pointer,
    +606                                                   @Nonnull SpannerDriverSettings settings) {
    +607        return resolveDefaultType(
    +608            pointer,
    +609            pointer.getField().getType(),
    +610            settings
    +611        );
    +612    }
    +613
    +614    /**
    +615     * Resolve a default Spanner type for the provided field `pointer`. This selects a sensible default when no
    +616     * explicit type annotations are present for a Spanner column's type.
    +617     *
    +618     * @param pointer Field pointer to resolve a Spanner type for.
    +619     * @param protoType Protocol Buffer type to resolve.
    +620     * @param settings Settings for the Spanner driver.
    +621     * @return Resolved normalized Spanner type.
    +622     */
    +623    public static @Nonnull Type resolveDefaultType(@Nonnull FieldPointer pointer,
    +624                                                   @Nonnull Descriptors.FieldDescriptor.Type protoType,
    +625                                                   @Nonnull SpannerDriverSettings settings) {
    +626        switch (protoType) {
    +627            case DOUBLE:
    +628            case FLOAT:
    +629                return maybeWrapType(pointer, Type.float64());
    +630
    +631            case INT32:
    +632            case INT64:
    +633            case UINT32:
    +634            case UINT64:
    +635            case FIXED32:
    +636            case FIXED64:
    +637            case SFIXED32:
    +638            case SFIXED64:
    +639            case SINT32:
    +640            case SINT64:
    +641                return maybeWrapType(pointer, Type.int64());
    +642
    +643            case BOOL: return maybeWrapType(pointer, Type.bool());
    +644            case STRING: return maybeWrapType(pointer, Type.string());
    +645
    +646            case BYTES: return maybeWrapType(pointer, Type.bytes());
    +647            case ENUM:
    +648                return settings.enumsAsNumbers() ?
    +649                    maybeWrapType(pointer, Type.int64()) :
    +650                    maybeWrapType(pointer, Type.string());
    +651
    +652            case MESSAGE:
    +653                var messageType = pointer.getField().getMessageType();
    +654                if (Timestamp.getDescriptor().getFullName().equals(messageType.getFullName())) {
    +655                    // `TIMESTAMP` records should always be expressed as type `TIMESTAMP`.
    +656                    return maybeWrapType(pointer, Type.timestamp());
    +657
    +658                } else if (Date.getDescriptor().getFullName().equals(messageType.getFullName())) {
    +659                    // `DATE` records should always be expressed as type `DATE`.
    +660                    return maybeWrapType(pointer, Type.date());
    +661
    +662                } else {
    +663                    // special case: if this is a key field, we should treat it like it's actually the key's ID field.
    +664                    if (fieldAnnotation(pointer.getField(), Datamodel.field).orElse(
    +665                        FieldPersistenceOptions.getDefaultInstance()
    +666                    ).getType().equals(FieldType.KEY)) {
    +667                        // it's a key field, so instead, resolve the model's ID field and add that.
    +668                        return resolveKeyType(idField(pointer.getBase()).orElseThrow());
    +669                    }
    +670
    +671                    // any other type should fallback to being wrapped as a `STRUCT`.
    +672                    return maybeWrapType(pointer, Type.struct(generateStruct(
    +673                        pointer.getField().getMessageType(),
    +674                        settings
    +675                    )));
    +676                }
    +677
    +678            case GROUP:
    +679            default:
    +680                throw new IllegalArgumentException(String.format(
    +681                    "Unrecognized Protocol Buffer type: %s.",
    +682                    pointer.getField().getType().name()
    +683                ));
    +684        }
    +685    }
    +686
    +687    /**
    +688     * Given a pre-resolved {@link FieldPointer}, resolve any present {@link TableFieldOptions}.
    +689     *
    +690     * @see #columnOpts(Descriptors.FieldDescriptor) For the low-level version of this method, which includes a more
    +691     *      detailed explanation of {@link TableFieldOptions} with regard to Spanner.
    +692     * @param fieldPointer Field pointer for which we should resolve any present column-generic options.
    +693     * @return Set of specified table field options, or {@link Optional#empty()}.
    +694     */
    +695    public static @Nonnull Optional<TableFieldOptions> columnOpts(@Nonnull FieldPointer fieldPointer) {
    +696        return columnOpts(fieldPointer.getField());
    +697    }
    +698
    +699    /**
    +700     * Given a resolved Protocol Buffer {@link Descriptors.FieldDescriptor} which is considered eligible for interaction
    +701     * with Spanner, resolve any present column-generic options and annotations via {@link TableFieldOptions}.
    +702     *
    +703     * <p>Column-generic options apply to engines which operate in a columnar manner. This includes Spanner, but also
    +704     * includes engines like BigQuery and SQL-based systems. To allow adaptation to those systems without curtailing
    +705     * control of table and field naming, the Spanner driver respects {@link TableFieldOptions} but defers to any
    +706     * present {@link SpannerFieldOptions}.</p>
    +707     *
    +708     * @see #columnOpts(FieldPointer) For a version of this method which operates on {@link FieldPointer}.
    +709     * @see #spannerOpts(Descriptors.FieldDescriptor) For the equivalent version of this method that returns Spanner-
    +710     *      specific field options.
    +711     * @param field Field descriptor for which we should resolve any present {@link TableFieldOptions}.
    +712     * @return Any present column-generic field options or annotations, or {@link Optional#empty()}.
    +713     */
    +714    public static @Nonnull Optional<TableFieldOptions> columnOpts(@Nonnull Descriptors.FieldDescriptor field) {
    +715        // resolve any generic column options...
    +716        var columnOpts = fieldAnnotation(
    +717            field,
    +718            Datamodel.column
    +719        );
    +720
    +721        if (columnOpts.isPresent() && logging.isDebugEnabled())
    +722            logging.debug("Found column options for field '{}': \n{}",
    +723                field.getName(),
    +724                columnOpts.toString());
    +725        else if (columnOpts.isEmpty() && logging.isDebugEnabled()) {
    +726            logging.debug("No column opts for field '{}'. Using defaults.",
    +727                field.getName());
    +728        }
    +729        return columnOpts;
    +730    }
    +731
    +732    /**
    +733     * Given a pre-resolved {@link FieldPointer}, resolve any present generic {@link FieldPersistenceOptions}.
    +734     *
    +735     * @see #fieldOpts(Descriptors.FieldDescriptor) For the low-level version of this method, which includes a more
    +736     *      detailed explanation of {@link FieldPersistenceOptions}.
    +737     * @param fieldPointer Field pointer for which we should resolve any present field-generic options.
    +738     * @return Set of specified general field options, or {@link Optional#empty()}.
    +739     */
    +740    public static @Nonnull Optional<FieldPersistenceOptions> fieldOpts(@Nonnull FieldPointer fieldPointer) {
    +741        return fieldOpts(fieldPointer.getField());
    +742    }
    +743
    +744    /**
    +745     * Given a resolved Protocol Buffer {@link Descriptors.FieldDescriptor} which is considered eligible for interaction
    +746     * with Spanner, resolve any present field-generic options and annotations via {@link FieldPersistenceOptions}.
    +747     *
    +748     * <p>To adapt to other persistence engines, model fields may be annotated with {@link FieldPersistenceOptions}. In
    +749     * all cases, present {@link FieldPersistenceOptions} yield with regard to matchin Spanner-specific fields.</p>
    +750     *
    +751     * @see #spannerOpts(Descriptors.FieldDescriptor) Equivalent method for Spanner options
    +752     * @see #columnOpts(Descriptors.FieldDescriptor) equivalent method for column generic options
    +753     * @param field Field descriptor for which we should resolve any present {@link FieldPersistenceOptions}.
    +754     * @return Any present field-generic field options or annotations, or {@link Optional#empty()}.
    +755     */
    +756    public static @Nonnull Optional<FieldPersistenceOptions> fieldOpts(@Nonnull Descriptors.FieldDescriptor field) {
    +757        // resolve field options
    +758        var fieldOpts = fieldAnnotation(
    +759            field,
    +760            Datamodel.field
    +761        );
    +762
    +763        if (fieldOpts.isPresent() && logging.isDebugEnabled())
    +764            logging.debug("Found generic options for field '{}': \n{}",
    +765                field.getName(),
    +766                fieldOpts.toString());
    +767        else if (fieldOpts.isEmpty() && logging.isDebugEnabled()) {
    +768            logging.debug("No generic opts for field '{}'. Using defaults.",
    +769                field.getName());
    +770        }
    +771        return fieldOpts;
    +772    }
    +773
    +774    /**
    +775     * Given a pre-resolved {@link FieldPointer}, resolve any present {@link SpannerFieldOptions}.
    +776     *
    +777     * @see #spannerOpts(Descriptors.FieldDescriptor) For the low-level version of this method, which includes a more
    +778     *      detailed explanation of {@link SpannerFieldOptions}.
    +779     * @param fieldPointer Field pointer for which we should resolve any present column-generic options.
    +780     * @return Set of specified table field options, or {@link Optional#empty()}.
    +781     */
    +782    public static @Nonnull Optional<SpannerFieldOptions> spannerOpts(@Nonnull FieldPointer fieldPointer) {
    +783        return spannerOpts(fieldPointer.getField());
    +784    }
    +785
    +786    /**
    +787     * Given a resolved Protocol Buffer {@link Descriptors.FieldDescriptor} which is considered eligible for interaction
    +788     * with Spanner, resolve any present Spanner-specific options and annotations via {@link SpannerFieldOptions}.
    +789     *
    +790     * <p>To adapt to other columnar-style engines, model fields may be annotated with {@link TableFieldOptions}. In all
    +791     * cases, present {@link SpannerFieldOptions} override with regard to Spanner Driver behavior.</p>
    +792     *
    +793     * @see #spannerOpts(FieldPointer) For a version of this method which operates on {@link FieldPointer}.
    +794     * @see #columnOpts(Descriptors.FieldDescriptor) For the equivalent version of this method that returns column-
    +795     *      generic field options.
    +796     * @param field Field descriptor for which we should resolve any present {@link SpannerFieldOptions}.
    +797     * @return Any present column-generic field options or annotations, or {@link Optional#empty()}.
    +798     */
    +799    public static @Nonnull Optional<SpannerFieldOptions> spannerOpts(@Nonnull Descriptors.FieldDescriptor field) {
    +800        // resolve spanner options, which override any default table options.
    +801        var spannerOpts = fieldAnnotation(
    +802            field,
    +803            Datamodel.spanner
    +804        );
    +805
    +806        if (spannerOpts.isPresent() && logging.isDebugEnabled())
    +807            logging.debug("Found Spanner options for field '{}': \n{}",
    +808                    field.getName(),
    +809                    spannerOpts.toString());
    +810        else if (spannerOpts.isEmpty() && logging.isDebugEnabled()) {
    +811            logging.debug("No Spanner opts for field '{}'. Using defaults.",
    +812                field.getName());
    +813        }
    +814        return spannerOpts;
    +815    }
    +816}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/CacheDriver.html b/docs/java/src-html/gust/backend/model/CacheDriver.html new file mode 100644 index 000000000..64a086d26 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/CacheDriver.html @@ -0,0 +1,176 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013
    +014package gust.backend.model;
    +015
    +016import com.google.common.util.concurrent.Futures;
    +017import com.google.common.util.concurrent.ListenableFuture;
    +018import com.google.common.util.concurrent.ListeningScheduledExecutorService;
    +019import com.google.protobuf.Message;
    +020import gust.backend.runtime.ReactiveFuture;
    +021
    +022import javax.annotation.Nonnull;
    +023import java.util.*;
    +024
    +025
    +026/**
    +027 * Describes the surface of a <i>cache driver</i>, which is a partner object to a {@link PersistenceDriver} specifically
    +028 * tailored to deal with caching engines. Cache drivers may be used with any {@link ModelAdapter} implementation for
    +029 * transparent read-through caching support.
    +030 *
    +031 * <p>Caches implemented in this manner are expected to adhere to options defined on {@link CacheOptions}, particularly
    +032 * with regard to eviction and timeouts. Specific implementations may extend that interface to define custom options,
    +033 * which may be provided to the implementation at runtime either via stubbed options parameters or app config.</p>
    +034 */
    +035@SuppressWarnings("UnstableApiUsage")
    +036public interface CacheDriver<Key extends Message, Model extends Message> {
    +037  /**
    +038   * Flush the entire cache managed by this driver. This should drop all keys related to model instance caching that are
    +039   * currently held by the cache.
    +040   *
    +041   * @param executor Executor to use for any async operations.
    +042   * @return Future, which simply completes when the flush is done.
    +043   */
    +044  @Nonnull ReactiveFuture flush(@Nonnull ListeningScheduledExecutorService executor);
    +045
    +046  /**
    +047   * Write a record ({@code model}) at {@code key} into the cache, overwriting any model currently stored at the same
    +048   * key, if applicable. The resulting future completes with no value, when the cache write has finished, to let the
    +049   * framework know the cache is done following up.
    +050   *
    +051   * @param key Key for the record we should inject into the cache.
    +052   * @param model Record data to inject into the cache.
    +053   * @param executor Executor to use for any async operations.
    +054   * @return Future, which simply completes when the write is done.
    +055   */
    +056  @Nonnull ReactiveFuture put(@Nonnull Key key,
    +057                              @Nonnull Model model,
    +058                              @Nonnull ListeningScheduledExecutorService executor);
    +059
    +060  /**
    +061   * Force-evict any cached record at the provided {@code key} in the cache managed by this driver. This operation is
    +062   * expected to succeed in all cases and perform its work in an idempotent manner.
    +063   *
    +064   * @param key Key for the record to force-evict from the cache.
    +065   * @param executor Executor to use for any async operations.
    +066   * @return Future, which resolves to the evicted key when the operation completes.
    +067   */
    +068  @Nonnull ReactiveFuture<Key> evict(@Nonnull Key key, @Nonnull ListeningScheduledExecutorService executor);
    +069
    +070  /**
    +071   * Force-evict the set of cached records specified by {@code keys}, in the cache managed by this driver. This
    +072   * operation is expected to succeed in all cases and perform its work in an idempotent manner, similar to the single-
    +073   * key version of this method (see: {@link #evict(Message, ListeningScheduledExecutorService)}).
    +074   *
    +075   * @param keys Set of keys to evict from the cache.
    +076   * @param executor Executor to sue for any async operations.
    +077   * @return Future, which simply completes when the bulk-evict operation is done.
    +078   */
    +079  default @Nonnull ReactiveFuture evict(@Nonnull Iterable<Key> keys,
    +080                                        @Nonnull ListeningScheduledExecutorService executor) {
    +081    List<ListenableFuture<?>> evictions = new ArrayList<>();
    +082    keys.forEach((key) -> evictions.add(evict(key, executor)));
    +083    return ReactiveFuture.wrap(Futures.allAsList(evictions));
    +084  }
    +085
    +086  /**
    +087   * Attempt to resolve a known model, addressed by {@code key}, from the cache powered/backed by this driver, according
    +088   * to {@code options} and making use of {@code executor}.
    +089   *
    +090   * <p>If no value is available in the cache, {@link Optional#empty()} must be returned, which triggers a call to the
    +091   * driver to resolve the record. If the record can be fetched originally, it will later be added to the cache by a
    +092   * separate call to {@link #put(Message, Message, ListeningScheduledExecutorService)}.</p>
    +093   *
    +094   * @param key Key for the record which we should look for in the cache.
    +095   * @param options Options to apply to the fetch routine taking place.
    +096   * @param executor Executor to use for async tasks. Provided by the driver or adapter.
    +097   * @return Future value, which resolves either to {@link Optional#empty()} or a wrapped result value.
    +098   */
    +099  @Nonnull ReactiveFuture<Optional<Model>> fetch(@Nonnull Key key,
    +100                                                 @Nonnull FetchOptions options,
    +101                                                 @Nonnull ListeningScheduledExecutorService executor);
    +102}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/CacheOptions.EvictionMode.html b/docs/java/src-html/gust/backend/model/CacheOptions.EvictionMode.html new file mode 100644 index 000000000..fe170e20d --- /dev/null +++ b/docs/java/src-html/gust/backend/model/CacheOptions.EvictionMode.html @@ -0,0 +1,168 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013
    +014package gust.backend.model;
    +015
    +016import javax.annotation.Nonnull;
    +017import java.util.Optional;
    +018import java.util.concurrent.TimeUnit;
    +019
    +020
    +021/**
    +022 * Specifies operational options related to caching. These options are usable orthogonally to the main
    +023 * {@link OperationOptions} tree. See below for a description of each configurable property.
    +024 *
    +025 * <p><b>Cache configuration (<b>defaults</b> in parens):
    +026 * <ul>
    +027 *   <li>{@link #enableCache()} ({@code true}): Whether to allow caching at all.</li>
    +028 *   <li>{@link #cacheTimeout()} ({@code 1}): Amount of time to give the cache before falling back to storage.</li>
    +029 *   <li>{@link #cacheTimeoutUnit()} ({@code SECONDS}): Time unit to correspond with {@code cacheTimeoutValue}.</li>
    +030 *   <li>{@link #cacheDefaultTTL()}: Default amount of time to let things stick around in the cache.</li>
    +031 *   <li>{@link #cacheDefaultTTLUnit()}: Time unit to correspond with {@code cacheDefaultTTL}.</li>
    +032 *   <li>{@link #cacheEvictionMode()}: Eviction mode to operate in.
    +033 * </ul></p>
    +034 */
    +035public interface CacheOptions extends OperationOptions {
    +036  /** Describes operating modes with regard to cache eviction. */
    +037  enum EvictionMode {
    +038    /** Flag to enable TTL enforcement. */
    +039    TTL("Time-to-Live"),
    +040
    +041    /** Least-Frequently-Used mode for cache eviction. */
    +042    LFU("Least-Frequently Used"),
    +043
    +044    /** Least-Recently-Used mode for cache eviction. */
    +045    LRU("Least-Recently Used");
    +046
    +047    /** Pretty label for this mode. */
    +048    private final @Nonnull String label;
    +049
    +050    EvictionMode(@Nonnull String label) {
    +051      this.label = label;
    +052    }
    +053
    +054    @Override
    +055    public String toString() {
    +056      return String.format("EvictionMode(%s - %s)", this.name(), this.label);
    +057    }
    +058
    +059    /** @return Human-readable label for this eviction mode. */
    +060    public @Nonnull String getLabel() {
    +061      return label;
    +062    }
    +063  }
    +064
    +065  /** @return Whether the cache should be enabled, if installed. Defaults to `true`. */
    +066  default @Nonnull Boolean enableCache() {
    +067    return true;
    +068  }
    +069
    +070  /** @return Value to apply to the cache timeout. If left unspecified, the global default is used. */
    +071  default @Nonnull Optional<Long> cacheTimeout() {
    +072    return Optional.of(2L);
    +073  }
    +074
    +075  /** @return Unit to apply to the cache timeout. If left unspecified, the global default is used. */
    +076  default @Nonnull TimeUnit cacheTimeoutUnit() {
    +077    return TimeUnit.SECONDS;
    +078  }
    +079
    +080  /** @return Default amount of time to let things remain in the cache. */
    +081  default @Nonnull Optional<Long> cacheDefaultTTL() {
    +082    return Optional.of(1L);
    +083  }
    +084
    +085  /** @return Unit to apply to the default cache lifetime (TTL) value. */
    +086  default @Nonnull TimeUnit cacheDefaultTTLUnit() {
    +087    return TimeUnit.HOURS;
    +088  }
    +089
    +090  /** @return Specifier describing the cache eviction mode to apply, if any. */
    +091  default @Nonnull Optional<EvictionMode> cacheEvictionMode() {
    +092    return Optional.of(EvictionMode.TTL);
    +093  }
    +094}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/CacheOptions.html b/docs/java/src-html/gust/backend/model/CacheOptions.html new file mode 100644 index 000000000..fe170e20d --- /dev/null +++ b/docs/java/src-html/gust/backend/model/CacheOptions.html @@ -0,0 +1,168 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013
    +014package gust.backend.model;
    +015
    +016import javax.annotation.Nonnull;
    +017import java.util.Optional;
    +018import java.util.concurrent.TimeUnit;
    +019
    +020
    +021/**
    +022 * Specifies operational options related to caching. These options are usable orthogonally to the main
    +023 * {@link OperationOptions} tree. See below for a description of each configurable property.
    +024 *
    +025 * <p><b>Cache configuration (<b>defaults</b> in parens):
    +026 * <ul>
    +027 *   <li>{@link #enableCache()} ({@code true}): Whether to allow caching at all.</li>
    +028 *   <li>{@link #cacheTimeout()} ({@code 1}): Amount of time to give the cache before falling back to storage.</li>
    +029 *   <li>{@link #cacheTimeoutUnit()} ({@code SECONDS}): Time unit to correspond with {@code cacheTimeoutValue}.</li>
    +030 *   <li>{@link #cacheDefaultTTL()}: Default amount of time to let things stick around in the cache.</li>
    +031 *   <li>{@link #cacheDefaultTTLUnit()}: Time unit to correspond with {@code cacheDefaultTTL}.</li>
    +032 *   <li>{@link #cacheEvictionMode()}: Eviction mode to operate in.
    +033 * </ul></p>
    +034 */
    +035public interface CacheOptions extends OperationOptions {
    +036  /** Describes operating modes with regard to cache eviction. */
    +037  enum EvictionMode {
    +038    /** Flag to enable TTL enforcement. */
    +039    TTL("Time-to-Live"),
    +040
    +041    /** Least-Frequently-Used mode for cache eviction. */
    +042    LFU("Least-Frequently Used"),
    +043
    +044    /** Least-Recently-Used mode for cache eviction. */
    +045    LRU("Least-Recently Used");
    +046
    +047    /** Pretty label for this mode. */
    +048    private final @Nonnull String label;
    +049
    +050    EvictionMode(@Nonnull String label) {
    +051      this.label = label;
    +052    }
    +053
    +054    @Override
    +055    public String toString() {
    +056      return String.format("EvictionMode(%s - %s)", this.name(), this.label);
    +057    }
    +058
    +059    /** @return Human-readable label for this eviction mode. */
    +060    public @Nonnull String getLabel() {
    +061      return label;
    +062    }
    +063  }
    +064
    +065  /** @return Whether the cache should be enabled, if installed. Defaults to `true`. */
    +066  default @Nonnull Boolean enableCache() {
    +067    return true;
    +068  }
    +069
    +070  /** @return Value to apply to the cache timeout. If left unspecified, the global default is used. */
    +071  default @Nonnull Optional<Long> cacheTimeout() {
    +072    return Optional.of(2L);
    +073  }
    +074
    +075  /** @return Unit to apply to the cache timeout. If left unspecified, the global default is used. */
    +076  default @Nonnull TimeUnit cacheTimeoutUnit() {
    +077    return TimeUnit.SECONDS;
    +078  }
    +079
    +080  /** @return Default amount of time to let things remain in the cache. */
    +081  default @Nonnull Optional<Long> cacheDefaultTTL() {
    +082    return Optional.of(1L);
    +083  }
    +084
    +085  /** @return Unit to apply to the default cache lifetime (TTL) value. */
    +086  default @Nonnull TimeUnit cacheDefaultTTLUnit() {
    +087    return TimeUnit.HOURS;
    +088  }
    +089
    +090  /** @return Specifier describing the cache eviction mode to apply, if any. */
    +091  default @Nonnull Optional<EvictionMode> cacheEvictionMode() {
    +092    return Optional.of(EvictionMode.TTL);
    +093  }
    +094}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/CollapsedMessage.Operation.html b/docs/java/src-html/gust/backend/model/CollapsedMessage.Operation.html new file mode 100644 index 000000000..a5750d0f2 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/CollapsedMessage.Operation.html @@ -0,0 +1,298 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import tools.elide.core.CollectionMode;
    +016import com.google.protobuf.Message;
    +017import com.google.protobuf.Descriptors;
    +018import javax.annotation.Nonnull;
    +019import javax.annotation.Nullable;
    +020
    +021import java.util.List;
    +022import java.util.Optional;
    +023
    +024
    +025/**
    +026 * Describes a generic, collapsed message (i.e. serialized for storage). When using the universal model layer (based on
    +027 * Protocol Buffer messages), objects can be seamlessly serialized or deserialized to/from key value storage based on
    +028 * this layer / set of objects.
    +029 *
    +030 * <p>In particular, collapsed messages are useful with the Firestore adapter - and other data storage adapters which
    +031 * adopt parent-child style hierarchy. This object is not needed for in-memory storage.</p>
    +032 */
    +033public final class CollapsedMessage {
    +034  /** Describes a generic collapsed data store operation. */
    +035  public interface Operation {
    +036    /** @return Path for this write. */
    +037    @Nonnull String getPath();
    +038
    +039    /** @return Parent operation for this write, if applicable. */
    +040    @Nonnull Optional<Operation> getParent();
    +041
    +042    /**
    +043     * Execute the underlying operation against the provided write proxy. If there is an additional runtime path
    +044     * prefix, to apply, it is supplied here. Calling this method is sufficient to see change in underlying storage.
    +045     *
    +046     * @param prefix Additional path prefix to apply before executing the operation.
    +047     * @param proxy Write proxy to send our write invocations to.
    +048     * @param scope Scope of the write, which dictates parent context.
    +049     */
    +050    void execute(@Nullable String prefix, @Nonnull WriteProxy<Object> proxy, @Nonnull Optional<Parent> scope);
    +051  }
    +052
    +053  /**
    +054   * Wrap a set of pre-built operations in a collapsed message record.
    +055   *
    +056   * @param operations Set of operations to execute against underlying storage.
    +057   * @return Pre-filled collapsed message.
    +058   */
    +059  public static @Nonnull CollapsedMessage of(@Nonnull List<Operation> operations) {
    +060    return new CollapsedMessage(operations);
    +061  }
    +062
    +063  /** Base operation implementation class. */
    +064  private abstract static class BaseOperation implements Operation {
    +065    /** Path to prefix all writes with. */
    +066    protected final @Nonnull String path;
    +067
    +068    /**
    +069     * Initialize a new base key-value storage operation.
    +070     *
    +071     * @param path Path prefix to apply to the implementing write.
    +072     */
    +073    BaseOperation(@Nonnull String path) {
    +074      this.path = path;
    +075    }
    +076  }
    +077
    +078  /** Parent placeholder for non-root writes to key value stores. */
    +079  final static class Parent extends BaseOperation {
    +080    /** Message backing a write for a parent (optional). */
    +081    protected final @Nonnull Optional<Message> message;
    +082
    +083    /**
    +084     * Construct a write parent placeholder.
    +085     *
    +086     * @param path Path to prefix all writes with.
    +087     * @param message Optional message attached to this parent, if held.
    +088     */
    +089    Parent(@Nonnull String path, @Nonnull Optional<Message> message) {
    +090      super(path);
    +091      this.message = message;
    +092    }
    +093
    +094    /** @return Path prefix for this parent placeholder. */
    +095    @Override public String getPath() {
    +096      return this.path;
    +097    }
    +098
    +099    /** @return `empty` - parent write placeholders never have parents themselves. */
    +100    @Nonnull @Override public Optional<Operation> getParent() {
    +101      return Optional.empty();
    +102    }
    +103
    +104    /** @inheritDoc */
    +105    @Override public void execute(@Nullable String prefix,
    +106                                  @Nonnull WriteProxy<Object> proxy,
    +107                                  @Nonnull Optional<Parent> scope) {
    +108      // no-op (we skip this write symbol, because it's just a placeholder)
    +109    }
    +110  }
    +111
    +112  /** Describes a generic write operation with a key value store. */
    +113  final static class Write extends BaseOperation {
    +114    /** Specifies the disposition (strategy) for the write. */
    +115    private final @Nonnull ModelSerializer.WriteDisposition disposition;
    +116
    +117    /** Operational parent to this write, as applicable. */
    +118    private final @Nullable Operation parent;
    +119
    +120    /** Collection sub-write mode. */
    +121    private final @Nonnull CollectionMode collectionMode;
    +122
    +123    /** Field which this write is satisfying, if applicable. */
    +124    private final @Nullable Descriptors.FieldDescriptor field;
    +125
    +126    /** Serialized object data to write during this operation. */
    +127    protected final @Nonnull SerializedModel data;
    +128
    +129    /**
    +130     * Construct a write operation from scratch.
    +131     *
    +132     * @param path Path prefix to apply to this write operation.
    +133     * @param disposition Disposition (strategy) to apply to this write operation.
    +134     * @param collectionMode Collection mode to apply for this write operation.
    +135     * @param parentOperation Parent operation for this write, if applicable.
    +136     * @param field Field this write is satisfying, if applicable.
    +137     * @param data Data payload to apply with this write operation.
    +138     */
    +139    Write(@Nonnull String path,
    +140          @Nonnull ModelSerializer.WriteDisposition disposition,
    +141          @Nonnull CollectionMode collectionMode,
    +142          @Nonnull Optional<Operation> parentOperation,
    +143          @Nonnull Optional<Descriptors.FieldDescriptor> field,
    +144          @Nonnull SerializedModel data) {
    +145      super(path);
    +146      this.disposition = disposition;
    +147      this.collectionMode = collectionMode;
    +148      this.parent = parentOperation.orElse(null);
    +149      this.field = field.orElse(null);
    +150      this.data = data;
    +151    }
    +152
    +153    /** @return Disposition for this write. */
    +154    public @Nonnull ModelSerializer.WriteDisposition getDisposition() {
    +155      return disposition;
    +156    }
    +157
    +158    /** @return Collection sub-write mode. */
    +159    public @Nonnull CollectionMode getCollectionMode() {
    +160      return collectionMode;
    +161    }
    +162
    +163    /** @return Field this write is satisfying, if applicable. */
    +164    public @Nonnull Optional<Descriptors.FieldDescriptor> getField() {
    +165      return field != null ? Optional.of(field) : Optional.empty();
    +166    }
    +167
    +168    public @Nonnull SerializedModel getData() {
    +169      return data;
    +170    }
    +171
    +172    /** @return Path prefix for this parent placeholder. */
    +173    @Override public String getPath() {
    +174      return this.path;
    +175    }
    +176
    +177    /** @return Parent operation for this parent write. */
    +178    @Override public Optional<Operation> getParent() {
    +179      return this.parent == null ? Optional.empty() : Optional.of(this.parent);
    +180    }
    +181
    +182    /** @inheritDoc */
    +183    @Override public void execute(@Nullable String prefix,
    +184                                  @Nonnull WriteProxy<Object> proxy,
    +185                                  @Nonnull Optional<Parent> scope) {
    +186      switch (this.disposition) {
    +187        case BLIND:
    +188          proxy.put(proxy.ref(this.path, prefix), this.data);
    +189          return;
    +190        case CREATE:
    +191          proxy.create(proxy.ref(this.path, prefix), this.data);
    +192          return;
    +193        case UPDATE:
    +194          proxy.update(proxy.ref(this.path, prefix), this.data);
    +195      }
    +196    }
    +197  }
    +198
    +199  /** Operations held by this collapsed message. */
    +200  private final List<Operation> operations;
    +201
    +202  /**
    +203   * Create a collapsed message from scratch.
    +204   *
    +205   * @param operations Set of operations to wrap.
    +206   */
    +207  private CollapsedMessage(List<Operation> operations) {
    +208    this.operations = operations;
    +209  }
    +210
    +211  /**
    +212   * Execute the collapsed message against the provided {@link WriteProxy}, which creates it in underlying storage (once
    +213   * any wrapping transaction finishes).
    +214   *
    +215   * @param prefix Path prefix to apply to all sub-writes.
    +216   * @param proxy Write proxy to employ.
    +217   */
    +218  public void persist(@Nullable String prefix, @Nonnull WriteProxy<?> proxy) {
    +219    operations.forEach(operation -> {
    +220      //noinspection unchecked
    +221      operation.execute(prefix, (WriteProxy<Object>)proxy, Optional.empty());
    +222    });
    +223  }
    +224}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/CollapsedMessage.html b/docs/java/src-html/gust/backend/model/CollapsedMessage.html new file mode 100644 index 000000000..a5750d0f2 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/CollapsedMessage.html @@ -0,0 +1,298 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import tools.elide.core.CollectionMode;
    +016import com.google.protobuf.Message;
    +017import com.google.protobuf.Descriptors;
    +018import javax.annotation.Nonnull;
    +019import javax.annotation.Nullable;
    +020
    +021import java.util.List;
    +022import java.util.Optional;
    +023
    +024
    +025/**
    +026 * Describes a generic, collapsed message (i.e. serialized for storage). When using the universal model layer (based on
    +027 * Protocol Buffer messages), objects can be seamlessly serialized or deserialized to/from key value storage based on
    +028 * this layer / set of objects.
    +029 *
    +030 * <p>In particular, collapsed messages are useful with the Firestore adapter - and other data storage adapters which
    +031 * adopt parent-child style hierarchy. This object is not needed for in-memory storage.</p>
    +032 */
    +033public final class CollapsedMessage {
    +034  /** Describes a generic collapsed data store operation. */
    +035  public interface Operation {
    +036    /** @return Path for this write. */
    +037    @Nonnull String getPath();
    +038
    +039    /** @return Parent operation for this write, if applicable. */
    +040    @Nonnull Optional<Operation> getParent();
    +041
    +042    /**
    +043     * Execute the underlying operation against the provided write proxy. If there is an additional runtime path
    +044     * prefix, to apply, it is supplied here. Calling this method is sufficient to see change in underlying storage.
    +045     *
    +046     * @param prefix Additional path prefix to apply before executing the operation.
    +047     * @param proxy Write proxy to send our write invocations to.
    +048     * @param scope Scope of the write, which dictates parent context.
    +049     */
    +050    void execute(@Nullable String prefix, @Nonnull WriteProxy<Object> proxy, @Nonnull Optional<Parent> scope);
    +051  }
    +052
    +053  /**
    +054   * Wrap a set of pre-built operations in a collapsed message record.
    +055   *
    +056   * @param operations Set of operations to execute against underlying storage.
    +057   * @return Pre-filled collapsed message.
    +058   */
    +059  public static @Nonnull CollapsedMessage of(@Nonnull List<Operation> operations) {
    +060    return new CollapsedMessage(operations);
    +061  }
    +062
    +063  /** Base operation implementation class. */
    +064  private abstract static class BaseOperation implements Operation {
    +065    /** Path to prefix all writes with. */
    +066    protected final @Nonnull String path;
    +067
    +068    /**
    +069     * Initialize a new base key-value storage operation.
    +070     *
    +071     * @param path Path prefix to apply to the implementing write.
    +072     */
    +073    BaseOperation(@Nonnull String path) {
    +074      this.path = path;
    +075    }
    +076  }
    +077
    +078  /** Parent placeholder for non-root writes to key value stores. */
    +079  final static class Parent extends BaseOperation {
    +080    /** Message backing a write for a parent (optional). */
    +081    protected final @Nonnull Optional<Message> message;
    +082
    +083    /**
    +084     * Construct a write parent placeholder.
    +085     *
    +086     * @param path Path to prefix all writes with.
    +087     * @param message Optional message attached to this parent, if held.
    +088     */
    +089    Parent(@Nonnull String path, @Nonnull Optional<Message> message) {
    +090      super(path);
    +091      this.message = message;
    +092    }
    +093
    +094    /** @return Path prefix for this parent placeholder. */
    +095    @Override public String getPath() {
    +096      return this.path;
    +097    }
    +098
    +099    /** @return `empty` - parent write placeholders never have parents themselves. */
    +100    @Nonnull @Override public Optional<Operation> getParent() {
    +101      return Optional.empty();
    +102    }
    +103
    +104    /** @inheritDoc */
    +105    @Override public void execute(@Nullable String prefix,
    +106                                  @Nonnull WriteProxy<Object> proxy,
    +107                                  @Nonnull Optional<Parent> scope) {
    +108      // no-op (we skip this write symbol, because it's just a placeholder)
    +109    }
    +110  }
    +111
    +112  /** Describes a generic write operation with a key value store. */
    +113  final static class Write extends BaseOperation {
    +114    /** Specifies the disposition (strategy) for the write. */
    +115    private final @Nonnull ModelSerializer.WriteDisposition disposition;
    +116
    +117    /** Operational parent to this write, as applicable. */
    +118    private final @Nullable Operation parent;
    +119
    +120    /** Collection sub-write mode. */
    +121    private final @Nonnull CollectionMode collectionMode;
    +122
    +123    /** Field which this write is satisfying, if applicable. */
    +124    private final @Nullable Descriptors.FieldDescriptor field;
    +125
    +126    /** Serialized object data to write during this operation. */
    +127    protected final @Nonnull SerializedModel data;
    +128
    +129    /**
    +130     * Construct a write operation from scratch.
    +131     *
    +132     * @param path Path prefix to apply to this write operation.
    +133     * @param disposition Disposition (strategy) to apply to this write operation.
    +134     * @param collectionMode Collection mode to apply for this write operation.
    +135     * @param parentOperation Parent operation for this write, if applicable.
    +136     * @param field Field this write is satisfying, if applicable.
    +137     * @param data Data payload to apply with this write operation.
    +138     */
    +139    Write(@Nonnull String path,
    +140          @Nonnull ModelSerializer.WriteDisposition disposition,
    +141          @Nonnull CollectionMode collectionMode,
    +142          @Nonnull Optional<Operation> parentOperation,
    +143          @Nonnull Optional<Descriptors.FieldDescriptor> field,
    +144          @Nonnull SerializedModel data) {
    +145      super(path);
    +146      this.disposition = disposition;
    +147      this.collectionMode = collectionMode;
    +148      this.parent = parentOperation.orElse(null);
    +149      this.field = field.orElse(null);
    +150      this.data = data;
    +151    }
    +152
    +153    /** @return Disposition for this write. */
    +154    public @Nonnull ModelSerializer.WriteDisposition getDisposition() {
    +155      return disposition;
    +156    }
    +157
    +158    /** @return Collection sub-write mode. */
    +159    public @Nonnull CollectionMode getCollectionMode() {
    +160      return collectionMode;
    +161    }
    +162
    +163    /** @return Field this write is satisfying, if applicable. */
    +164    public @Nonnull Optional<Descriptors.FieldDescriptor> getField() {
    +165      return field != null ? Optional.of(field) : Optional.empty();
    +166    }
    +167
    +168    public @Nonnull SerializedModel getData() {
    +169      return data;
    +170    }
    +171
    +172    /** @return Path prefix for this parent placeholder. */
    +173    @Override public String getPath() {
    +174      return this.path;
    +175    }
    +176
    +177    /** @return Parent operation for this parent write. */
    +178    @Override public Optional<Operation> getParent() {
    +179      return this.parent == null ? Optional.empty() : Optional.of(this.parent);
    +180    }
    +181
    +182    /** @inheritDoc */
    +183    @Override public void execute(@Nullable String prefix,
    +184                                  @Nonnull WriteProxy<Object> proxy,
    +185                                  @Nonnull Optional<Parent> scope) {
    +186      switch (this.disposition) {
    +187        case BLIND:
    +188          proxy.put(proxy.ref(this.path, prefix), this.data);
    +189          return;
    +190        case CREATE:
    +191          proxy.create(proxy.ref(this.path, prefix), this.data);
    +192          return;
    +193        case UPDATE:
    +194          proxy.update(proxy.ref(this.path, prefix), this.data);
    +195      }
    +196    }
    +197  }
    +198
    +199  /** Operations held by this collapsed message. */
    +200  private final List<Operation> operations;
    +201
    +202  /**
    +203   * Create a collapsed message from scratch.
    +204   *
    +205   * @param operations Set of operations to wrap.
    +206   */
    +207  private CollapsedMessage(List<Operation> operations) {
    +208    this.operations = operations;
    +209  }
    +210
    +211  /**
    +212   * Execute the collapsed message against the provided {@link WriteProxy}, which creates it in underlying storage (once
    +213   * any wrapping transaction finishes).
    +214   *
    +215   * @param prefix Path prefix to apply to all sub-writes.
    +216   * @param proxy Write proxy to employ.
    +217   */
    +218  public void persist(@Nullable String prefix, @Nonnull WriteProxy<?> proxy) {
    +219    operations.forEach(operation -> {
    +220      //noinspection unchecked
    +221      operation.execute(prefix, (WriteProxy<Object>)proxy, Optional.empty());
    +222    });
    +223  }
    +224}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/CollapsedMessageCodec.html b/docs/java/src-html/gust/backend/model/CollapsedMessageCodec.html new file mode 100644 index 000000000..7195e9316 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/CollapsedMessageCodec.html @@ -0,0 +1,195 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.protobuf.Message;
    +016import io.micronaut.context.annotation.Context;
    +017import io.micronaut.context.annotation.Factory;
    +018
    +019import javax.annotation.Nonnull;
    +020import javax.annotation.concurrent.Immutable;
    +021import javax.annotation.concurrent.ThreadSafe;
    +022
    +023
    +024/**
    +025 *
    +026 *
    +027 * @param <Model>
    +028 * @param <ReadIntermediate>
    +029 */
    +030@Factory
    +031@Immutable
    +032@ThreadSafe
    +033public final class CollapsedMessageCodec<Model extends Message, ReadIntermediate>
    +034    implements ModelCodec<Model, CollapsedMessage, ReadIntermediate> {
    +035  /** Builder proto-type used to spawn new instances. */
    +036  private final Message.Builder builder;
    +037
    +038  /** Default model instance to build with. */
    +039  private final Model instance;
    +040
    +041  /** Singleton access to serializer. */
    +042  private final CollapsedMessageSerializer serializer = new CollapsedMessageSerializer();
    +043
    +044  /** Deserializer for the model, provided at construction time. */
    +045  private final ModelDeserializer<ReadIntermediate, Model> deserializer;
    +046
    +047  private CollapsedMessageCodec(@Nonnull Model instance,
    +048                                @Nonnull ModelDeserializer<ReadIntermediate, Model> deserializer) {
    +049    this.instance = instance;
    +050    this.builder = instance.newBuilderForType();
    +051    this.deserializer = deserializer;
    +052  }
    +053
    +054  /**
    +055   * Create a collapsed message codec which adapts the provided builder to {@link CollapsedMessage} and back. These
    +056   * "collapsed" messages follow the framework-defined protocol for serializing hierarchical data.
    +057   *
    +058   * @param <M> Model type for which we will construct or otherwise resolve a collapsed message codec.
    +059   * @return Collapsed message codec bound to the provided message type.
    +060   */
    +061  @Context
    +062  public static @Nonnull <M extends Message, RI> CollapsedMessageCodec<M, RI> forModel(
    +063      M instance, ModelDeserializer<RI, M> deserializer) {
    +064    return new CollapsedMessageCodec<>(instance, deserializer);
    +065  }
    +066
    +067  /**
    +068   * Acquire an instance of the {@link ModelSerializer} attached to this adapter. The instance is not guaranteed to be
    +069   * created fresh for this invocation.
    +070   *
    +071   * @return Serializer instance.
    +072   * @see #deserializer() For the inverse of this method.
    +073   * @see #deserialize(Object) To call into de-serialization directly.
    +074   */
    +075  @Override
    +076  public @Nonnull ModelSerializer<Model, CollapsedMessage> serializer() {
    +077    return serializer;
    +078  }
    +079
    +080  /**
    +081   * Acquire an instance of the {@link ModelDeserializer} attached to this adapter. The instance is not guaranteed to be
    +082   * created fresh for this invocation.
    +083   *
    +084   * @return Deserializer instance.
    +085   * @see #serializer() For the inverse of this method.
    +086   * @see #serialize(Message) To call into serialization directly.
    +087   */
    +088  @Override
    +089  public @Nonnull ModelDeserializer<ReadIntermediate, Model> deserializer() {
    +090    return deserializer;
    +091  }
    +092
    +093  /** Serializes model instances into {@link CollapsedMessage} objects. */
    +094  private final class CollapsedMessageSerializer implements ModelSerializer<Model, CollapsedMessage> {
    +095    /**
    +096     * Serialize a model instance from the provided object type to the specified output type, throwing exceptions
    +097     * verbosely if we are unable to correctly and properly export the record.
    +098     *
    +099     * @param input Input record object to serialize.
    +100     * @return Serialized record data, of the specified output type.
    +101     * @throws ModelDeflateException If the model fails to export or serialize for any reason.
    +102     */
    +103    @Override
    +104    public @Nonnull CollapsedMessage deflate(@Nonnull Message input) throws ModelDeflateException {
    +105      return ObjectModelSerializer.Companion
    +106          .defaultInstance()
    +107          .collapse(input, null, null, WriteDisposition.BLIND);
    +108    }
    +109  }
    +110
    +111  /** @return Builder for the model handled by this codec. */
    +112  public Message.Builder getBuilder() {
    +113    return builder;
    +114  }
    +115
    +116  /** @return Default model instance. */
    +117  @Override
    +118  public @Nonnull Model instance() {
    +119    return this.instance;
    +120  }
    +121}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/CollapsedMessageSerializer.html b/docs/java/src-html/gust/backend/model/CollapsedMessageSerializer.html new file mode 100644 index 000000000..a264beb30 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/CollapsedMessageSerializer.html @@ -0,0 +1,98 @@ + + + +Source code + + + +
    + +
    + + diff --git a/docs/java/src-html/gust/backend/model/DatabaseAdapter.html b/docs/java/src-html/gust/backend/model/DatabaseAdapter.html new file mode 100644 index 000000000..12515d86e --- /dev/null +++ b/docs/java/src-html/gust/backend/model/DatabaseAdapter.html @@ -0,0 +1,116 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.protobuf.Message;
    +016
    +017import javax.annotation.Nonnull;
    +018
    +019
    +020/**
    +021 * Extends the standard {@link ModelAdapter} interface with rich persistence features, including querying, indexing, and
    +022 * other stuff one would expect when interacting with a full database.
    +023 *
    +024 * @param <Key> Type of key used to uniquely address models.
    +025 * @param <Model> Message type which this database adapter is handling.
    +026 */
    +027public interface DatabaseAdapter<Key extends Message, Model extends Message, ReadRecord, WriteRecord>
    +028  extends ModelAdapter<Key, Model, ReadRecord, WriteRecord> {
    +029  /**
    +030   * Return the lower-level {@link DatabaseDriver} powering this adapter. The driver is responsible for communicating
    +031   * with the actual database or storage service, either via local stubs/emulators or a production API.
    +032   *
    +033   * @return Database driver instance currently in use by this model adapter.
    +034   */
    +035  @Nonnull DatabaseDriver<Key, Model, ReadRecord, WriteRecord> engine();
    +036
    +037  /** {@inheritDoc} */
    +038  @Override
    +039  default @Nonnull Key generateKey(@Nonnull Message instance) {
    +040    return engine().generateKey(instance);
    +041  }
    +042}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/DatabaseDriver.html b/docs/java/src-html/gust/backend/model/DatabaseDriver.html new file mode 100644 index 000000000..d29625da7 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/DatabaseDriver.html @@ -0,0 +1,97 @@ + + + +Source code + + + +
    + +
    + + diff --git a/docs/java/src-html/gust/backend/model/DatabaseManager.html b/docs/java/src-html/gust/backend/model/DatabaseManager.html new file mode 100644 index 000000000..3f67ba762 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/DatabaseManager.html @@ -0,0 +1,96 @@ + + + +Source code + + + +
    + +
    + + diff --git a/docs/java/src-html/gust/backend/model/DeleteOptions.html b/docs/java/src-html/gust/backend/model/DeleteOptions.html new file mode 100644 index 000000000..84ab6d7b3 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/DeleteOptions.html @@ -0,0 +1,94 @@ + + + +Source code + + + +
    + +
    + + diff --git a/docs/java/src-html/gust/backend/model/EncodedModel.html b/docs/java/src-html/gust/backend/model/EncodedModel.html new file mode 100644 index 000000000..ce5968565 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/EncodedModel.html @@ -0,0 +1,319 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.protobuf.ByteString;
    +016import com.google.protobuf.Descriptors;
    +017import com.google.protobuf.InvalidProtocolBufferException;
    +018import com.google.protobuf.Message;
    +019import com.google.protobuf.util.JsonFormat;
    +020
    +021import javax.annotation.Nonnull;
    +022import javax.annotation.Nullable;
    +023import javax.annotation.concurrent.Immutable;
    +024import javax.annotation.concurrent.ThreadSafe;
    +025import java.io.IOException;
    +026import java.io.ObjectInputStream;
    +027import java.io.ObjectOutputStream;
    +028import java.io.Serializable;
    +029import java.nio.charset.StandardCharsets;
    +030import java.util.Arrays;
    +031import java.util.Objects;
    +032
    +033
    +034/**
    +035 * Container class for an encoded Protocol Buffer model. Holds raw encoded model data, in any of Protobuf's built-in
    +036 * well-defined serialization formats (for instance {@code BINARY} or {@code JSON}).
    +037 *
    +038 * <p>Raw model data is encoded before being held by this record. In addition to holding the raw data, it also keeps
    +039 * the fully-qualified path to the model that the data came from, and the serialization format the data lives in. After
    +040 * being wrapped in this class, a batch of model data is additionally compliant with {@link Serializable}.</p>
    +041 */
    +042@Immutable
    +043@ThreadSafe
    +044@SuppressWarnings({"unused", "WeakerAccess"})
    +045public final class EncodedModel implements Serializable, Cloneable {
    +046  private static final long serialVersionUID = 1L;
    +047
    +048  /** Raw bytes of the enclosed model. */
    +049  private @Nonnull byte[] rawBytes;
    +050
    +051  /** Type of model held by this entity. */
    +052  private @Nonnull String type;
    +053
    +054  /** Operating mode for the underlying data. Always {@code BINARY} unless manually constructed. */
    +055  private @Nonnull EncodingMode dataMode;
    +056
    +057  /**
    +058   * Initialize a new encoded model directly from a {@link Message}.
    +059   *
    +060   * @param rawBytes Raw bytes to hold.
    +061   * @param mode Operating data mode (usually {@code BINARY}).
    +062   * @param type Fully-qualified model type name.
    +063   */
    +064  private EncodedModel(@Nonnull byte[] rawBytes, @Nonnull EncodingMode mode, @Nonnull String type) {
    +065    this.type = type;
    +066    this.dataMode = mode;
    +067    this.rawBytes = rawBytes;
    +068  }
    +069
    +070  /**
    +071   * Write the attached encoded Protocol Buffer data to the specified object stream, such that this object is fully
    +072   * packed for Java serialization purposes.
    +073   *
    +074   * @param out Output stream to write this encoded model object to.
    +075   * @throws IOException If an IO error of some kind occurs.
    +076   */
    +077  private void writeObject(@Nonnull ObjectOutputStream out) throws IOException {
    +078    out.writeObject(type);
    +079    out.writeObject(dataMode);
    +080    out.write(rawBytes.length);
    +081    out.write(rawBytes);
    +082  }
    +083
    +084  /**
    +085   * Re-inflate an encoded model from a Java serialization context. Read the stream to install local properties, such
    +086   * that the object is re-constituted.
    +087   *
    +088   * @param in Input stream to read object data from.
    +089   * @throws IOException If an IO error of some kind occurs.
    +090   * @throws ClassNotFoundException If the specified Protobuf model cannot be found or resolved.
    +091   */
    +092  private void readObject(@Nonnull ObjectInputStream in) throws IOException,ClassNotFoundException {
    +093    this.type = Objects.requireNonNull((String)in.readObject(),
    +094      "Cannot deserialize EncodedModel with empty type.");
    +095    this.dataMode = Objects.requireNonNull((EncodingMode)in.readObject(),
    +096      "Cannot deserialize EncodedModel with empty data mode.");
    +097
    +098    // read length-prefixed raw bytes
    +099    int datasize = in.read();
    +100    byte[] data = new byte[datasize];
    +101    int read = in.read(data);
    +102    assert datasize == read;
    +103    this.rawBytes = data;
    +104  }
    +105
    +106  /**
    +107   * Return an encoded representation of the provided message. This method is rather heavy-weight: it fully encodes the
    +108   * provided Protobuf message into the Protocol Buffers binary format.
    +109   *
    +110   * <p>Note that this method must access the message's descriptor. If a descriptor is already on-hand, use the other
    +111   * variant of this method.</p>
    +112   *
    +113   * @see #from(Message, Descriptors.Descriptor) If a descriptor is already on-hand for the provided message.
    +114   * @param message Message to encode as binary.
    +115   * @return Java-serializable wrapper including the encoded Protobuf model.
    +116   */
    +117  public static EncodedModel from(@Nonnull Message message) {
    +118    return from(message, null);
    +119  }
    +120
    +121  /**
    +122   * Return an encoded representation of the provided message. This method is rather heavy-weight: it fully encodes the
    +123   * provided Protobuf message into the Protocol Buffers binary format.
    +124   *
    +125   * <p>If a descriptor is <i>not</i> already on-hand, use the other variant of this method, which accesses the
    +126   * descriptor directly from the message.</p>
    +127   *
    +128   * @see #from(Message, Descriptors.Descriptor) If no descriptor is on-hand.
    +129   * @param message Message to encode as binary.
    +130   * @return Java-serializable wrapper including the encoded Protobuf model.
    +131   */
    +132  public static EncodedModel from(@Nonnull Message message, @Nullable Descriptors.Descriptor descriptor) {
    +133    return new EncodedModel(
    +134      message.toByteArray(),
    +135      EncodingMode.BINARY,
    +136      (descriptor != null ? descriptor : message.getDescriptorForType()).getFullName());
    +137  }
    +138
    +139  /**
    +140   * Wrap a blob of opaque data, asserting that it is actually an encoded model record. Using this factory method, any
    +141   * of the Protobuf serialization formats may be used safely with this class.
    +142   *
    +143   * <p>All details must be provided manually to this method variant. It is incumbent on the developer that they line
    +144   * up properly. For safer options, see the other factory methods on this class.</p>
    +145   *
    +146   * @see #from(Message) To encode a model instance.
    +147   * @see #from(Message, Descriptors.Descriptor) To encode a model instance with a descriptor already in-hand.
    +148   * @param type Fully-qualified type name, for the encoded instance we are storing.
    +149   * @param data Raw data for the encoded model to be wrapped.
    +150   * @return Encoded model instance.
    +151   */
    +152  public static EncodedModel wrap(@Nonnull String type, @Nonnull EncodingMode mode, @Nonnull byte[] data) {
    +153    return new EncodedModel(data, mode, type);
    +154  }
    +155
    +156  // -- Cloneable -- //
    +157
    +158  /** {@inheritDoc} */
    +159  @Override
    +160  @SuppressWarnings("MethodDoesntCallSuperMethod")
    +161  protected EncodedModel clone() {
    +162    byte[] rawData = new byte[this.rawBytes.length];
    +163    System.arraycopy(this.rawBytes, 0, rawData, 0, this.rawBytes.length);
    +164    return new EncodedModel(rawData, this.dataMode, this.type);
    +165  }
    +166
    +167
    +168  // -- Equals/Hash -- //
    +169
    +170  /** {@inheritDoc} */
    +171  @Override
    +172  public boolean equals(Object o) {
    +173    if (this == o) return true;
    +174    if (o == null || getClass() != o.getClass()) return false;
    +175    EncodedModel that = (EncodedModel) o;
    +176    return com.google.common.base.Objects.equal(type, that.type) &&
    +177      dataMode == that.dataMode &&
    +178      com.google.common.base.Objects.equal(
    +179        Arrays.hashCode(rawBytes),
    +180        Arrays.hashCode(that.rawBytes));
    +181  }
    +182
    +183  /** {@inheritDoc} */
    +184  @Override
    +185  public int hashCode() {
    +186    return com.google.common.base.Objects
    +187      .hashCode(type, dataMode, Arrays.hashCode(rawBytes));
    +188  }
    +189
    +190  /** {@inheritDoc} */
    +191  @Override
    +192  public String toString() {
    +193    return "EncodedModel{" +
    +194      "dataMode='" + dataMode + '\'' +
    +195      ", type=" + type +
    +196      '}';
    +197  }
    +198
    +199  // -- Getters -- //
    +200
    +201  /** @return Raw bytes held by this encoded model. */
    +202  public @Nonnull ByteString getRawBytes() {
    +203    return ByteString.copyFrom(this.rawBytes);
    +204  }
    +205
    +206  /** @return Fully-qualified path to the type of model backing this encoded instance. */
    +207  public @Nonnull String getType() {
    +208    return type;
    +209  }
    +210
    +211  /** @return Operating mode for the underlying model instance data. */
    +212  public @Nonnull EncodingMode getDataMode() {
    +213    return dataMode;
    +214  }
    +215
    +216  // -- Inflate -- //
    +217
    +218  /**
    +219   * Re-inflate the encoded model data held by this object, into an instance of {@code Model}, via the provided
    +220   * {@code builder}.
    +221   *
    +222   * <p><b>Note:</b> before the model is returned from this method, it will be casted to match the generic type the user
    +223   * is looking for. It is incumbent on the invoking developer to make sure the generic access that occurs won't produce
    +224   * a {@link ClassCastException}. {@link #getType()} can be interrogated to resolve types before inflation.</p>
    +225   *
    +226   * @param model Empty model instance from which to resolve a parser.
    +227   * @param <Model> Generic model type inflated and returned by this method.
    +228   * @return Instance of the model, inflated from the encoded data.
    +229   * @throws InvalidProtocolBufferException If the held data is incorrectly formatted.
    +230   */
    +231  public @Nonnull <Model extends Message> Model inflate(@Nonnull Message model) throws InvalidProtocolBufferException {
    +232    if (dataMode == EncodingMode.JSON) {
    +233      Message.Builder builder = model.newBuilderForType();
    +234      JsonFormat.parser().merge(
    +235        new String(this.rawBytes, StandardCharsets.UTF_8),
    +236        builder);
    +237
    +238      //noinspection unchecked
    +239      return (Model)builder.build();
    +240    } else {
    +241      //noinspection unchecked
    +242      return (Model)model.getParserForType().parseFrom(this.rawBytes);
    +243    }
    +244  }
    +245}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/EncodingMode.html b/docs/java/src-html/gust/backend/model/EncodingMode.html new file mode 100644 index 000000000..b18db0962 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/EncodingMode.html @@ -0,0 +1,97 @@ + + + +Source code + + + +
    + +
    + + diff --git a/docs/java/src-html/gust/backend/model/FetchOptions.MaskMode.html b/docs/java/src-html/gust/backend/model/FetchOptions.MaskMode.html new file mode 100644 index 000000000..f68706d07 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/FetchOptions.MaskMode.html @@ -0,0 +1,130 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015
    +016import com.google.protobuf.FieldMask;
    +017
    +018import javax.annotation.Nonnull;
    +019import java.util.Optional;
    +020
    +021
    +022/**
    +023 * Specifies options which may be applied, generically, to model instance fetch operations implemented through the
    +024 * {@link PersistenceDriver} interface.
    +025 */
    +026public interface FetchOptions extends CacheOptions, OperationOptions {
    +027  /** Default set of fetch options. */
    +028  FetchOptions DEFAULTS = new FetchOptions() {};
    +029
    +030  /** Enumerates ways the {@code FieldMask} may be applied. */
    +031  enum MaskMode {
    +032    /** Only include fields mentioned in the field mask. */
    +033    INCLUDE,
    +034
    +035    /** Omit fields mentioned in the field mask. */
    +036    EXCLUDE,
    +037
    +038    /** Treat the field mask as a projection, for query purposes only. */
    +039    PROJECTION
    +040  }
    +041
    +042  /** @return Field mask to apply when fetching properties. Fields not mentioned in the mask will be omitted. */
    +043  default @Nonnull Optional<FieldMask> fieldMask() {
    +044    return Optional.empty();
    +045  }
    +046
    +047  /** @return Mode to operate in when applying the affixed field mask, if any. */
    +048  default @Nonnull MaskMode fieldMaskMode() {
    +049    return MaskMode.INCLUDE;
    +050  }
    +051
    +052  /** @return Read snapshot time, if applicable. */
    +053  default @Nonnull Optional<Long> snapshot() {
    +054    return Optional.empty();
    +055  }
    +056}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/FetchOptions.html b/docs/java/src-html/gust/backend/model/FetchOptions.html new file mode 100644 index 000000000..f68706d07 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/FetchOptions.html @@ -0,0 +1,130 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015
    +016import com.google.protobuf.FieldMask;
    +017
    +018import javax.annotation.Nonnull;
    +019import java.util.Optional;
    +020
    +021
    +022/**
    +023 * Specifies options which may be applied, generically, to model instance fetch operations implemented through the
    +024 * {@link PersistenceDriver} interface.
    +025 */
    +026public interface FetchOptions extends CacheOptions, OperationOptions {
    +027  /** Default set of fetch options. */
    +028  FetchOptions DEFAULTS = new FetchOptions() {};
    +029
    +030  /** Enumerates ways the {@code FieldMask} may be applied. */
    +031  enum MaskMode {
    +032    /** Only include fields mentioned in the field mask. */
    +033    INCLUDE,
    +034
    +035    /** Omit fields mentioned in the field mask. */
    +036    EXCLUDE,
    +037
    +038    /** Treat the field mask as a projection, for query purposes only. */
    +039    PROJECTION
    +040  }
    +041
    +042  /** @return Field mask to apply when fetching properties. Fields not mentioned in the mask will be omitted. */
    +043  default @Nonnull Optional<FieldMask> fieldMask() {
    +044    return Optional.empty();
    +045  }
    +046
    +047  /** @return Mode to operate in when applying the affixed field mask, if any. */
    +048  default @Nonnull MaskMode fieldMaskMode() {
    +049    return MaskMode.INCLUDE;
    +050  }
    +051
    +052  /** @return Read snapshot time, if applicable. */
    +053  default @Nonnull Optional<Long> snapshot() {
    +054    return Optional.empty();
    +055  }
    +056}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/InvalidModelType.html b/docs/java/src-html/gust/backend/model/InvalidModelType.html new file mode 100644 index 000000000..f71f28b81 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/InvalidModelType.html @@ -0,0 +1,156 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.common.base.Joiner;
    +016import com.google.protobuf.Descriptors.Descriptor;
    +017import com.google.protobuf.Message;
    +018import tools.elide.core.DatapointType;
    +019
    +020import javax.annotation.Nonnull;
    +021import java.util.Set;
    +022
    +023
    +024/**
    +025 * Specifies an error, wherein a user has requested a data adapter or some other database object, for a model which is
    +026 * not usable with data storage systems (via annotations).
    +027 */
    +028public final class InvalidModelType extends PersistenceException {
    +029  /** Message that violated some type expectation. */
    +030  private final Descriptor violatingSchema;
    +031
    +032  /** Set of allowed types that the message didn't match. */
    +033  private final Set<DatapointType> datapointTypes;
    +034
    +035  /**
    +036   * Create a persistence exception from scratch.
    +037   *
    +038   * @param type Descriptor type.
    +039   * @param expectedTypes Available types, any one of which it can conform to.
    +040   */
    +041  InvalidModelType(@Nonnull Descriptor type, @Nonnull Set<DatapointType> expectedTypes) {
    +042    super(String.format("Invalid model type: '%s' is not one of the allowed types '%s'.",
    +043      type.getName(),
    +044      Joiner.on(", ").join(expectedTypes)));
    +045    this.violatingSchema = type;
    +046    this.datapointTypes = expectedTypes;
    +047  }
    +048
    +049  /**
    +050   * Factory to create a new `InvalidModelType` exception directly from a model descriptor.
    +051   *
    +052   * @param type Type of model to create this exception for.
    +053   * @param expectedTypes Available types, any one of which it can conform to.
    +054   * @return Created `InvalidModelType` exception.
    +055   */
    +056  static @Nonnull InvalidModelType from(@Nonnull Descriptor type, @Nonnull Set<DatapointType> expectedTypes) {
    +057    return new InvalidModelType(type, expectedTypes);
    +058  }
    +059
    +060  /**
    +061   * Factory to create a new `InvalidModelType` exception from a model instance.
    +062   *
    +063   * @param type Type of model to create this exception for.
    +064   * @param expectedTypes Available types, any one of which it can conform to.
    +065   * @return Created `InvalidModelType` exception.
    +066   */
    +067  static @Nonnull InvalidModelType from(@Nonnull Message type, @Nonnull Set<DatapointType> expectedTypes) {
    +068    return from(type.getDescriptorForType(), expectedTypes);
    +069  }
    +070
    +071  // -- Getters -- //
    +072
    +073  /** @return Message instance that violated this type constraint. */
    +074  public Descriptor getViolatingSchema() {
    +075    return violatingSchema;
    +076  }
    +077
    +078  /** @return Allowed types which the violating message did not match. */
    +079  public Set<DatapointType> getDatapointTypes() {
    +080    return datapointTypes;
    +081  }
    +082}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/MissingAnnotatedField.html b/docs/java/src-html/gust/backend/model/MissingAnnotatedField.html new file mode 100644 index 000000000..6113a5427 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/MissingAnnotatedField.html @@ -0,0 +1,132 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.protobuf.Descriptors;
    +016import tools.elide.core.FieldType;
    +017
    +018import javax.annotation.Nonnull;
    +019
    +020
    +021/** Specifies that a model was missing a required annotated-field for a given operation. */
    +022@SuppressWarnings("WeakerAccess")
    +023public final class MissingAnnotatedField extends PersistenceException {
    +024  /** Specifies the descriptor that violated the required field constraint. */
    +025  private final @Nonnull Descriptors.Descriptor violatingSchema;
    +026
    +027  /** Field type that was required but not found. */
    +028  private final @Nonnull FieldType requiredField;
    +029
    +030  /**
    +031   * Create an exception describing a missing, but required, annotated schema field.
    +032   *
    +033   * @param descriptor Descriptor for the message type in question.
    +034   * @param type Field type that was required but missing.
    +035   */
    +036  MissingAnnotatedField(@Nonnull Descriptors.Descriptor descriptor, @Nonnull FieldType type) {
    +037    super(String.format(
    +038      "Model type '%s' failed to be processed, because it is missing the annotated field '%s', " +
    +039      "which was required for the requested operation.",
    +040      descriptor.getName(),
    +041      type.name()));
    +042
    +043    this.violatingSchema = descriptor;
    +044    this.requiredField = type;
    +045  }
    +046
    +047  // -- Getters -- //
    +048
    +049  /** @return Descriptor for the schema type which violated the required-field constraint. */
    +050  public @Nonnull Descriptors.Descriptor getViolatingSchema() {
    +051    return violatingSchema;
    +052  }
    +053
    +054  /** @return The type of field that must be added to pass the failing constraint. */
    +055  public @Nonnull FieldType getRequiredField() {
    +056    return requiredField;
    +057  }
    +058}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/ModelAdapter.html b/docs/java/src-html/gust/backend/model/ModelAdapter.html new file mode 100644 index 000000000..160a23647 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/ModelAdapter.html @@ -0,0 +1,275 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.common.base.Function;
    +016import com.google.common.util.concurrent.AsyncFunction;
    +017import com.google.common.util.concurrent.Futures;
    +018import com.google.common.util.concurrent.ListenableFuture;
    +019import com.google.common.util.concurrent.ListeningScheduledExecutorService;
    +020import com.google.protobuf.Message;
    +021import gust.backend.runtime.ReactiveFuture;
    +022import tools.elide.core.DatapointType;
    +023
    +024import javax.annotation.Nonnull;
    +025import javax.annotation.Nullable;
    +026import java.util.Objects;
    +027import java.util.Optional;
    +028
    +029import static java.lang.String.format;
    +030import static gust.backend.model.ModelMetadata.*;
    +031
    +032
    +033/**
    +034 * Specifies an adapter for data models. "Adapters" are responsible for handling data storage and recall, and generic
    +035 * model serialization and deserialization activities. Adapters are composed of a handful of components, which together
    +036 * define the functionality that composes the adapter writ-large.
    +037 *
    +038 * <p>Major components of functionality are described below:
    +039 * <ul>
    +040 *   <li><b>Codec:</b> The {@link ModelCodec} is responsible for serialization and deserialization. In some cases,
    +041 *   codecs can be mixed with other objects to customize how data is stored. For example, the Redis cache layer supports
    +042 *   using ProtoJSON, Protobuf binary, or JVM serialization, optionally with compression. On the other hand, the
    +043 *   Firestore adapter specifies its own codecs which serialize into Firestore models.</li>
    +044 *   <li><b>Driver:</b> The {@link PersistenceDriver} is responsible for persisting serialized/collapsed models into
    +045 *   underlying storage, deleting data recalling data via key fetches, and querying indexes to produce result-sets.</li>
    +046 * </ul></p>
    +047 *
    +048 * @see PersistenceDriver Interface which defines basic driver functionality.
    +049 * @see CacheDriver Cache-specific persistence driver support, included in this object.
    +050 * @see DatabaseAdapter Extends this interface with richer data engine features.
    +051 * @param <Key> Key type, instances of which uniquely address instances of {@code Model}.
    +052 * @param <Model> Model type which this adapter is responsible for adapting.
    +053 * @param <ReadIntermediate> Intermediate record format used by the implementation when de-serializing model instances.
    +054 * @param <WriteIntermediate> Intermediate record format used when serializing model instances for write.
    +055 */
    +056@SuppressWarnings("UnstableApiUsage")
    +057public interface ModelAdapter<Key extends Message, Model extends Message, ReadIntermediate, WriteIntermediate>
    +058  extends PersistenceDriver<Key, Model, ReadIntermediate, WriteIntermediate> {
    +059  // -- Interface: Drivers -- //
    +060  /**
    +061   * Return the cache driver in use for this particular model adapter. If a cache driver is present, and active/enabled
    +062   * according to database driver settings, it will be used on read-paths (such as fetching objects by ID).
    +063   *
    +064   * @return Cache driver currently in use by this model adapter.
    +065   */
    +066  @Nonnull Optional<CacheDriver<Key, Model>> cache();
    +067
    +068  /**
    +069   * Return the lower-level {@link PersistenceDriver} powering this adapter. The driver is responsible for communicating
    +070   * with the actual backing storage service, either via local stubs/emulators or a production API.
    +071   *
    +072   * @return Persistence driver instance currently in use by this model adapter.
    +073   */
    +074  @Nonnull PersistenceDriver<Key, Model, ReadIntermediate, WriteIntermediate> engine();
    +075
    +076  // -- Interface: Execution -- //
    +077  /** {@inheritDoc} */
    +078  @Override
    +079  default @Nonnull ListeningScheduledExecutorService executorService() {
    +080    return engine().executorService();
    +081  }
    +082
    +083  // -- Interface: Key Generation -- //
    +084  /** {@inheritDoc} */
    +085  @Override
    +086  default @Nonnull Key generateKey(@Nonnull Message instance) {
    +087    return engine().generateKey(instance);
    +088  }
    +089
    +090  // -- Interface: Fetch -- //
    +091  /** {@inheritDoc} */
    +092  @Override
    +093  default @Nonnull ReactiveFuture<Optional<Model>> retrieve(@Nonnull Key key, @Nonnull FetchOptions options) {
    +094    enforceRole(key, DatapointType.OBJECT_KEY);
    +095    final ListeningScheduledExecutorService exec = options.executorService().orElseGet(this::executorService);
    +096    if (Internals.logging.isTraceEnabled())
    +097      Internals.logging.trace(format("Retrieving record '%s' from storage (executor: '%s')...", id(key), exec));
    +098
    +099    final Optional<CacheDriver<Key, Model>> cache = this.cache();
    +100    if (options.enableCache() && cache.isPresent()) {
    +101      if (Internals.logging.isDebugEnabled())
    +102        Internals.logging.debug(
    +103          format("Caching enabled with object of type '%s'.", cache.get().getClass().getSimpleName()));
    +104
    +105      // cache result future
    +106      final ReactiveFuture<Optional<Model>> cacheFetchFuture = Objects.requireNonNull(
    +107        cache.get().fetch(key, options, exec), "Cache cannot return `null` for `retrieve`.");
    +108
    +109      // wrap in a future, with a non-propagating cancelling timeout, which handles any nulls from the cache.
    +110      final ListenableFuture<Optional<Model>> cacheFuture = (Futures.nonCancellationPropagating(
    +111        Futures.transform(cacheFetchFuture, new Function<>() {
    +112          @Override
    +113          public @Nonnull Optional<Model> apply(@Nullable Optional<Model> cacheResult) {
    +114            if (Internals.logging.isDebugEnabled()) {
    +115              //noinspection OptionalAssignedToNull
    +116              Internals.logging.debug(
    +117                format("Received response from cache (value present: '%s').",
    +118                  cacheResult == null ? "null" : cacheResult.isPresent()));
    +119            }
    +120            if (cacheResult != null && cacheResult.isPresent()) {
    +121              return cacheResult;
    +122            }
    +123            return Optional.empty();  // not found
    +124          }
    +125        }, exec)));
    +126
    +127      // wrap the cache future in a timeout function, which enforces the configured (or default) cache timeout
    +128      final ListenableFuture<Optional<Model>> limitedCacheFuture = Futures.withTimeout(
    +129        cacheFuture,
    +130        options.cacheTimeout().orElse(PersistenceDriver.DEFAULT_CACHE_TIMEOUT),
    +131        options.cacheTimeoutUnit(),
    +132        exec);
    +133
    +134      // finally, respond to a cache miss by deferring to the driver directly. this must be separate from `cacheFuture`
    +135      // to allow separate cancellation of the cache future and the future which backstops it.
    +136      return ReactiveFuture.wrap(Futures.transformAsync(limitedCacheFuture, new AsyncFunction<>() {
    +137        @Override
    +138        public @Nonnull ListenableFuture<Optional<Model>> apply(@Nullable Optional<Model> cacheResult) {
    +139          if (Internals.logging.isTraceEnabled()) {
    +140            //noinspection OptionalAssignedToNull
    +141            Internals.logging.debug(
    +142              format("Returning response from cache (value present: '%s')",
    +143                cacheResult == null ? "null" : cacheResult.isPresent()));
    +144          }
    +145
    +146          if (cacheResult != null && cacheResult.isPresent()) {
    +147            return Futures.immediateFuture(cacheResult);
    +148          } else {
    +149            var record = engine().retrieve(key, options);
    +150            record.addListener(() -> {
    +151              if (Internals.logging.isDebugEnabled()) {
    +152                Internals.logging.debug("Response was NOT cached. Storing in cache...");
    +153              }
    +154
    +155              Internals.swallowExceptions(() -> {
    +156                Optional<Model> fetchResult = record.get();
    +157                fetchResult.ifPresent(model -> cache.get().put(
    +158                    key,
    +159                    model,
    +160                    options.executorService().orElseGet(ModelAdapter.this::executorService)));
    +161              });
    +162            }, options.executorService().orElseGet(ModelAdapter.this::executorService));
    +163            return record;
    +164          }
    +165        }
    +166      }, exec), exec);
    +167    } else {
    +168      if (Internals.logging.isDebugEnabled()) {
    +169        Internals.logging.debug("Caching is disabled. Deferring to driver.");
    +170      }
    +171      return engine().retrieve(key, options);
    +172    }
    +173  }
    +174
    +175  // -- Interface: Persist -- //
    +176  /** {@inheritDoc} */
    +177  @Override
    +178  default @Nonnull ReactiveFuture<Model> persist(@Nullable Key key,
    +179                                                 @Nonnull Model model,
    +180                                                 @Nonnull WriteOptions options) {
    +181    return engine().persist(key, model, options);
    +182  }
    +183
    +184  /** {@inheritDoc} */
    +185  @Override
    +186  default @Nonnull ReactiveFuture<Key> delete(@Nonnull Key key,
    +187                                              @Nonnull DeleteOptions options) {
    +188    ReactiveFuture<Key> op = engine().delete(key, options);
    +189    if (options.enableCache()) {
    +190      // if caching is enabled and a cache driver is present, make sure to evict any cached record behind this key.
    +191      Optional<CacheDriver<Key, Model>> cacheDriver = this.cache();
    +192      if (cacheDriver.isPresent()) {
    +193        ListeningScheduledExecutorService exec = options.executorService().orElseGet(this::executorService);
    +194        ReactiveFuture<Key> storageDelete = engine().delete(key, options);
    +195        ReactiveFuture<Key> cacheEvict = cacheDriver.get().evict(key, exec);
    +196        return ReactiveFuture.wrap(Futures.whenAllComplete(storageDelete, cacheEvict).call(() -> key, exec));
    +197      }
    +198    }
    +199    return op;
    +200  }
    +201}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/ModelCodec.html b/docs/java/src-html/gust/backend/model/ModelCodec.html new file mode 100644 index 000000000..663aab705 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/ModelCodec.html @@ -0,0 +1,170 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.protobuf.Message;
    +016
    +017import javax.annotation.Nonnull;
    +018import java.io.IOException;
    +019
    +020
    +021/**
    +022 * Specifies the requisite interface for a data codec implementation. These objects are responsible for performing model
    +023 * serialization and deserialization, within different circumstances. For example, models are used with databases via
    +024 * adapters that serialize each model into a corresponding database object, or series of database calls.
    +025 *
    +026 * <p>Adapters are <i>bi-directional</i>, i.e., they must support transitioning both <i>to</i> and <i>from</i> message
    +027 * representations, based on circumstance. Services are the only case where this is generally not necessary, because
    +028 * gRPC handles serialization automatically.</p>
    +029 *
    +030 * @see ModelSerializer Surface definition for a model serializer.
    +031 * @see ModelDeserializer Surface definition for a model de-serializer.
    +032 * @param <Model> Model type which this codec is responsible for serializing and de-serializing.
    +033 * @param <WriteIntermediate> Intermediate record type which this codec converts model instances into.
    +034 */
    +035@SuppressWarnings("unused")
    +036public interface ModelCodec<Model extends Message, WriteIntermediate, ReadIntermediate> {
    +037  // -- Components -- //
    +038  /**
    +039   * Acquire an instance of the {@link ModelSerializer} attached to this adapter. The instance is not guaranteed to be
    +040   * created fresh for this invocation.
    +041   *
    +042   * @see #deserializer() For the inverse of this method.
    +043   * @see #deserialize(Object) To call into de-serialization directly.
    +044   * @return Serializer instance.
    +045   */
    +046  @Nonnull ModelSerializer<Model, WriteIntermediate> serializer();
    +047
    +048  /**
    +049   * Acquire an instance of the {@link ModelDeserializer} attached to this adapter. The instance is not guaranteed to be
    +050   * created fresh for this invocation.
    +051   *
    +052   * @see #serializer() For the inverse of this method.
    +053   * @see #serialize(Message) To call into serialization directly.
    +054   * @return Deserializer instance.
    +055   */
    +056  @Nonnull ModelDeserializer<ReadIntermediate, Model> deserializer();
    +057
    +058  /**
    +059   * Retrieve the default instance stored with this codec. Each {@link Message} with a paired {@link ModelCodec} retains
    +060   * a reference to its corresponding default instance.
    +061   *
    +062   * @return Default model instance.
    +063   */
    +064  @Nonnull Model instance();
    +065
    +066  // -- Proxies -- //
    +067  /**
    +068   * Sugar shortcut to serialize a model through the current codec's installed {@link ModelSerializer}.
    +069   *
    +070   * <p>This method just proxies to that object (which can be acquired via {@link #serializer()}). If any error occurs
    +071   * while serializing, {@link ModelDeflateException} is thrown.</p>
    +072   *
    +073   * @param instance Input model to serialize.
    +074   * @return Serialized output data or object.
    +075   * @throws ModelDeflateException If some error occurs while serializing the model.
    +076   * @throws IOException If some IO error occurs.
    +077   */
    +078  default @Nonnull WriteIntermediate serialize(Model instance) throws ModelDeflateException, IOException {
    +079    return serializer().deflate(instance);
    +080  }
    +081
    +082  /**
    +083   * Sugar shortcut to de-serialize a model through the current codec's installed {@link ModelDeserializer}.
    +084   *
    +085   * <p>This method just proxies to that object (which can be acquired via {@link #deserializer()}). If any error occurs
    +086   * while de-serializing, {@link ModelInflateException} is thrown.</p>
    +087   *
    +088   * @param input Input data to de-serialize into a model instance.
    +089   * @return Model instance, deserialized from the input data.
    +090   * @throws ModelInflateException If some error occurs while de-serializing the model.
    +091   * @throws IOException If some IO error occurs.
    +092   */
    +093  default @Nonnull Model deserialize(ReadIntermediate input) throws ModelInflateException, IOException {
    +094    return deserializer().inflate(input);
    +095  }
    +096}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/ModelDeflateException.html b/docs/java/src-html/gust/backend/model/ModelDeflateException.html new file mode 100644 index 000000000..b903c9374 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/ModelDeflateException.html @@ -0,0 +1,113 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import javax.annotation.Nonnull;
    +016import javax.annotation.Nullable;
    +017
    +018
    +019/** Describes an error that occurred while serializing a model. */
    +020public final class ModelDeflateException extends PersistenceException {
    +021  /**
    +022   * Main package-private constructor.
    +023   *
    +024   * @param message Error message to apply.
    +025   * @param cause Optional cause.
    +026   */
    +027  ModelDeflateException(@Nonnull String message, @Nullable Throwable cause) {
    +028    super(message, cause);
    +029  }
    +030
    +031  /**
    +032   * Create a persistence exception with a throwable as a cause.
    +033   *
    +034   * @param cause Cause for the error.
    +035   */
    +036  ModelDeflateException(@Nonnull Throwable cause) {
    +037    super(cause);
    +038  }
    +039}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/ModelDeserializer.DeserializationError.html b/docs/java/src-html/gust/backend/model/ModelDeserializer.DeserializationError.html new file mode 100644 index 000000000..f97e384a3 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/ModelDeserializer.DeserializationError.html @@ -0,0 +1,131 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.protobuf.Message;
    +016
    +017import javax.annotation.Nonnull;
    +018import java.io.IOException;
    +019
    +020
    +021/**
    +022 * Describes the interface for a de-serializer, which is responsible for transitioning (adapting) objects from a given
    +023 * type (or tree of types) to {@link Message} instances, corresponding with the data record type managed by this object.
    +024 *
    +025 * <p>Deserializers are part of a wider set of objects, including the inverse of this object, which is called a
    +026 * {@link ModelSerializer}. Generally these objects come in matching pairs, but sometimes they are mixed and matched as
    +027 * needed. Pairs of serializers/de-serializers are grouped by a {@link ModelCodec}.</p>
    +028 *
    +029 * @see ModelSerializer The inverse of this object, which is responsible for adapting {@link Message} instances to some
    +030 *      other type of object or data.
    +031 * @param <Input> Input data type, which this de-serializer will convert into a message instance.
    +032 * @param <Model> Message object type, which is the output of this de-serializer.
    +033 */
    +034public interface ModelDeserializer<Input, Model extends Message> {
    +035  /**
    +036   * De-serialize a model instance from the provided input type, throwing exceptions verbosely if we are unable to
    +037   * correctly, verifiably, and properly load the record.
    +038   *
    +039   * @param input Input data or object from which to load the model instance.
    +040   * @return De-serialized and inflated model instance. Always a {@link Message}.
    +041   * @throws ModelInflateException If the model fails to load for any reason.
    +042   * @throws IOException If some IO error occurs.
    +043   */
    +044  @Nonnull Model inflate(@Nonnull Input input) throws ModelInflateException, IOException;
    +045
    +046  /** Describes errors that occur during model deserialization or inflation activities. */
    +047  final class DeserializationError extends RuntimeException {
    +048    /**
    +049     * Create a generic de-serializer error from the provided message.
    +050     *
    +051     * @param message Error message.
    +052     */
    +053    DeserializationError(@Nonnull String message) {
    +054      super(message);
    +055    }
    +056  }
    +057}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/ModelDeserializer.html b/docs/java/src-html/gust/backend/model/ModelDeserializer.html new file mode 100644 index 000000000..f97e384a3 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/ModelDeserializer.html @@ -0,0 +1,131 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.protobuf.Message;
    +016
    +017import javax.annotation.Nonnull;
    +018import java.io.IOException;
    +019
    +020
    +021/**
    +022 * Describes the interface for a de-serializer, which is responsible for transitioning (adapting) objects from a given
    +023 * type (or tree of types) to {@link Message} instances, corresponding with the data record type managed by this object.
    +024 *
    +025 * <p>Deserializers are part of a wider set of objects, including the inverse of this object, which is called a
    +026 * {@link ModelSerializer}. Generally these objects come in matching pairs, but sometimes they are mixed and matched as
    +027 * needed. Pairs of serializers/de-serializers are grouped by a {@link ModelCodec}.</p>
    +028 *
    +029 * @see ModelSerializer The inverse of this object, which is responsible for adapting {@link Message} instances to some
    +030 *      other type of object or data.
    +031 * @param <Input> Input data type, which this de-serializer will convert into a message instance.
    +032 * @param <Model> Message object type, which is the output of this de-serializer.
    +033 */
    +034public interface ModelDeserializer<Input, Model extends Message> {
    +035  /**
    +036   * De-serialize a model instance from the provided input type, throwing exceptions verbosely if we are unable to
    +037   * correctly, verifiably, and properly load the record.
    +038   *
    +039   * @param input Input data or object from which to load the model instance.
    +040   * @return De-serialized and inflated model instance. Always a {@link Message}.
    +041   * @throws ModelInflateException If the model fails to load for any reason.
    +042   * @throws IOException If some IO error occurs.
    +043   */
    +044  @Nonnull Model inflate(@Nonnull Input input) throws ModelInflateException, IOException;
    +045
    +046  /** Describes errors that occur during model deserialization or inflation activities. */
    +047  final class DeserializationError extends RuntimeException {
    +048    /**
    +049     * Create a generic de-serializer error from the provided message.
    +050     *
    +051     * @param message Error message.
    +052     */
    +053    DeserializationError(@Nonnull String message) {
    +054      super(message);
    +055    }
    +056  }
    +057}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/ModelInflateException.html b/docs/java/src-html/gust/backend/model/ModelInflateException.html new file mode 100644 index 000000000..8fdb0534a --- /dev/null +++ b/docs/java/src-html/gust/backend/model/ModelInflateException.html @@ -0,0 +1,113 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import javax.annotation.Nonnull;
    +016import javax.annotation.Nullable;
    +017
    +018
    +019/** Describes an error that occurred while de-serializing a model. */
    +020public final class ModelInflateException extends PersistenceException {
    +021  /**
    +022   * Main package-private constructor.
    +023   *
    +024   * @param message Error message to apply.
    +025   * @param cause Optional cause.
    +026   */
    +027  ModelInflateException(@Nonnull String message, @Nullable Throwable cause) {
    +028    super(message, cause);
    +029  }
    +030
    +031  /**
    +032   * Create a persistence exception with a throwable as a cause.
    +033   *
    +034   * @param cause Cause for the error.
    +035   */
    +036  ModelInflateException(@Nonnull Throwable cause) {
    +037    super(cause);
    +038  }
    +039}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/ModelMetadata.FieldContainer.html b/docs/java/src-html/gust/backend/model/ModelMetadata.FieldContainer.html new file mode 100644 index 000000000..12edad7df --- /dev/null +++ b/docs/java/src-html/gust/backend/model/ModelMetadata.FieldContainer.html @@ -0,0 +1,1838 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.common.annotations.VisibleForTesting;
    +016import com.google.common.base.CharMatcher;
    +017import com.google.common.collect.ImmutableSortedSet;
    +018import com.google.errorprone.annotations.CanIgnoreReturnValue;
    +019import com.google.protobuf.DescriptorProtos.FieldOptions;
    +020import com.google.protobuf.DescriptorProtos.MessageOptions;
    +021import com.google.protobuf.Descriptors.Descriptor;
    +022import com.google.protobuf.Descriptors.FieldDescriptor;
    +023import com.google.protobuf.FieldMask;
    +024import com.google.protobuf.GeneratedMessage.GeneratedExtension;
    +025import com.google.protobuf.Message;
    +026import com.google.protobuf.util.FieldMaskUtil;
    +027import tools.elide.core.CollectionMode;
    +028import tools.elide.core.Datamodel;
    +029import tools.elide.core.DatapointType;
    +030import tools.elide.core.FieldType;
    +031
    +032import javax.annotation.Nonnull;
    +033import javax.annotation.concurrent.Immutable;
    +034import javax.annotation.concurrent.ThreadSafe;
    +035import java.io.Serializable;
    +036import java.util.*;
    +037import java.util.concurrent.ConcurrentSkipListSet;
    +038import java.util.function.Function;
    +039import java.util.function.Predicate;
    +040import java.util.stream.Collectors;
    +041import java.util.stream.Stream;
    +042
    +043
    +044/**
    +045 * Utility helper class, which is responsible for resolving metadata (based on the core framework annotations) from
    +046 * arbitrary model definitions.
    +047 *
    +048 * <p>Model "metadata," in this case, refers to annotation-based declarations on the protocol buffer definitions
    +049 * themselves. As such, the source for most (if not all) of the data provided by this helper is the {@link Descriptor}
    +050 * that accompanies a Java-side protobuf model.</p>
    +051 *
    +052 * <p><b>Note:</b> Using this class, or the model layer writ-large, requires the full runtime Protobuf library (the lite
    +053 * runtime for Protobuf in Java does not include descriptors at all, which this class relies on).</p>
    +054 */
    +055@ThreadSafe
    +056@SuppressWarnings({"WeakerAccess", "unused", "OptionalUsedAsFieldOrParameterType"})
    +057public final class ModelMetadata {
    +058  private ModelMetadata() { /* Disallow construction. */ }
    +059
    +060  /** Utility class that points to a specific field, in a specific context. */
    +061  @Immutable
    +062  @ThreadSafe
    +063  public final static class FieldPointer implements Serializable, Comparable<FieldPointer> {
    +064    private static final long serialVersionUID = 20210203L;
    +065
    +066    /** Depth of this field, based on the number of dots in the path. */
    +067    private final @Nonnull Integer depth;
    +068
    +069    /** Access path, minus the leaf field. */
    +070    private final @Nonnull String parent;
    +071
    +072    /** Access path to the field in some context. */
    +073    private final @Nonnull String path;
    +074
    +075    /** Base model type. */
    +076    private final @Nonnull Descriptor base;
    +077
    +078    /** Field descriptor for the field in question. */
    +079    private final @Nonnull FieldDescriptor field;
    +080
    +081    /**
    +082     * Setup a new field pointer - generally kept private and resolved via {@link ModelMetadata}.
    +083     *
    +084     * @param base Base model type where {@code path} begins.
    +085     * @param parent Dotted-path without the leaf field, or just `""` if the field is at the root.
    +086     * @param path Dotted-path to the field in question.
    +087     * @param field Field descriptor for the field in question.
    +088     */
    +089    FieldPointer(@Nonnull Descriptor base,
    +090                 @Nonnull String parent,
    +091                 @Nonnull String path,
    +092                 @Nonnull FieldDescriptor field) {
    +093      this.path = path;
    +094      this.base = base;
    +095      this.field = field;
    +096      this.parent = parent;
    +097      this.depth = CharMatcher.is('.').countIn(path);
    +098    }
    +099
    +100    /**
    +101     * Setup a new field pointer - generally kept private and resolved via {@link ModelMetadata}. This consructor
    +102     * creates a field without a parent set.
    +103     *
    +104     * @param base Base model type where {@code path} begins.
    +105     * @param path Dotted-path to the field in question.
    +106     * @param field Field descriptor for the field in question.
    +107     */
    +108    FieldPointer(@Nonnull Descriptor base,
    +109                 @Nonnull String path,
    +110                 @Nonnull FieldDescriptor field) {
    +111      this.path = path;
    +112      this.base = base;
    +113      this.field = field;
    +114      this.parent = "";
    +115      this.depth = CharMatcher.is('.').countIn(path);
    +116    }
    +117
    +118    /**
    +119     * Wrap the field at the specified name on the provided model.
    +120     *
    +121     * @param model Descriptor for a protocol buffer model.
    +122     * @param name Name of a field to get from the provided buffer model.
    +123     * @return Field pointer wrapping the provided information.
    +124     */
    +125    public static @Nonnull FieldPointer fieldAtName(@Nonnull Descriptor model,
    +126                                                    @Nonnull String name) {
    +127      return new FieldPointer(
    +128          model,
    +129          name,
    +130          model.findFieldByName(name)
    +131      );
    +132    }
    +133
    +134    /** {@inheritDoc} */
    +135    @Override
    +136    public boolean equals(Object o) {
    +137      if (this == o) return true;
    +138      if (o == null || getClass() != o.getClass()) return false;
    +139      FieldPointer that = (FieldPointer) o;
    +140      return this.depth.equals(that.depth)
    +141        && com.google.common.base.Objects.equal(path, that.path)
    +142        && com.google.common.base.Objects.equal(base.getFullName(), that.base.getFullName());
    +143    }
    +144
    +145    /** {@inheritDoc} */
    +146    @Override
    +147    public int hashCode() {
    +148      return com.google.common.base.Objects
    +149        .hashCode(path, base.getFullName());
    +150    }
    +151
    +152    /** {@inheritDoc} */
    +153    @Override
    +154    public int compareTo(@Nonnull FieldPointer other) {
    +155      return this.path.compareTo(other.path);
    +156    }
    +157
    +158    /** {@inheritDoc} */
    +159    @Override
    +160    public String toString() {
    +161      return "FieldPointer{" +
    +162        "base='" + base.getName() + '\'' +
    +163        ", path=" + path +
    +164        '}';
    +165    }
    +166
    +167    /** @return Path to the specified field. */
    +168    public @Nonnull String getParent() {
    +169      return parent;
    +170    }
    +171
    +172    /** @return Path to the specified field. */
    +173    public boolean hasParent() {
    +174      return !parent.isEmpty() && !parent.isBlank();
    +175    }
    +176
    +177    /** @return Path to the specified field. */
    +178    public @Nonnull String getPath() {
    +179      return path;
    +180    }
    +181
    +182    /** @return Simple proto name for the field. */
    +183    public @Nonnull String getName() {
    +184      return field.getName();
    +185    }
    +186
    +187    /** @return Simple JSON name for the field. */
    +188    public @Nonnull String getJsonName() {
    +189      return field.getJsonName();
    +190    }
    +191
    +192    /** @return Base model type where the specified path begins. */
    +193    public @Nonnull Descriptor getBase() {
    +194      return base;
    +195    }
    +196
    +197    /** @return Descriptor for the targeted field. */
    +198    public @Nonnull FieldDescriptor getField() {
    +199      return field;
    +200    }
    +201  }
    +202
    +203  /** Utility class that holds a {@link FieldPointer} and matching field value. */
    +204  public final static class FieldContainer<V> implements Serializable, Comparable<FieldContainer<V>> {
    +205    /** Pointer to the field which holds this value. */
    +206    private final @Nonnull FieldPointer field;
    +207
    +208    /** Value for the field, if found. */
    +209    private final @Nonnull Optional<V> value;
    +210
    +211    /**
    +212     * Setup a new field pointer - generally kept private and resolved via {@link ModelMetadata}.
    +213     *
    +214     * @param field Pointer to the field that holds this value.
    +215     * @param value Value extracted for the specified field.
    +216     */
    +217    FieldContainer(@Nonnull FieldPointer field,
    +218                   @Nonnull Optional<V> value) {
    +219      this.field = field;
    +220      this.value = value;
    +221    }
    +222
    +223    /** {@inheritDoc} */
    +224    @Override
    +225    public boolean equals(Object o) {
    +226      if (this == o) return true;
    +227      if (o == null || getClass() != o.getClass()) return false;
    +228      FieldContainer<?> that = (FieldContainer<?>) o;
    +229      return field.equals(that.field)
    +230        && (value.isPresent() == that.value.isPresent())
    +231        && (value.equals(that.value));
    +232    }
    +233
    +234    /** {@inheritDoc} */
    +235    @Override
    +236    public int hashCode() {
    +237      return com.google.common.base.Objects
    +238        .hashCode(field, value);
    +239    }
    +240
    +241    /** {@inheritDoc} */
    +242    @Override
    +243    public int compareTo(@Nonnull FieldContainer<V> other) {
    +244      return this.field.compareTo(other.field);
    +245    }
    +246
    +247    /** {@inheritDoc} */
    +248    @Override
    +249    public String toString() {
    +250      return "FieldContainer{" +
    +251        "" + field.base.getName() +
    +252        ", path=" + field.path +
    +253        ", hasValue=" + value.isPresent() +
    +254        '}';
    +255    }
    +256
    +257    /** Pointer to the field holding the specified value. */
    +258    public @Nonnull FieldPointer getField() {
    +259      return field;
    +260    }
    +261
    +262    /** Value associated with the specified field, or {@link Optional#empty()} if the field has no initialized value. */
    +263    public @Nonnull Optional<V> getValue() {
    +264      return value;
    +265    }
    +266  }
    +267
    +268  // -- Internals -- //
    +269
    +270  /**
    +271   * Match an annotation to a field. If the field is not annotated as such, the method returns `false`.
    +272   *
    +273   * @param field Field to check for the provided annotation.
    +274   * @param annotation Annotation to check for.
    +275   * @return Whether the field is annotated with the provided annotation.
    +276   */
    +277  public static boolean matchFieldAnnotation(@Nonnull FieldDescriptor field, @Nonnull FieldType annotation) {
    +278    if (field.getOptions().hasExtension(Datamodel.field)) {
    +279      var extension = field.getOptions().getExtension(Datamodel.field);
    +280      return annotation.equals(extension.getType());
    +281    }
    +282    return false;
    +283  }
    +284
    +285  /**
    +286   * Match a collection annotation. If the field or model is not annotated as such, the method returns `false`.
    +287   *
    +288   * @param field Field to check for the provided annotation.
    +289   * @param mode Collection mode to check for.
    +290   * @return Whether the field is annotated for the provided collection mode.
    +291   */
    +292  @SuppressWarnings("SameParameterValue")
    +293  public static boolean matchCollectionAnnotation(@Nonnull FieldDescriptor field, @Nonnull CollectionMode mode) {
    +294    if (field.getOptions().hasExtension(Datamodel.collection)) {
    +295      var extension = field.getOptions().getExtension(Datamodel.collection);
    +296      return mode.equals(extension.getMode());
    +297    }
    +298    return false;
    +299  }
    +300
    +301  /**
    +302   * Resolve a model field within the tree of {@code descriptor}, where an instance of annotation data of type
    +303   * {@code ext} is affixed to the field. If the (optional) provided {@code filter} function agrees, the item is
    +304   * returned to the caller in a {@link FieldPointer}.
    +305   *
    +306   * <p>If the field cannot be found, no exception is raised, and {@link Optional#empty()} is returned. The search may
    +307   * also be conducted in {@code recursive} mode, which proceeds to examine sub-messages if the requested field cannot
    +308   * be located on the top-level {@code descriptor}.</p>
    +309   *
    +310   * @param descriptor Descriptor where we should begin our search for the desired property.
    +311   * @param ext Extension the field is annotated with. Only fields annotated with this extension are eligible.
    +312   * @param recursive Whether to search recursively, or just on the top-level instance.
    +313   * @param filter Filter function to dispatch per-found-field. The first one to return {@code true} wins.
    +314   * @param stack Property stack, filled out as we recursively descend.
    +315   * @param <E> Generic type of the model extension object.
    +316   * @return Optional, containing either a resolved {@link FieldPointer}, or empty.
    +317   */
    +318  @VisibleForTesting
    +319  static @Nonnull <E> Optional<FieldPointer> resolveAnnotatedField(@Nonnull Descriptor descriptor,
    +320                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext,
    +321                                                                   @Nonnull Boolean recursive,
    +322                                                                   @Nonnull Optional<Function<E, Boolean>> filter,
    +323                                                                   @Nonnull String stack) {
    +324    Objects.requireNonNull(descriptor, "Cannot resolve field from `null` descriptor.");
    +325    Objects.requireNonNull(ext, "Cannot resolve field from `null` descriptor.");
    +326    Objects.requireNonNull(recursive, "Cannot pass `null` for `recursive` flag.");
    +327    Objects.requireNonNull(filter, "Pass empty optional, not `null`, for field filter parameter.");
    +328    Objects.requireNonNull(stack, "Recursive property stack should not be `null`.");
    +329
    +330    for (FieldDescriptor field : descriptor.getFields()) {
    +331      if (field.getOptions().hasExtension(ext)) {
    +332        var extension = field.getOptions().getExtension(ext);
    +333        if (filter.isEmpty() || filter.get().apply(extension))
    +334          return Optional.of(new FieldPointer(
    +335            descriptor,
    +336            stack.isEmpty() ? field.getName() : stack + "." + field.getName(),
    +337            field));
    +338      }
    +339
    +340      // should we recurse?
    +341      if (recursive && field.getType() == FieldDescriptor.Type.MESSAGE) {
    +342        // if so, append the current prop to the stack and give it a shot
    +343        //noinspection ConstantConditions
    +344        var sub = resolveAnnotatedField(
    +345          field.getMessageType(),
    +346          ext,
    +347          recursive,
    +348          filter,
    +349          stack.isEmpty() ? field.getName() : stack + "." + field.getName());
    +350        if (sub.isPresent())
    +351          return sub;
    +352      }
    +353    }
    +354    return Optional.empty();
    +355  }
    +356
    +357  /**
    +358   * Resolve a model field within the tree of {@code descriptor}, identified by the specified deep {@code path}. If the
    +359   * (optional) provided {@code filter} function agrees, the item is returned to the caller in a {@link FieldPointer}.
    +360   *
    +361   * <p>If the field cannot be found, no exception is raised, and {@link Optional#empty()} is returned. The search may
    +362   * also be conducted in {@code recursive} mode, which proceeds to examine sub-messages if the requested field cannot
    +363   * be located on the top-level {@code descriptor}.</p>
    +364   *
    +365   * @param original Top-level descriptor where we should begin our search for the desired property.
    +366   * @param descriptor Current-level descriptor we are scanning (for recursion).
    +367   * @param path Deep dotted-path to the field we are being asked to resolve.
    +368   * @param remaining Remaining segments of {@code path} to follow/compute.
    +369   * @return Optional, containing either a resolved {@link FieldPointer}, or empty.
    +370   * @throws IllegalArgumentException If the provided path is syntactically invalid.
    +371   * @throws IllegalArgumentException If an attempt is made to access a property on a primitive field.
    +372   */
    +373  @VisibleForTesting
    +374  static @Nonnull Optional<FieldPointer> resolveArbitraryField(@Nonnull Descriptor original,
    +375                                                               @Nonnull Descriptor descriptor,
    +376                                                               @Nonnull String path,
    +377                                                               @Nonnull String remaining) {
    +378    Objects.requireNonNull(original, "Cannot resolve field from `null` descriptor.");
    +379    Objects.requireNonNull(descriptor, "Cannot resolve field from `null` descriptor.");
    +380    Objects.requireNonNull(path, "Cannot resolve field from `null` path.");
    +381    Objects.requireNonNull(remaining, "Recursive remaining stack should not be `null`.");
    +382
    +383    if (remaining.startsWith(".") || remaining.endsWith(".") || remaining.contains(" "))
    +384      throw new IllegalArgumentException(String.format("Invalid deep-field path '%s'.", path));
    +385    if (!remaining.contains(".")) {
    +386      // maybe we're lucky and don't need to recurse
    +387      for (FieldDescriptor field : descriptor.getFields()) {
    +388        if (remaining.equals(field.getName())) {
    +389          return Optional.of(new FieldPointer(
    +390            original,
    +391            path,
    +392            field));
    +393        }
    +394      }
    +395    } else {
    +396      // need to recurse
    +397      String segment = remaining.substring(0, remaining.indexOf('.'));
    +398      var messageField = descriptor.findFieldByName(segment);
    +399      if (messageField != null && messageField.getType() == FieldDescriptor.Type.MESSAGE) {
    +400        // found the next tier
    +401        var subType = messageField.getMessageType();
    +402        String newRemainder = remaining.substring(remaining.indexOf('.') + 1);
    +403        return resolveArbitraryField(
    +404          original,
    +405          subType,
    +406          path,
    +407          newRemainder);
    +408      } else if (messageField != null) {
    +409        // it's not a message :(
    +410        throw new IllegalArgumentException(
    +411          String.format(
    +412            "Cannot access sub-field of primitive leaf field, at '%s' on model type '%s'.",
    +413            path,
    +414            original.getName()));
    +415      }
    +416    }
    +417    // property not found
    +418    return Optional.empty();
    +419  }
    +420
    +421  /**
    +422   * Splice an arbitrary field {@code value} at {@code path} into the provided {@code builder}. If an empty value
    +423   * ({@link Optional#empty()}) is provided, clear any existing value residing at {@code path}. In all cases, mutate the
    +424   * existing {@code builder} rather than returning a copy.
    +425   *
    +426   * @param original Top-level builder, which we hand back at the end.
    +427   * @param builder Builder to splice the value into and return.
    +428   * @param path Path at which the target property resides.
    +429   * @param value Value which we should set the target property to, or clear (if passed {@link Optional#empty()}).
    +430   * @param remaining Remaining properties to recurse down to. Internal use only.
    +431   * @param <Builder> Builder type which we are operating on for this splice.
    +432   * @param <Value> Value type which we are operating with for this splice.
    +433   * @return Provided {@code builder} after being mutated with the specified property value.
    +434   */
    +435  @VisibleForTesting
    +436  static <Builder extends Message.Builder, Value> Builder spliceArbitraryField(@Nonnull Message.Builder original,
    +437                                                                               @Nonnull Message.Builder builder,
    +438                                                                               @Nonnull String path,
    +439                                                                               @Nonnull Optional<Value> value,
    +440                                                                               @Nonnull String remaining) {
    +441    Objects.requireNonNull(original, "Cannot splice field into `null` original builder.");
    +442    Objects.requireNonNull(builder, "Cannot splice field into `null` builder.");
    +443    Objects.requireNonNull(path, "Cannot resolve field from `null` path.");
    +444    Objects.requireNonNull(value, "Pass an empty optional, not `null`, for value.");
    +445    Objects.requireNonNull(remaining, "Recursive remaining stack should not be `null`.");
    +446    if (path.startsWith("."))
    +447      throw new IllegalArgumentException(String.format(
    +448        "Cannot splice path that starts with `.` (got: '%s').", path));
    +449    if (remaining.startsWith("."))
    +450      throw new IllegalArgumentException(String.format(
    +451        "Cannot splice path that starts with `.` (got: '%s').", remaining));
    +452
    +453    var descriptor = builder.getDescriptorForType();
    +454    if (!remaining.isEmpty() && !remaining.contains(".")) {
    +455      // thankfully, no need to recurse
    +456      var field = Objects.requireNonNull(
    +457        descriptor.findFieldByName(remaining), String.format("failed to locate field %s", remaining));
    +458      if (value.isPresent()) {
    +459        try {
    +460          builder.setField(field, value.get());
    +461        } catch (IllegalArgumentException iae) {
    +462          throw new ClassCastException(String.format(
    +463            "Failed to set field '%s': value type mismatch.",
    +464            path));
    +465        }
    +466      } else {
    +467        builder.clearField(field);
    +468      }
    +469      //noinspection unchecked
    +470      return (Builder)original;
    +471    } else {
    +472      // we have a sub-message that is initialized, so we need to recurse.
    +473      String segment = remaining.substring(0, remaining.indexOf('.'));
    +474      String newRemainder = remaining.substring(remaining.indexOf('.') + 1);
    +475      return spliceArbitraryField(
    +476        original,
    +477        builder.getFieldBuilder(Objects.requireNonNull(
    +478          descriptor.findFieldByName(segment),
    +479          String.format(
    +480            "Failed to locate sub-builder at path '%s' on model '%s'.",
    +481            segment,
    +482            builder.getDescriptorForType().getFullName()
    +483          )
    +484        )),
    +485        path,
    +486        value,
    +487        newRemainder
    +488      );
    +489    }
    +490  }
    +491
    +492  @VisibleForTesting
    +493  static @Nonnull <V> FieldContainer<V> pluckFieldRecursive(@Nonnull Message original,
    +494                                                            @Nonnull Message instance,
    +495                                                            @Nonnull String path,
    +496                                                            @Nonnull String remaining) {
    +497    Objects.requireNonNull(original, "Cannot resolve field from `null` descriptor.");
    +498    Objects.requireNonNull(instance, "Cannot resolve field from `null` instance.");
    +499    Objects.requireNonNull(path, "Cannot resolve field from `null` path.");
    +500    Objects.requireNonNull(remaining, "Recursive remaining stack should not be `null`.");
    +501
    +502    var descriptor = instance.getDescriptorForType();
    +503    if (remaining.startsWith(".") || remaining.endsWith(".") || remaining.contains(" ")) {
    +504      throw new IllegalArgumentException("Cannot begin or end model property path with `.`");
    +505    } else if (!remaining.isEmpty() && !remaining.contains(".")) {
    +506      // we got lucky, no need to recurse
    +507      var field = descriptor.findFieldByName(remaining);
    +508      if (field != null) {
    +509        if (field.getType() == FieldDescriptor.Type.MESSAGE) {
    +510          Message modelInstance = (Message)instance.getField(field);
    +511          //noinspection unchecked
    +512          return new FieldContainer<>(
    +513            new FieldPointer(descriptor, path, field),
    +514            !modelInstance.getAllFields().isEmpty() ? Optional.of((V)modelInstance) : Optional.empty());
    +515        } else {
    +516          //noinspection unchecked
    +517          return new FieldContainer<>(
    +518            new FieldPointer(descriptor, path, field),
    +519            Optional.of((V) instance.getField(field)));
    +520        }
    +521      }
    +522    } else {
    +523      // find next segment
    +524      String segment = remaining.substring(0, remaining.indexOf('.'));
    +525      var messageField = descriptor.findFieldByName(segment);
    +526      if (messageField != null && messageField.getType() == FieldDescriptor.Type.MESSAGE) {
    +527        if (!instance.hasField(messageField)) {
    +528          // there is a sub-message that is not initialized. so the field is technically empty.
    +529          return new FieldContainer<>(
    +530            new FieldPointer(original.getDescriptorForType(), path, messageField),
    +531            Optional.empty());
    +532        } else {
    +533          // we have a sub-message that is initialized, so we need to recurse.
    +534          String newRemainder = remaining.substring(remaining.indexOf('.') + 1);
    +535          return pluckFieldRecursive(
    +536            original,
    +537            (Message)instance.getField(messageField),
    +538            path,
    +539            newRemainder);
    +540        }
    +541      } else if (messageField != null) {
    +542        // it's not a message :(
    +543        throw new IllegalArgumentException(
    +544          String.format(
    +545            "Cannot access sub-field of primitive leaf field, at '%s' on model type '%s'.",
    +546            path,
    +547            original.getDescriptorForType().getName()));
    +548      }
    +549    }
    +550    throw new IllegalArgumentException(
    +551      String.format("Failed to locate field '%s' on model type '%s'.", path, descriptor.getName()));
    +552  }
    +553
    +554  // -- Metadata: Qualified Names -- //
    +555
    +556  /**
    +557   * Resolve the fully-qualified type path, or name, for the provided datamodel type descriptor. This is essentially
    +558   * syntactic sugar.
    +559   *
    +560   * @param descriptor Model descriptor to resolve a fully-qualified name for.
    +561   * @return Fully-qualified model type name.
    +562   */
    +563  public static @Nonnull String fullyQualifiedName(@Nonnull Descriptor descriptor) {
    +564    return descriptor.getFullName();
    +565  }
    +566
    +567  /**
    +568   * Resolve the fully-qualified type path, or name, for the provided datamodel instance. This method is essentially
    +569   * syntactic sugar for accessing the model instance's descriptor, and then grabbing the fully-qualified name.
    +570   *
    +571   * @param model Model instance to resolve a fully-qualified name for.
    +572   * @return Fully-qualified model type name.
    +573   */
    +574  public static @Nonnull String fullyQualifiedName(@Nonnull Message model) {
    +575    return fullyQualifiedName(model.getDescriptorForType());
    +576  }
    +577
    +578  // -- Metadata: Role Annotations -- //
    +579
    +580  /**
    +581   * Resolve the general type for a given datamodel type descriptor. The type is either set by default, or set by an
    +582   * explicit annotation affixed to the protocol buffer definition that backs the model.
    +583   *
    +584   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +585   * model? A wire model? {@link DatapointType} will tell you.</p>
    +586   *
    +587   * @param descriptor Model descriptor to retrieve a type for.
    +588   * @return Type of the provided datamodel.
    +589   */
    +590  public static @Nonnull DatapointType role(@Nonnull Descriptor descriptor) {
    +591    return modelAnnotation(descriptor, Datamodel.role, false).orElse(DatapointType.OBJECT);
    +592  }
    +593
    +594  /**
    +595   * Resolve the general type for a given datamodel. The type is either set by default, or set by an explicit annotation
    +596   * affixed to the protocol buffer definition that backs the model.
    +597   *
    +598   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +599   * model? A wire model? {@link DatapointType} will tell you.</p>
    +600   *
    +601   * @param model Model to retrieve a type for.
    +602   * @return Type of the provided datamodel.
    +603   */
    +604  public static @Nonnull DatapointType role(@Nonnull Message model) {
    +605    Objects.requireNonNull(model, "Cannot resolve type for `null` model.");
    +606    return role(model.getDescriptorForType());
    +607  }
    +608
    +609  /**
    +610   * Enforce that a particular datamodel type matches <b>any</b> of the provided {@link DatapointType} annotations.
    +611   *
    +612   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +613   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +614   * a set of types for you.</p>
    +615   *
    +616   * @param model Model to validate against the provided set of types.
    +617   * @param type Type to enforce for the provided model.
    +618   * @return Whether the provided model is a <i>member-of</i> (annotated-by) any of the provided {@code types}.
    +619   */
    +620  public static boolean matchRole(@Nonnull Message model, @Nonnull DatapointType type) {
    +621    Objects.requireNonNull(type, "Cannot match `null` model type.");
    +622    return type.equals(role(model));
    +623  }
    +624
    +625  /**
    +626   * Enforce that a particular datamodel schema {@code descriptor} matches <b>any</b> of the provided
    +627   * {@link DatapointType} annotations.
    +628   *
    +629   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +630   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +631   * a set of types for you.</p>
    +632   *
    +633   * @param descriptor Schema descriptor to validate against the provided set of types.
    +634   * @param type Type to enforce for the provided model.
    +635   * @return Whether the provided model is a <i>member-of</i> (annotated-by) any of the provided {@code types}.
    +636   */
    +637  public static boolean matchRole(@Nonnull Descriptor descriptor, @Nonnull DatapointType type) {
    +638    Objects.requireNonNull(type, "Cannot match `null` descriptor type.");
    +639    return type.equals(role(descriptor));
    +640  }
    +641
    +642  /**
    +643   * <b>Check</b> that a particular datamodel type matches <b>any</b> of the provided {@link DatapointType} annotations.
    +644   *
    +645   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +646   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +647   * a set of types for you.</p>
    +648   *
    +649   * @param model Model to validate against the provided set of types.
    +650   * @param types Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +651   * @return Whether the provided model is a <i>member-of</i> (annotated-by) any of the provided {@code types}.
    +652   */
    +653  public static boolean matchAnyRole(@Nonnull Message model, @Nonnull DatapointType ...types) {
    +654    Objects.requireNonNull(types, "Cannot match `null` model types.");
    +655    return EnumSet.copyOf(Arrays.asList(types)).contains(role(model));
    +656  }
    +657
    +658  /**
    +659   * <b>Check</b> that a particular schema {@code descriptor} matches <b>any</b> of the provided {@link DatapointType}
    +660   * annotations.
    +661   *
    +662   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +663   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +664   * a set of types for you.</p>
    +665   *
    +666   * @param descriptor Schema descriptor to validate against the provided set of types.
    +667   * @param types Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +668   * @return Whether the provided model is a <i>member-of</i> (annotated-by) any of the provided {@code types}.
    +669   */
    +670  public static boolean matchAnyRole(@Nonnull Descriptor descriptor, @Nonnull DatapointType ...types) {
    +671    Objects.requireNonNull(types, "Cannot match `null` model types.");
    +672    return EnumSet.copyOf(Arrays.asList(types)).contains(role(descriptor));
    +673  }
    +674
    +675  /**
    +676   * <b>Enforce</b> that a particular {@code model} instance matches the provided {@link DatapointType} annotation.
    +677   *
    +678   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +679   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +680   * a set of types for you.</p>
    +681   *
    +682   * @param model Model to validate against the provided set of types.
    +683   * @param type Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +684   * @throws InvalidModelType If the specified model's type is not included in {@code types}.
    +685   */
    +686  public static void enforceRole(@Nonnull Message model, @Nonnull DatapointType type) throws InvalidModelType {
    +687    if (!matchRole(model, type)) throw InvalidModelType.from(model, EnumSet.of(type));
    +688  }
    +689
    +690  /**
    +691   * <b>Enforce</b> that a particular datamodel schema {@code descriptor} matches the provided {@link DatapointType}
    +692   * annotation.
    +693   *
    +694   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +695   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +696   * a set of types for you.</p>
    +697   *
    +698   * @param descriptor Descriptor to validate against the provided set of types.
    +699   * @param type Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +700   * @throws InvalidModelType If the specified model's type is not included in {@code types}.
    +701   */
    +702  public static void enforceRole(@Nonnull Descriptor descriptor, @Nonnull DatapointType type) throws InvalidModelType {
    +703    if (!matchRole(descriptor, type)) throw InvalidModelType.from(descriptor, EnumSet.of(type));
    +704  }
    +705
    +706  /**
    +707   * <b>Enforce</b> that a particular datamodel type matches <b>any</b> of the provided {@link DatapointType}
    +708   * annotations.
    +709   *
    +710   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +711   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +712   * a set of types for you.</p>
    +713   *
    +714   * @param model Model to validate against the provided set of types.
    +715   * @param types Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +716   * @throws InvalidModelType If the specified model's type is not included in {@code types}.
    +717   */
    +718  public static void enforceAnyRole(@Nonnull Message model, @Nonnull DatapointType ...types) throws InvalidModelType {
    +719    if (!matchAnyRole(model, types)) throw InvalidModelType.from(model, EnumSet.copyOf(Arrays.asList(types)));
    +720  }
    +721
    +722  /**
    +723   * <b>Enforce</b> that a particular schema {@code descriptor} matches <b>any</b> of the provided {@link DatapointType}
    +724   * annotations.
    +725   *
    +726   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +727   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +728   * a set of types for you.</p>
    +729   *
    +730   * @param descriptor Schema descriptor to validate against the provided set of types.
    +731   * @param types Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +732   * @throws InvalidModelType If the specified model's type is not included in {@code types}.
    +733   */
    +734  public static void enforceAnyRole(@Nonnull Descriptor descriptor,
    +735                                    @Nonnull DatapointType ...types) throws InvalidModelType {
    +736    if (!matchAnyRole(descriptor, types)) throw InvalidModelType.from(descriptor, EnumSet.copyOf(Arrays.asList(types)));
    +737  }
    +738
    +739  // -- Metadata: Field Resolution -- //
    +740
    +741  /**
    +742   * Resolve an arbitrary field pointer from the provided model {@code instance}, specified by the given {@code path} to
    +743   * the property. If the property cannot be found, {@link Optional#empty()} is returned.
    +744   *
    +745   * <p>This method is <b>safe</b>, in that, unlike other util methods for model metadata, it will not throw if the
    +746   * provided {@code path} is invalid.</p>
    +747   *
    +748   * @param instance Model instance on which to resolve the specified field.
    +749   * @param path Dotted deep-path to the desired field.
    +750   * @return Resolved field pointer for the requested field, or {@link Optional#empty()}.
    +751   */
    +752  public static @Nonnull Optional<FieldPointer> resolveField(@Nonnull Message instance, @Nonnull String path) {
    +753    return resolveField(instance.getDescriptorForType(), path);
    +754  }
    +755
    +756  /**
    +757   * Resolve an arbitrary field pointer from the provided model type {@code escriptor}, specified by the given
    +758   * {@code path} to the property. If the property cannot be found, {@link Optional#empty()} is returned.
    +759   *
    +760   * <p>This method is <b>safe</b>, in that, unlike other util methods for model metadata, it will not throw if the
    +761   * provided {@code path} is invalid.</p>
    +762   *
    +763   * @param descriptor Model type descriptor on which to resolve the specified field.
    +764   * @param path Dotted deep-path to the desired field.
    +765   * @return Resolved field pointer for the requested field, or {@link Optional#empty()}.
    +766   */
    +767  public static @Nonnull Optional<FieldPointer> resolveField(@Nonnull Descriptor descriptor, @Nonnull String path) {
    +768    return resolveArbitraryField(descriptor, descriptor, path, path);
    +769  }
    +770
    +771  // -- Metadata: Model Annotations -- //
    +772
    +773  /**
    +774   * Retrieve a model-level annotation, from {@code instance}, structured by {@code ext}. If no instance of the
    +775   * requested model annotation can be found, {@link Optional#empty()} is returned. Search recursively is supported as
    +776   * well, which descends the search to sub-messages to search for the desired annotation.
    +777   *
    +778   * @param instance Message instance to scan for the specified annotation.
    +779   * @param ext Extension to fetch from the subject model, or any sub-model (if {@code recursive} is {@code true}).
    +780   * @param recursive Whether to search recursively for the desired extension.
    +781   * @param <E> Generic type of extension we are looking for.
    +782   * @return Optional, either {@link Optional#empty()}, or wrapping the found extension data instance.
    +783   */
    +784  public static @Nonnull <E> Optional<E> modelAnnotation(@Nonnull Message instance,
    +785                                                         @Nonnull GeneratedExtension<MessageOptions, E> ext,
    +786                                                         @Nonnull Boolean recursive) {
    +787    return modelAnnotation(instance.getDescriptorForType(), ext, recursive);
    +788  }
    +789
    +790  /**
    +791   * Retrieve a model-level annotation, from the provided model schema {@code descriptor}, structured by {@code ext}. If
    +792   * no instance of the requested model annotation can be found, {@link Optional#empty()} is returned. Search
    +793   * recursively is supported as well, which descends the search to sub-messages to search for the desired annotation.
    +794   *
    +795   * @param descriptor Schema descriptor for a model type.
    +796   * @param ext Extension to fetch from the subject model, or any sub-model (if {@code recursive} is {@code true}).
    +797   * @param recursive Whether to search recursively for the desired extension.
    +798   * @param <E> Generic type of extension we are looking for.
    +799   * @return Optional, either {@link Optional#empty()}, or wrapping the found extension data instance.
    +800   */
    +801  public static @Nonnull <E> Optional<E> modelAnnotation(@Nonnull Descriptor descriptor,
    +802                                                         @Nonnull GeneratedExtension<MessageOptions, E> ext,
    +803                                                         @Nonnull Boolean recursive) {
    +804    Objects.requireNonNull(descriptor, "Cannot resolve type for `null` descriptor.");
    +805    if (descriptor.getOptions().hasExtension(ext))
    +806      return Optional.of(descriptor.getOptions().getExtension(ext));
    +807    if (recursive) {
    +808      // loop through fields. gather any sub-messages, and check procedurally if any of them match. if we find one that
    +809      // does, we return immediately.
    +810      for (FieldDescriptor field : descriptor.getFields()) {
    +811        if (field.getType() == FieldDescriptor.Type.MESSAGE) {
    +812          //noinspection ConstantConditions
    +813          var subresult = modelAnnotation(field.getMessageType(), ext, recursive);
    +814          if (subresult.isPresent())
    +815            return subresult;
    +816        }
    +817      }
    +818    }
    +819    return Optional.empty();
    +820  }
    +821
    +822  // -- Metadata: Field Annotations -- //
    +823
    +824  /**
    +825   * Resolve a {@link FieldPointer} within the scope of {@code instance}, that holds values for the specified metadata
    +826   * annotation {@code ext}. By default, this method searches recursively.
    +827   *
    +828   * @see #annotatedField(Descriptor, GeneratedExtension) variant if a descriptor is on-hand
    +829   * @see #annotatedField(Descriptor, GeneratedExtension, Boolean, Optional) full-spec variant.
    +830   * @param instance Model instance to search for the specified annotated field on.
    +831   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +832   * @param <E> Extension generic type.
    +833   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +834   */
    +835  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Message instance,
    +836                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext) {
    +837    return annotatedField(instance, ext, true);
    +838  }
    +839
    +840  /**
    +841   * Resolve a {@link FieldPointer} within the scope of {@code instance}, that holds values for the specified metadata
    +842   * annotation {@code ext}.
    +843   *
    +844   * <p>This method variant also allows specifying a <b>recursive</b> flag, which, if specified, causes the search to
    +845   * proceed to sub-models (recursively) until a matching field is found. If <b>recursive</b> is passed as {@code false}
    +846   * then the search will only occur at the top-level of {@code instance}.</p>
    +847   *
    +848   * @see #annotatedField(Message, GeneratedExtension, Boolean, Optional) Variant that supports a filter
    +849   * @see #annotatedField(Descriptor, GeneratedExtension, Boolean, Optional) full-spec variant.
    +850   * @param instance Model instance to search for the specified annotated field on.
    +851   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +852   * @param recursive Whether to conduct this search recursively, or just at the top-level.
    +853   * @param <E> Extension generic type.
    +854   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +855   */
    +856  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Message instance,
    +857                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext,
    +858                                                                   @Nonnull Boolean recursive) {
    +859    return annotatedField(instance, ext, recursive, Optional.empty());
    +860  }
    +861
    +862  /**
    +863   * Resolve a {@link FieldPointer} within the scope of {@code instance}, that holds values for the specified metadata
    +864   * annotation {@code ext}.
    +865   *
    +866   * <p>This method variant also allows specifying a <b>filter</b>, which will be run for each property encountered with
    +867   * the annotation present. If the filter returns {@code true}, the field will be selected, otherwise, the search
    +868   * continues until all properties are exhausted (depending on {@code recursive}).</p>
    +869   *
    +870   * @param instance Model instance to search for the specified annotated field on.
    +871   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +872   * @param recursive Whether to conduct this search recursively, or just at the top-level.
    +873   * @param <E> Extension generic type.
    +874   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +875   */
    +876  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Message instance,
    +877                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext,
    +878                                                                   @Nonnull Boolean recursive,
    +879                                                                   @Nonnull Optional<Function<E, Boolean>> filter) {
    +880    return annotatedField(instance.getDescriptorForType(), ext, recursive, filter);
    +881  }
    +882
    +883  /**
    +884   * Resolve a {@link FieldPointer} within the scope of the provided model {@code descriptor}, that holds values for the
    +885   * specified metadata annotation {@code ext}. By default, this search occurs recursively, examining all nested sub-
    +886   * models on the provided instance.
    +887   *
    +888   * @param descriptor Model object descriptor to search for the specified annotated field on.
    +889   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +890   * @param <E> Extension generic type.
    +891   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +892   */
    +893  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Descriptor descriptor,
    +894                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext) {
    +895    return annotatedField(descriptor, ext, Optional.empty());
    +896  }
    +897
    +898  /**
    +899   * Resolve a {@link FieldPointer} within the scope of the provided model {@code descriptor}, that holds values for the
    +900   * specified metadata annotation {@code ext}. By default, this search occurs recursively, examining all nested sub-
    +901   * models on the provided instance.
    +902   *
    +903   * <p>This method variant also allows specifying a <b>filter</b>, which will be run for each property encountered with
    +904   * the annotation present. If the filter returns {@code true}, the field will be selected, otherwise, the search
    +905   * continues until all properties are exhausted (depending on {@code recursive}).</p>
    +906   *
    +907   * @param descriptor Model object descriptor to search for the specified annotated field on.
    +908   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +909   * @param <E> Extension generic type.
    +910   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +911   */
    +912  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Descriptor descriptor,
    +913                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext,
    +914                                                                   @Nonnull Optional<Function<E, Boolean>> filter) {
    +915    return annotatedField(descriptor, ext, true, filter);
    +916  }
    +917
    +918  /**
    +919   * Resolve a {@link FieldPointer} within the scope of the provided model {@code descriptor}, that holds values for the
    +920   * specified metadata annotation {@code ext}. Using the {@code recursive} parameter, the invoking developer may opt to
    +921   * search for the annotated field recursively.
    +922   *
    +923   * <p>This method variant also allows specifying a <b>filter</b>, which will be run for each property encountered with
    +924   * the annotation present. If the filter returns {@code true}, the field will be selected, otherwise, the search
    +925   * continues until all properties are exhausted (depending on {@code recursive}).</p>
    +926   *
    +927   * @param descriptor Model object descriptor to search for the specified annotated field on.
    +928   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +929   * @param <E> Extension generic type.
    +930   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +931   */
    +932  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Descriptor descriptor,
    +933                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext,
    +934                                                                   @Nonnull Boolean recursive,
    +935                                                                   @Nonnull Optional<Function<E, Boolean>> filter) {
    +936    return resolveAnnotatedField(descriptor, ext, recursive, filter, "");
    +937  }
    +938
    +939  /**
    +940   * Retrieve a field-level annotation, from the provided field schema {@code descriptor}, structured by {@code ext}. If
    +941   * no instance of the requested field annotation can be found, {@link Optional#empty()} is returned.
    +942   *
    +943   * @param descriptor Schema descriptor for a field on a model type.
    +944   * @param ext Extension to fetch from the subject field.
    +945   * @param <E> Generic type of extension we are looking for.
    +946   * @return Optional, either {@link Optional#empty()}, or wrapping the found extension data instance.
    +947   */
    +948  public static @Nonnull <E> Optional<E> fieldAnnotation(@Nonnull FieldDescriptor descriptor,
    +949                                                         @Nonnull GeneratedExtension<FieldOptions, E> ext) {
    +950    Objects.requireNonNull(descriptor, "Cannot resolve type for `null` field descriptor.");
    +951    if (descriptor.getOptions().hasExtension(ext))
    +952      return Optional.of(descriptor.getOptions().getExtension(ext));
    +953    return Optional.empty();
    +954  }
    +955
    +956  // -- Metadata: ID Fields -- //
    +957
    +958  /**
    +959   * Resolve a pointer to the provided model {@code instance}'s ID field, whether or not it has a value. If there is no
    +960   * ID-annotated field at all, {@link Optional#empty()} is returned. Alternatively, if the model is not compatible with
    +961   * ID fields, an exception is raised (see below).
    +962   *
    +963   * @param instance Model instance for which an ID field is being resolved.
    +964   * @return Optional, either {@link Optional#empty()} or containing a {@link FieldPointer} to the resolved ID field.
    +965   * @throws InvalidModelType If the specified model does not support IDs. Only objects of type {@code OBJECT} can be
    +966   *         used with this interface.
    +967   */
    +968  public static @Nonnull Optional<FieldPointer> idField(@Nonnull Message instance) throws InvalidModelType {
    +969    return idField(instance.getDescriptorForType());
    +970  }
    +971
    +972  /**
    +973   * Resolve a pointer to the provided schema type {@code descriptor}'s ID field, whether or not it has a value. If
    +974   * there is no ID-annotated field at all, {@link Optional#empty()} is returned. Alternatively, if the model is not
    +975   * compatible with ID fields, an exception is raised (see below).
    +976   *
    +977   * @param descriptor Model instance for which an ID field is being resolved.
    +978   * @return Optional, either {@link Optional#empty()} or containing a {@link FieldPointer} to the resolved ID field.
    +979   * @throws InvalidModelType If the specified model does not support IDs. Only objects of type {@code OBJECT} can be
    +980   *         used with this interface.
    +981   */
    +982  public static @Nonnull Optional<FieldPointer> idField(@Nonnull Descriptor descriptor) throws InvalidModelType {
    +983    enforceAnyRole(Objects.requireNonNull(descriptor), DatapointType.OBJECT, DatapointType.OBJECT_KEY);
    +984    var topLevelId = Objects.requireNonNull(annotatedField(
    +985      descriptor,
    +986      Datamodel.field,
    +987      false,
    +988      Optional.of((field) -> field.getType() == FieldType.ID)));
    +989
    +990    if (topLevelId.isPresent()) {
    +991      return topLevelId;
    +992    } else {
    +993      // okay. no top level ID. what about keys, which must be top-level?
    +994      var keyBase = keyField(descriptor);
    +995      if (keyBase.isPresent()) {
    +996        // we found a key, so scan the key for an ID, which is required on keys.
    +997        return Objects.requireNonNull(resolveAnnotatedField(
    +998          keyBase.get().field.getMessageType(),
    +999          Datamodel.field,
    +1000          false,
    +1001          Optional.of((field) -> field.getType() == FieldType.ID),
    +1002          keyBase.get().getField().getName()));
    +1003      }
    +1004    }
    +1005    // there's no top-level ID, and no top-level key, or the key had no ID. we're done here.
    +1006    return Optional.empty();
    +1007  }
    +1008
    +1009  // -- Metadata: Key Fields -- //
    +1010
    +1011  /**
    +1012   * Resolve a pointer to the provided schema type {@code descriptor}'s {@code KEY} field, whether or not it has a value
    +1013   * assigned. If there is no key-annotated field at all, {@link Optional#empty()} is returned. Alternatively, if the
    +1014   * model is not compatible with key fields, an exception is raised (see below).
    +1015   *
    +1016   * @param instance Model instance for which a key field is being resolved.
    +1017   * @return Optional, either {@link Optional#empty()} or containing a {@link FieldPointer} to the resolved key field.
    +1018   * @throws InvalidModelType If the specified model does not support keys. Only objects of type {@code OBJECT} can be
    +1019   *         used with this interface.
    +1020   */
    +1021  public static @Nonnull Optional<FieldPointer> keyField(@Nonnull Message instance) throws InvalidModelType {
    +1022    return keyField(instance.getDescriptorForType());
    +1023  }
    +1024
    +1025  /**
    +1026   * Resolve a pointer to the provided schema type {@code descriptor}'s {@code KEY} field, whether or not it has a value
    +1027   * assigned. If there is no key-annotated field at all, {@link Optional#empty()} is returned. Alternatively, if the
    +1028   * model is not compatible with key fields, an exception is raised (see below).
    +1029   *
    +1030   * @param descriptor Model type descriptor for which a key field is being resolved.
    +1031   * @return Optional, either {@link Optional#empty()} or containing a {@link FieldPointer} to the resolved key field.
    +1032   * @throws InvalidModelType If the specified model does not support keys. Only objects of type {@code OBJECT} can be
    +1033   *         used with this interface.
    +1034   */
    +1035  public static @Nonnull Optional<FieldPointer> keyField(@Nonnull Descriptor descriptor) throws InvalidModelType {
    +1036    enforceAnyRole(Objects.requireNonNull(descriptor), DatapointType.OBJECT);
    +1037    return Objects.requireNonNull(annotatedField(
    +1038      Objects.requireNonNull(descriptor),
    +1039      Datamodel.field,
    +1040      false,
    +1041      Optional.of((field) -> field.getType() == FieldType.KEY)));
    +1042  }
    +1043
    +1044  // -- Metadata: Value Pluck -- //
    +1045
    +1046  /**
    +1047   * Pluck a field value, addressed by a {@link FieldPointer}, from the provided {@code instance}. If the referenced
    +1048   * field is a message, a message instance will be handed back only if there is an initialized value. Leaf fields
    +1049   * return their raw value, if set. In all cases, if there is no initialized value, {@link Optional#empty()} is
    +1050   * returned.
    +1051   *
    +1052   * @param instance Model instance from which to pluck the property.
    +1053   * @param fieldPointer Pointer to the field we wish to fetch.
    +1054   * @param <V> Generic type of data returned by this operation.
    +1055   * @return Optional wrapping the resolved value, or {@link Optional#empty()} if no value could be resolved.
    +1056   * @throws IllegalStateException If the referenced property is not found, despite witnessing matching types.
    +1057   * @throws IllegalArgumentException If the specified field does not have a matching base type with {@code instance}.
    +1058   */
    +1059  public static @Nonnull <V> FieldContainer<V> pluck(@Nonnull Message instance, @Nonnull FieldPointer fieldPointer) {
    +1060    return pluck(instance, fieldPointer.path);
    +1061  }
    +1062
    +1063  /**
    +1064   * Return a single field value container, plucked from the specified deep {@code path}, in dot form, using the regular
    +1065   * protobuf-definition names for each field. If a referenced field is a message, a message instance will be returned
    +1066   * only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no
    +1067   * initialized value, {@link Optional#empty()} is supplied in place.
    +1068   *
    +1069   * @param instance Model instance to pluck the specified property from.
    +1070   * @param path Deep path for the property value we wish to pluck.
    +1071   * @param <V> Expected type for the property. If types do not match, a {@link ClassCastException} will be raised.
    +1072   * @return Field container, either empty, or containing the plucked value.
    +1073   * @throws IllegalArgumentException If the provided path is syntactically invalid, or the field does not exist.
    +1074   */
    +1075  public static @Nonnull <V> FieldContainer<V> pluck(@Nonnull Message instance, @Nonnull String path) {
    +1076    return pluckFieldRecursive(instance, instance, path, path);
    +1077  }
    +1078
    +1079  /**
    +1080   * Return an iterable containing plucked value containers for each field mentioned in {@code mask}, that is present on
    +1081   * {@code instance} with an initialized value. If a referenced field is a message, a message instance will be included
    +1082   * only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no
    +1083   * initialized value, {@link Optional#empty()} is supplied in place.
    +1084   *
    +1085   * <p>If a field cannot be found, {@link Optional#empty()} is supplied in its place, so that the output order matches
    +1086   * path iteration order on the supplied {@code mask}. This method is therefore safe with regard to path access.</p>
    +1087   *
    +1088   * @param instance Model instance to pluck the specified properties from.
    +1089   * @param mask Mask of properties to pluck from the model instance.
    +1090   * @return Stream which emits each field container, with a generic {@code Object} for each value.
    +1091   */
    +1092  public static @Nonnull SortedSet<FieldContainer<Object>> pluckAll(@Nonnull Message instance, @Nonnull FieldMask mask) {
    +1093    return pluckAll(instance, mask, true);
    +1094  }
    +1095
    +1096  /**
    +1097   * Return an iterable containing plucked value containers for each field mentioned in {@code mask}, that is present on
    +1098   * {@code instance} with an initialized value. If a referenced field is a message, a message instance will be included
    +1099   * only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no
    +1100   * initialized value, {@link Optional#empty()} is supplied in place.
    +1101   *
    +1102   * <p>If a field cannot be found, {@link Optional#empty()} is supplied in its place, so that the output order matches
    +1103   * path iteration order on the supplied {@code mask}. This method is therefore safe with regard to path access. If
    +1104   * {@code normalize} is activated (the default for {@link #pluckAll(Message, FieldMask)}), the field mask will be
    +1105   * sorted and de-duplicated before processing.</p>
    +1106   *
    +1107   * <p>Sort order of the return value is based on the full path of properties selected - i.e. field containers are
    +1108   * returned in lexicographic sort order matching their underlying property paths.</p>
    +1109   *
    +1110   * @param instance Model instance to pluck the specified properties from.
    +1111   * @param mask Mask of properties to pluck from the model instance.
    +1112   * @param normalize Whether to normalize the field mask before plucking fields.
    +1113   * @return Stream which emits each field container, with a generic {@code Object} for each value.
    +1114   */
    +1115  public static @Nonnull SortedSet<FieldContainer<Object>> pluckAll(@Nonnull Message instance,
    +1116                                                                    @Nonnull FieldMask mask,
    +1117                                                                    @Nonnull Boolean normalize) {
    +1118    return ImmutableSortedSet.copyOfSorted(pluckStream(instance, mask, normalize)
    +1119      .collect(Collectors.toCollection(ConcurrentSkipListSet::new)));
    +1120  }
    +1121
    +1122  /**
    +1123   * Return a stream which emits plucked value containers for each field mentioned in {@code mask}, that is present on
    +1124   * {@code instance} with an initialized value. If a referenced field is a message, a message instance will be emitted
    +1125   * only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no
    +1126   * initialized value, {@link Optional#empty()} is supplied in place.
    +1127   *
    +1128   * <p>If a field cannot be found, {@link Optional#empty()} is supplied in its place, so that the output order matches
    +1129   * path iteration order on the supplied {@code mask}. This method is therefore safe with regard to path access.</p>
    +1130   *
    +1131   * <p><b>Performance note:</b> the {@link Stream} returned by this method is explicitly parallel-capable, because
    +1132   * reading descriptor schema is safely concurrent.</p>
    +1133   *
    +1134   * @param instance Model instance to pluck the specified properties from.
    +1135   * @param mask Mask of properties to pluck from the model instance.
    +1136   * @return Stream which emits each field container, with a generic {@code Object} for each value.
    +1137   */
    +1138  public static @Nonnull Stream<FieldContainer<Object>> pluckStream(@Nonnull Message instance,
    +1139                                                                    @Nonnull FieldMask mask) {
    +1140    return pluckStream(instance, mask, true);
    +1141  }
    +1142
    +1143  /**
    +1144   * Return a stream which emits plucked value containers for each field mentioned in {@code mask}, that is present on
    +1145   * {@code instance} with an initialized value. If a referenced field is a message, a message instance will be emitted
    +1146   * only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no
    +1147   * initialized value, {@link Optional#empty()} is supplied in place.
    +1148   *
    +1149   * <p>If a field cannot be found, {@link Optional#empty()} is supplied in its place, so that the output order matches
    +1150   * path iteration order on the supplied {@code mask}. This method is therefore safe with regard to path access. If
    +1151   * {@code normalize} is activated (the default for {@link #pluckStream(Message, FieldMask)}), the field mask will be
    +1152   * sorted and de-duplicated before processing.</p>
    +1153   *
    +1154   * <p><b>Performance note:</b> the {@link Stream} returned by this method is explicitly parallel-capable, because
    +1155   * reading descriptor schema is safely concurrent.</p>
    +1156   *
    +1157   * @param instance Model instance to pluck the specified properties from.
    +1158   * @param mask Mask of properties to pluck from the model instance.
    +1159   * @param normalize Whether to normalize the field mask before plucking fields.
    +1160   * @return Stream which emits each field container, with a generic {@code Object} for each value.
    +1161   */
    +1162  public static @Nonnull Stream<FieldContainer<Object>> pluckStream(@Nonnull Message instance,
    +1163                                                                    @Nonnull FieldMask mask,
    +1164                                                                    @Nonnull Boolean normalize) {
    +1165    return (new TreeSet<>((normalize ? FieldMaskUtil.normalize(mask) : mask).getPathsList()))
    +1166      .parallelStream()
    +1167      .map((fieldPath) -> pluck(instance, fieldPath));
    +1168  }
    +1169
    +1170  // -- Metadata: ID/Key Value Pluck -- //
    +1171
    +1172  /**
    +1173   * Resolve the provided model instance's assigned ID, by walking the property structure for the entity, and returning
    +1174   * either the first {@code id}-annotated field's value at the top-level, or the first {@code id}-annotated field value
    +1175   * on the first {@code key}-annotated message field at the top level of the provided message.
    +1176   *
    +1177   * <p>If no ID field <i>value</i> can be resolved, {@link Optional#empty()} is returned. On the other hand, if the
    +1178   * model is not a business object or does not have an ID annotation at all, an exception is raised (see below).</p>
    +1179   *
    +1180   * @param <ID> Type for the ID value we are resolving.
    +1181   * @param instance Model instance for which an ID value is desired.
    +1182   * @return Optional wrapping the value of the model instance's ID, or an empty optional if no value could be resolved.
    +1183   * @throws InvalidModelType If the supplied model is not a business object and/or does not have an ID field at all.
    +1184   */
    +1185  public static @Nonnull <ID> Optional<ID> id(@Nonnull Message instance) {
    +1186    var descriptor = instance.getDescriptorForType();
    +1187    enforceAnyRole(descriptor, DatapointType.OBJECT, DatapointType.OBJECT_KEY);
    +1188    Optional<FieldPointer> idField = idField(descriptor);
    +1189    if (idField.isEmpty())
    +1190      throw new MissingAnnotatedField(descriptor, FieldType.ID);
    +1191    return ModelMetadata.<ID>pluck(instance, idField.get()).getValue();
    +1192  }
    +1193
    +1194  /**
    +1195   * Resolve the provided model instance's assigned {@code KEY} instance, by walking the property structure for the
    +1196   * entity, and returning the first {@code key}-annotated field's value at the top-level of the provided message.
    +1197   *
    +1198   * <p>If no key field <i>value</i> can be resolved, {@link Optional#empty()} is returned. On the other hand, if the
    +1199   * model is not a business object or does not support key annotations at all, an exception is raised (see below).</p>
    +1200   *
    +1201   * @param <Key> Type for the key we are resolving.
    +1202   * @param instance Model instance for which an key value is desired.
    +1203   * @return Optional wrapping the value of the model instance's key, or an empty optional if none could be resolved.
    +1204   * @throws InvalidModelType If the supplied model is not a business object and/or does not have an key field at all.
    +1205   */
    +1206  public static @Nonnull <Key> Optional<Key> key(@Nonnull Message instance) {
    +1207    Descriptor descriptor = instance.getDescriptorForType();
    +1208    enforceRole(descriptor, DatapointType.OBJECT);
    +1209    Optional<FieldPointer> keyField = annotatedField(
    +1210      descriptor,
    +1211      Datamodel.field,
    +1212      false,
    +1213      Optional.of((field) -> field.getType() == FieldType.KEY));
    +1214
    +1215    if (keyField.isEmpty())
    +1216      throw new MissingAnnotatedField(descriptor, FieldType.KEY);
    +1217    //noinspection unchecked
    +1218    return (Optional<Key>)pluck(instance, keyField.get()).getValue();
    +1219  }
    +1220
    +1221  // -- Metadata: Value Splice -- //
    +1222
    +1223  /**
    +1224   * Splice the provided optional value (or clear any existing value) at the field {@code path} in the provided model
    +1225   * {@code instance}. Return a re-built message after the splice.
    +1226   *
    +1227   * <p>If {@link Optional#empty()} is passed for the {@code value} to set, any existing value placed in that field
    +1228   * will be cleared. This method works identically for primitive leaf fields and message fields.</p>
    +1229   *
    +1230   * @param instance Model instance to splice the value into.
    +1231   * @param path Deep path at which to splice the value.
    +1232   * @param val Value to splice into the model, or {@link Optional#empty()} to clear any existing value.
    +1233   * @param <Model> Model type which we are working with for this splice operation.
    +1234   * @param <Value> Value type which we are splicing in, if applicable.
    +1235   * @return Re-built model, after the splice operation.
    +1236   */
    +1237  public static @Nonnull <Model extends Message, Value> Model splice(@Nonnull Message instance,
    +1238                                                                     @Nonnull String path,
    +1239                                                                     @Nonnull Optional<Value> val) {
    +1240
    +1241    return splice(
    +1242      instance,
    +1243      resolveField(instance, path)
    +1244        .orElseThrow(() -> new IllegalArgumentException(String.format(
    +1245          "Failed to resolve path '%s' on model instance of type '%s' for value splice.",
    +1246          path,
    +1247          instance.getDescriptorForType().getName()))),
    +1248      val);
    +1249  }
    +1250
    +1251  /**
    +1252   * Splice the provided optional value (or clear any existing value) at the specified {@code field} pointer, in the
    +1253   * provided model {@code instance}. Return a re-built message after the splice.
    +1254   *
    +1255   * <p>If {@link Optional#empty()} is passed for the {@code value} to set, any existing value placed in that field
    +1256   * will be cleared. This method works identically for primitive leaf fields and message fields.</p>
    +1257   *
    +1258   * @param instance Model instance to splice the value into.
    +1259   * @param field Resolved and validated field pointer for the field to splice.
    +1260   * @param val Value to splice into the model, or {@link Optional#empty()} to clear any existing value.
    +1261   * @param <Model> Model type which we are working with for this splice operation.
    +1262   * @param <Value> Value type which we are splicing in, if applicable.
    +1263   * @return Re-built model, after the splice operation.
    +1264   */
    +1265  public static @Nonnull <Model extends Message, Value> Model splice(@Nonnull Message instance,
    +1266                                                                     @Nonnull FieldPointer field,
    +1267                                                                     @Nonnull Optional<Value> val) {
    +1268    //noinspection unchecked
    +1269    return (Model)spliceBuilder(instance.toBuilder(), field, val).build();
    +1270  }
    +1271
    +1272  /**
    +1273   * Splice the provided optional value (or clear any existing value) at the specified {@code field} pointer, in the
    +1274   * provided model {@code instance}. Return the provided builder after the splice operation. The return value may be
    +1275   * ignored if the developer so wishes (the provided {@code builder} is mutated in place).
    +1276   *
    +1277   * <p>If {@link Optional#empty()} is passed for the {@code value} to set, any existing value placed in that field
    +1278   * will be cleared. This method works identically for primitive leaf fields and message fields.</p>
    +1279   *
    +1280   * @param builder Model builder to splice the value into.
    +1281   * @param field Resolved and validated field pointer for the field to splice.
    +1282   * @param val Value to splice into the model, or {@link Optional#empty()} to clear any existing value.
    +1283   * @param <Builder> Model builder type which we are working with for this splice operation.
    +1284   * @param <Value> Value type which we are splicing in, if applicable.
    +1285   * @return Model {@code builder}, after the splice operation.
    +1286   */
    +1287  @CanIgnoreReturnValue
    +1288  public static @Nonnull <Builder extends Message.Builder, Value> Builder spliceBuilder(
    +1289    @Nonnull Message.Builder builder,
    +1290    @Nonnull FieldPointer field,
    +1291    @Nonnull Optional<Value> val) {
    +1292    var noPrefixPath = field.path.startsWith(".") ? field.path.substring(1) : field.path;
    +1293    return spliceArbitraryField(
    +1294      builder,
    +1295      builder,
    +1296      noPrefixPath,
    +1297      val,
    +1298      noPrefixPath
    +1299    );
    +1300  }
    +1301
    +1302  // -- Metadata: ID/Key Splice -- //
    +1303
    +1304  /**
    +1305   * Splice the provided value at {@code val}, into the ID field value for {@code instance}. If an ID-annotated property
    +1306   * cannot be located, or the model is not of a suitable/type role for use with IDs, an exception is raised (see below
    +1307   * for more info).
    +1308   *
    +1309   * <p>If an existing value exists for the model's ID, <b>it will be replaced</b>. In most object-based storage engines
    +1310   * this will end up copying the object, rather than mutating an ID. Be careful of this behavior. Passing
    +1311   * {@link Optional#empty()} will clear any existing ID on the model.</p>
    +1312   *
    +1313   * @param instance Model instance to splice the value into. Because models are immutable, this involves converting the
    +1314   *                 model to a builder, splicing in the value, and then re-building the model. As such, the model
    +1315   *                 returned will be a <i>different object instance</i>, but will otherwise be untouched.
    +1316   * @param val Value we should splice-into the ID field for the record. It is expected that the generic type of this
    +1317   *            value will line up with the ID field type, otherwise a {@link ClassCastException} will be thrown.
    +1318   * @param <Model> Type of model we are splicing an ID value into.
    +1319   * @param <Value> Type of ID value we are splicing into the model.
    +1320   * @return Model instance, rebuilt, after splicing in the provided value, at the model's ID-annotated field.
    +1321   * @throws InvalidModelType If the specified model is not suitable for use with IDs at all.
    +1322   * @throws ClassCastException If the {@code Value} generic type does not match the ID field primitive type.
    +1323   * @throws MissingAnnotatedField If the provided {@code instance} is not of the correct type, or has no ID field.
    +1324   */
    +1325  public static @Nonnull <Model extends Message, Value> Model spliceId(@Nonnull Message instance,
    +1326                                                                       @Nonnull Optional<Value> val) {
    +1327    //noinspection unchecked
    +1328    return (Model)spliceIdBuilder(instance.toBuilder(), val).build();
    +1329  }
    +1330
    +1331  /**
    +1332   * Splice the provided value at {@code val}, into the ID field value for the provided model {@code builder}. If an ID-
    +1333   * annotated property cannot be located, or the model is not of a suitable/type role for use with IDs, an exception is
    +1334   * raised (see below for more info).
    +1335   *
    +1336   * <p>If an existing value exists for the model's ID, <b>it will be replaced</b>. In most object-based storage engines
    +1337   * this will end up copying the object, rather than mutating an ID. Be careful of this behavior. Passing
    +1338   * {@link Optional#empty()} will clear any existing ID on the model.</p>
    +1339   *
    +1340   * @param builder Model instance builder to splice the value into. The builder provided is <i>mutated in place</i>, so
    +1341   *                it will be an identical object instance to the one provided, but with the ID property filled in.
    +1342   * @param val Value we should splice-into the ID field for the record. It is expected that the generic type of this
    +1343   *            value will line up with the ID field type, otherwise a {@link ClassCastException} will be thrown.
    +1344   * @param <Builder> Type of model builder we are splicing an ID value into.
    +1345   * @param <Value> Type of ID value we are splicing into the model.
    +1346   * @return Model builder, after splicing in the provided value, at the model's ID-annotated field.
    +1347   * @throws InvalidModelType If the specified model is not suitable for use with IDs at all.
    +1348   * @throws ClassCastException If the {@code Value} generic type does not match the ID field primitive type.
    +1349   * @throws MissingAnnotatedField If the provided {@code builder} is not of the correct type, or has no ID field.
    +1350   */
    +1351  public static @Nonnull <Builder extends Message.Builder, Value> Builder spliceIdBuilder(
    +1352    @Nonnull Message.Builder builder,
    +1353    @Nonnull Optional<Value> val) {
    +1354    // resolve descriptor and field
    +1355    if (val.isPresent() && val.get() instanceof Message)
    +1356      throw new IllegalArgumentException("Cannot set messages as ID values.");
    +1357    var descriptor = builder.getDescriptorForType();
    +1358    enforceAnyRole(descriptor, DatapointType.OBJECT, DatapointType.OBJECT_KEY);
    +1359    var fieldPath = idField(descriptor)
    +1360      .orElseThrow(() -> new MissingAnnotatedField(descriptor, FieldType.ID))
    +1361      .getPath();
    +1362
    +1363    return spliceArbitraryField(
    +1364      builder,
    +1365      builder,
    +1366      fieldPath,
    +1367      val,
    +1368      fieldPath);
    +1369  }
    +1370
    +1371  /**
    +1372   * Splice the provided value at {@code val}, into the key message value for {@code instance}. If a key-annotated
    +1373   * property cannot be located, or the model is not of a suitable/type role for use with keys, an exception is raised
    +1374   * (see below for more info).
    +1375   *
    +1376   * <p>If an existing value is set for the model's key, <b>it will be replaced</b>. In most object-based storage
    +1377   * engines this will end up copying the object, rather than mutating a key. Keys are usually immutable for this
    +1378   * reason, so use this method with care. Passing {@link Optional#empty()} will clear any existing key message
    +1379   * currently affixed to the model {@code instance}.</p>
    +1380   *
    +1381   * @param instance Model instance to splice the value into. Because models are immutable, this involves converting the
    +1382   *                 model to a builder, splicing in the value, and then re-building the model. As such, the model
    +1383   *                 returned will be a <i>different object instance</i>, but will otherwise be untouched.
    +1384   * @param val Value we should splice-into the ID field for the record. It is expected that the generic type of this
    +1385   *            value will line up with the ID field type, otherwise a {@link ClassCastException} will be thrown.
    +1386   * @param <Model> Type of model we are splicing an ID value into.
    +1387   * @param <Key> Type of key message we are splicing into the model.
    +1388   * @return Model instance, rebuilt, after splicing in the provided value, at the model's ID-annotated field.
    +1389   * @throws InvalidModelType If the specified model is not suitable for use with IDs at all.
    +1390   * @throws ClassCastException If the {@code Value} generic type does not match the ID field primitive type.
    +1391   * @throws MissingAnnotatedField If the provided {@code builder} is not of the correct type, or has no ID field.
    +1392   */
    +1393  public static @Nonnull <Model extends Message, Key extends Message> Model spliceKey(@Nonnull Message instance,
    +1394                                                                                      @Nonnull Optional<Key> val) {
    +1395    //noinspection unchecked
    +1396    return (Model)spliceKeyBuilder(instance.toBuilder(), val).build();
    +1397  }
    +1398
    +1399  /**
    +1400   * Splice the provided value at {@code val}, into the key message value for the supplied {@code builder}. If a
    +1401   * key-annotated property cannot be located, or the model is not of a suitable/type role for use with keys, an
    +1402   * exception is raised (see below for more info).
    +1403   *
    +1404   * <p>If an existing value is set for the model's key, <b>it will be replaced</b>. In most object-based storage
    +1405   * engines this will end up copying the object, rather than mutating a key. Keys are usually immutable for this
    +1406   * reason, so use this method with care. Passing {@link Optional#empty()} will clear any existing key message
    +1407   * currently affixed to the model {@code instance}.</p>
    +1408   *
    +1409   * @param builder Model instance builder to splice the value into. The builder provided is <i>mutated in place</i>, so
    +1410   *                it will be an identical object instance to the one provided, but with the key property filled in.
    +1411   * @param val Value we should splice-into the key field for the record. It is expected that the generic type of this
    +1412   *            value will line up with the key message type, otherwise a {@link ClassCastException} will be thrown.
    +1413   * @param <Builder> Type of model builder we are splicing a key value into.
    +1414   * @param <Key> Type of key message we are splicing into the model.
    +1415   * @return Model builder, after splicing in the provided message, at the model's key-annotated field.
    +1416   * @throws InvalidModelType If the specified model is not suitable for use with keys at all.
    +1417   * @throws ClassCastException If the {@code Value} generic type does not match the key field primitive type.
    +1418   * @throws MissingAnnotatedField If the provided {@code builder} is not of the correct type, or has no key field.
    +1419   */
    +1420  public static @Nonnull <Builder extends Message.Builder, Key extends Message> Builder spliceKeyBuilder(
    +1421    @Nonnull Message.Builder builder,
    +1422    @Nonnull Optional<Key> val) {
    +1423    // resolve descriptor and key message field
    +1424    var descriptor = builder.getDescriptorForType();
    +1425    enforceRole(descriptor, DatapointType.OBJECT);
    +1426    var fieldPath = keyField(descriptor)
    +1427      .orElseThrow(() -> new MissingAnnotatedField(descriptor, FieldType.KEY))
    +1428      .getPath();
    +1429
    +1430    return spliceArbitraryField(
    +1431      builder,
    +1432      builder,
    +1433      fieldPath,
    +1434      val,
    +1435      fieldPath);
    +1436  }
    +1437
    +1438  /**
    +1439   * Crawl all fields, recursively, on the descriptor provided. This data may also be accessed via a Java stream via the
    +1440   * method variants listed below. Variants of this method also allow predicate-based filtering or control of recursion.
    +1441   *
    +1442   * @see #allFields(Descriptor, Optional) to provide an optional filtering predicate.
    +1443   * @see #allFields(Descriptor, Optional, Boolean) to provide an optional predicate, and/or control recursion.
    +1444   *
    +1445   * @param descriptor Schema descriptor to crawl model definitions on.
    +1446   * @return Iterable of all fields, recursively, from the descriptor.
    +1447   */
    +1448  public static @Nonnull Iterable<FieldPointer> allFields(@Nonnull Descriptor descriptor) {
    +1449    return allFields(descriptor, Optional.empty(), true);
    +1450  }
    +1451
    +1452  /**
    +1453   * Crawl all fields, recursively, on the descriptor provided. For each field encountered, run `predicate` to determine
    +1454   * whether to include the field, filtering the returned iterable accordingly. This data may also be accessed via a
    +1455   * Java stream via the method variants listed below.
    +1456   *
    +1457   * @see #allFields(Descriptor, Optional, Boolean) to additionally control recursion.
    +1458   *
    +1459   * @param descriptor Schema descriptor to crawl model definitions on.
    +1460   * @param predicate Filter predicate function, if applicable.
    +1461   * @return Iterable of all fields, recursively, from the descriptor, filtered by `predicate`.
    +1462   */
    +1463  public static @Nonnull Iterable<FieldPointer> allFields(@Nonnull Descriptor descriptor,
    +1464                                                          @Nonnull Optional<Predicate<FieldPointer>> predicate) {
    +1465    return allFields(descriptor, predicate, true);
    +1466  }
    +1467
    +1468  /**
    +1469   * Crawl all fields, recursively, on the descriptor provided. For each field encountered, run `predicate` to determine
    +1470   * whether to include the field, filtering the returned iterable accordingly. This data may also be accessed via a
    +1471   * Java stream via the method variants listed below.
    +1472   *
    +1473   * @see #streamFields(Descriptor, Optional, Boolean) to access a stream of fields instead.
    +1474   *
    +1475   * @param descriptor Schema descriptor to crawl model definitions on.
    +1476   * @param predicate Filter predicate function, if applicable.
    +1477   * @return Iterable of all fields, optionally recursively, from the descriptor, filtered by `predicate`.
    +1478   */
    +1479  public static @Nonnull Iterable<FieldPointer> allFields(@Nonnull Descriptor descriptor,
    +1480                                                          @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1481                                                          @Nonnull Boolean recursive) {
    +1482    return streamFields(
    +1483      descriptor,
    +1484      predicate,
    +1485      recursive
    +1486    ).collect(Collectors.toUnmodifiableList());
    +1487  }
    +1488
    +1489  /**
    +1490   * Crawl all fields, recursively, on the descriptor provided. For each field encountered, run `predicate` to determine
    +1491   * whether to include the field, filtering the returned iterable accordingly. This data may also be accessed via a
    +1492   * Java stream via the method variants listed below.
    +1493   *
    +1494   * <p>If a `MESSAGE` field is encountered and the algorithm needs to decide whether to recurse, this variant includes
    +1495   * support for the `decider` function. `decider` is invoked to decide whether to recurse for opportunity to do so.</p>
    +1496   *
    +1497   * @see #streamFields(Descriptor, Optional, Boolean) to access a stream of fields instead.
    +1498   *
    +1499   * @param descriptor Schema descriptor to crawl model definitions on.
    +1500   * @param predicate Filter predicate function, if applicable.
    +1501   * @param decider Function which decides whether to recurse, for each opportunity to do so.
    +1502   * @return Iterable of all fields, optionally recursively, from the descriptor, filtered by `predicate`.
    +1503   */
    +1504  public static @Nonnull Iterable<FieldPointer> allFields(@Nonnull Descriptor descriptor,
    +1505                                                          @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1506                                                          @Nonnull Predicate<FieldPointer> decider) {
    +1507    return streamFields(
    +1508      descriptor,
    +1509      predicate,
    +1510      decider
    +1511    ).collect(Collectors.toUnmodifiableList());
    +1512  }
    +1513
    +1514  /**
    +1515   * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run
    +1516   * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. This
    +1517   * method variant runs each operation serially.
    +1518   *
    +1519   * <p>This method variant does not allow the invoking user to crawl recursively.</p>
    +1520   *
    +1521   * @see #streamFields(Descriptor) for the cleanest invocation of this method.
    +1522   *
    +1523   * @param descriptor Schema descriptor to crawl model definitions on.
    +1524   * @param predicate Filter predicate function, if applicable.
    +1525   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1526   */
    +1527  public static @Nonnull Stream<FieldPointer> forEachField(@Nonnull Descriptor descriptor,
    +1528                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate) {
    +1529    Objects.requireNonNull(descriptor);
    +1530    Objects.requireNonNull(predicate);
    +1531
    +1532    return streamFieldsRecursive(
    +1533            descriptor,
    +1534            descriptor,
    +1535            predicate,
    +1536            (field) -> false,
    +1537            "",
    +1538            false
    +1539    );
    +1540  }
    +1541
    +1542  /**
    +1543   * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run
    +1544   * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. This
    +1545   * method variant runs each operation serially.
    +1546   *
    +1547   * <p>This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search
    +1548   * is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate
    +1549   * is provided, the entire set of recursive effective fields is returned from the provided descriptor.</p>
    +1550   *
    +1551   * @see #streamFields(Descriptor) for the cleanest invocation of this method.
    +1552   *
    +1553   * @param descriptor Schema descriptor to crawl model definitions on.
    +1554   * @param predicate Filter predicate function, if applicable.
    +1555   * @param recursive Whether to perform recursion down to sub-messages.
    +1556   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1557   */
    +1558  public static @Nonnull Stream<FieldPointer> forEachField(@Nonnull Descriptor descriptor,
    +1559                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1560                                                           boolean recursive) {
    +1561    Objects.requireNonNull(descriptor);
    +1562    Objects.requireNonNull(predicate);
    +1563
    +1564    return streamFieldsRecursive(
    +1565            descriptor,
    +1566            descriptor,
    +1567            predicate,
    +1568            (field) -> recursive,
    +1569            "",
    +1570            false
    +1571    );
    +1572  }
    +1573
    +1574  /**
    +1575   * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run
    +1576   * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. This
    +1577   * method variant runs each operation serially.
    +1578   *
    +1579   * <p>If a `MESSAGE` field is encountered and the algorithm needs to decide whether to recurse, this variant includes
    +1580   * support for the `decider` function. `decider` is invoked to decide whether to recurse for opportunity to do so.</p>
    +1581   *
    +1582   * <p>This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search
    +1583   * is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate
    +1584   * is provided, the entire set of recursive effective fields is returned from the provided descriptor.</p>
    +1585   *
    +1586   * @see #streamFields(Descriptor) for the cleanest invocation of this method.
    +1587   *
    +1588   * @param descriptor Schema descriptor to crawl model definitions on.
    +1589   * @param predicate Filter predicate function, if applicable.
    +1590   * @param decider Function that decides whether to recurse.
    +1591   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1592   */
    +1593  public static @Nonnull Stream<FieldPointer> forEachField(@Nonnull Descriptor descriptor,
    +1594                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1595                                                           @Nonnull Predicate<FieldPointer> decider) {
    +1596    Objects.requireNonNull(descriptor);
    +1597    Objects.requireNonNull(predicate);
    +1598
    +1599    return streamFieldsRecursive(
    +1600            descriptor,
    +1601            descriptor,
    +1602            predicate,
    +1603            decider,
    +1604            "",
    +1605            false
    +1606    );
    +1607  }
    +1608
    +1609  /**
    +1610   * Crawl all fields, recursively, on the descriptor associated with the provided model instance, and return them in
    +1611   * a stream.
    +1612   *
    +1613   * <p>This method crawls recursively by default, but this behavior can be customized via the alternate method variants
    +1614   * listed below. Other variants also allow applying a predicate to filter the returned fields.</p>
    +1615   *
    +1616   * @see #streamFields(Descriptor, Optional) for the opportunity to provide a filter predicate.
    +1617   * @see #streamFields(Descriptor, Optional, Boolean) for the opportunity to control recursive crawling, and provide a
    +1618   *      filter predicate.
    +1619   *
    +1620   * @param descriptor Schema descriptor to crawl model definitions on.
    +1621   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1622   */
    +1623  public static @Nonnull <M extends Message> Stream<FieldPointer> streamFields(@Nonnull Descriptor descriptor) {
    +1624    return streamFields(descriptor, Optional.empty());
    +1625  }
    +1626
    +1627  /**
    +1628   * Crawl all fields, recursively, on the descriptor associated with the provided model instance. For each field
    +1629   * encountered, run `predicate` to determine whether to include the field, filtering the returned stream of fields
    +1630   * accordingly.
    +1631   *
    +1632   * <p>This method crawls recursively by default, but this behavior can be customized via the alternate method variants
    +1633   * listed below.</p>
    +1634   *
    +1635   * @see #streamFields(Descriptor, Optional, Boolean) for the opportunity to control recursive crawling.
    +1636   *
    +1637   * @param descriptor Schema descriptor to crawl model definitions on.
    +1638   * @param predicate Filter predicate function, if applicable.
    +1639   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1640   */
    +1641  public static @Nonnull Stream<FieldPointer> streamFields(@Nonnull Descriptor descriptor,
    +1642                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate) {
    +1643    return streamFields(descriptor, predicate, true);
    +1644  }
    +1645
    +1646  /**
    +1647   * Crawl all fields, recursively, on the descriptor associated with the provided model instance. For each field
    +1648   * encountered, run `predicate` to determine whether to include the field, filtering the returned stream of fields
    +1649   * accordingly. In this case, `predicate` is required.
    +1650   *
    +1651   * <p>This method crawls recursively by default, but this behavior can be customized via the alternate method variants
    +1652   * listed below.</p>
    +1653   *
    +1654   * @see #streamFields(Descriptor, Optional, Boolean) for the opportunity to control recursive crawling.
    +1655   *
    +1656   * @param descriptor Schema descriptor to crawl model definitions on.
    +1657   * @param predicate Filter predicate function, if applicable.
    +1658   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1659   */
    +1660  public static @Nonnull Stream<FieldPointer> streamFields(@Nonnull Descriptor descriptor,
    +1661                                                           @Nonnull Predicate<FieldPointer> predicate) {
    +1662    return streamFields(descriptor, Optional.of(predicate), true);
    +1663  }
    +1664
    +1665  /**
    +1666   * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run
    +1667   * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly.
    +1668   *
    +1669   * <p>This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search
    +1670   * is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate
    +1671   * is provided, the entire set of recursive effective fields is returned from the provided descriptor.</p>
    +1672   *
    +1673   * @see #streamFields(Descriptor) for the cleanest invocation of this method.
    +1674   *
    +1675   * @param descriptor Schema descriptor to crawl model definitions on.
    +1676   * @param predicate Filter predicate function, if applicable.
    +1677   * @param recursive Whether to descend to sub-models recursively.
    +1678   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1679   */
    +1680  public static @Nonnull Stream<FieldPointer> streamFields(@Nonnull Descriptor descriptor,
    +1681                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1682                                                           @Nonnull Boolean recursive) {
    +1683    Objects.requireNonNull(recursive, "cannot pass `null` for recursive switch");
    +1684
    +1685    return streamFields(
    +1686      descriptor,
    +1687      predicate,
    +1688      (field) -> recursive
    +1689    );
    +1690  }
    +1691
    +1692  /**
    +1693   * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run
    +1694   * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. By
    +1695   * default, all field streaming methods run in parallel.
    +1696   *
    +1697   * <p>If a `MESSAGE` field is encountered and the algorithm needs to decide whether to recurse, this variant includes
    +1698   * support for the `decider` function. `decider` is invoked to decide whether to recurse for opportunity to do so.</p>
    +1699   *
    +1700   * <p>This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search
    +1701   * is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate
    +1702   * is provided, the entire set of recursive effective fields is returned from the provided descriptor.</p>
    +1703   *
    +1704   * @see #streamFields(Descriptor) for the cleanest invocation of this method.
    +1705   *
    +1706   * @param descriptor Schema descriptor to crawl model definitions on.
    +1707   * @param predicate Filter predicate function, if applicable.
    +1708   * @param decider Function that decides whether to recurse.
    +1709   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1710   */
    +1711  public static @Nonnull Stream<FieldPointer> streamFields(@Nonnull Descriptor descriptor,
    +1712                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1713                                                           @Nonnull Predicate<FieldPointer> decider) {
    +1714    Objects.requireNonNull(descriptor);
    +1715    Objects.requireNonNull(predicate);
    +1716
    +1717    return streamFieldsRecursive(
    +1718      descriptor,
    +1719      descriptor,
    +1720      predicate,
    +1721      decider,
    +1722      "",
    +1723      true
    +1724    );
    +1725  }
    +1726
    +1727  private static @Nonnull Stream<FieldPointer> streamFieldsRecursive(
    +1728    @Nonnull Descriptor base,
    +1729    @Nonnull Descriptor descriptor,
    +1730    @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1731    @Nonnull Predicate<FieldPointer> decider,
    +1732    @Nonnull String parent,
    +1733    @Nonnull Boolean parallel) {
    +1734    return (parallel ? descriptor.getFields().parallelStream() : descriptor.getFields().stream()).flatMap((field) -> {
    +1735      var path = String.format("%s.%s", parent, field.getName());
    +1736      var pointer = new FieldPointer(
    +1737        base,
    +1738        parent,
    +1739        path,
    +1740        field
    +1741      );
    +1742
    +1743      var branch = Stream.of(pointer);
    +1744      if (field.getType() == FieldDescriptor.Type.MESSAGE
    +1745          && !field.getMessageType().getFullName().equals(field.getContainingType().getFullName())
    +1746          && decider.test(pointer)) {
    +1747        return Stream.concat(branch, streamFieldsRecursive(
    +1748          base,
    +1749          descriptor.findFieldByNumber(field.getNumber()).getMessageType(),
    +1750          predicate,
    +1751          decider,
    +1752          path,
    +1753          parallel
    +1754        ));
    +1755      }
    +1756      return branch;
    +1757
    +1758    }).filter((field) ->
    +1759      predicate.map(fieldDescriptorPredicate ->
    +1760        fieldDescriptorPredicate.test(field)).orElse(true)
    +1761
    +1762    );
    +1763  }
    +1764}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/ModelMetadata.FieldPointer.html b/docs/java/src-html/gust/backend/model/ModelMetadata.FieldPointer.html new file mode 100644 index 000000000..12edad7df --- /dev/null +++ b/docs/java/src-html/gust/backend/model/ModelMetadata.FieldPointer.html @@ -0,0 +1,1838 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.common.annotations.VisibleForTesting;
    +016import com.google.common.base.CharMatcher;
    +017import com.google.common.collect.ImmutableSortedSet;
    +018import com.google.errorprone.annotations.CanIgnoreReturnValue;
    +019import com.google.protobuf.DescriptorProtos.FieldOptions;
    +020import com.google.protobuf.DescriptorProtos.MessageOptions;
    +021import com.google.protobuf.Descriptors.Descriptor;
    +022import com.google.protobuf.Descriptors.FieldDescriptor;
    +023import com.google.protobuf.FieldMask;
    +024import com.google.protobuf.GeneratedMessage.GeneratedExtension;
    +025import com.google.protobuf.Message;
    +026import com.google.protobuf.util.FieldMaskUtil;
    +027import tools.elide.core.CollectionMode;
    +028import tools.elide.core.Datamodel;
    +029import tools.elide.core.DatapointType;
    +030import tools.elide.core.FieldType;
    +031
    +032import javax.annotation.Nonnull;
    +033import javax.annotation.concurrent.Immutable;
    +034import javax.annotation.concurrent.ThreadSafe;
    +035import java.io.Serializable;
    +036import java.util.*;
    +037import java.util.concurrent.ConcurrentSkipListSet;
    +038import java.util.function.Function;
    +039import java.util.function.Predicate;
    +040import java.util.stream.Collectors;
    +041import java.util.stream.Stream;
    +042
    +043
    +044/**
    +045 * Utility helper class, which is responsible for resolving metadata (based on the core framework annotations) from
    +046 * arbitrary model definitions.
    +047 *
    +048 * <p>Model "metadata," in this case, refers to annotation-based declarations on the protocol buffer definitions
    +049 * themselves. As such, the source for most (if not all) of the data provided by this helper is the {@link Descriptor}
    +050 * that accompanies a Java-side protobuf model.</p>
    +051 *
    +052 * <p><b>Note:</b> Using this class, or the model layer writ-large, requires the full runtime Protobuf library (the lite
    +053 * runtime for Protobuf in Java does not include descriptors at all, which this class relies on).</p>
    +054 */
    +055@ThreadSafe
    +056@SuppressWarnings({"WeakerAccess", "unused", "OptionalUsedAsFieldOrParameterType"})
    +057public final class ModelMetadata {
    +058  private ModelMetadata() { /* Disallow construction. */ }
    +059
    +060  /** Utility class that points to a specific field, in a specific context. */
    +061  @Immutable
    +062  @ThreadSafe
    +063  public final static class FieldPointer implements Serializable, Comparable<FieldPointer> {
    +064    private static final long serialVersionUID = 20210203L;
    +065
    +066    /** Depth of this field, based on the number of dots in the path. */
    +067    private final @Nonnull Integer depth;
    +068
    +069    /** Access path, minus the leaf field. */
    +070    private final @Nonnull String parent;
    +071
    +072    /** Access path to the field in some context. */
    +073    private final @Nonnull String path;
    +074
    +075    /** Base model type. */
    +076    private final @Nonnull Descriptor base;
    +077
    +078    /** Field descriptor for the field in question. */
    +079    private final @Nonnull FieldDescriptor field;
    +080
    +081    /**
    +082     * Setup a new field pointer - generally kept private and resolved via {@link ModelMetadata}.
    +083     *
    +084     * @param base Base model type where {@code path} begins.
    +085     * @param parent Dotted-path without the leaf field, or just `""` if the field is at the root.
    +086     * @param path Dotted-path to the field in question.
    +087     * @param field Field descriptor for the field in question.
    +088     */
    +089    FieldPointer(@Nonnull Descriptor base,
    +090                 @Nonnull String parent,
    +091                 @Nonnull String path,
    +092                 @Nonnull FieldDescriptor field) {
    +093      this.path = path;
    +094      this.base = base;
    +095      this.field = field;
    +096      this.parent = parent;
    +097      this.depth = CharMatcher.is('.').countIn(path);
    +098    }
    +099
    +100    /**
    +101     * Setup a new field pointer - generally kept private and resolved via {@link ModelMetadata}. This consructor
    +102     * creates a field without a parent set.
    +103     *
    +104     * @param base Base model type where {@code path} begins.
    +105     * @param path Dotted-path to the field in question.
    +106     * @param field Field descriptor for the field in question.
    +107     */
    +108    FieldPointer(@Nonnull Descriptor base,
    +109                 @Nonnull String path,
    +110                 @Nonnull FieldDescriptor field) {
    +111      this.path = path;
    +112      this.base = base;
    +113      this.field = field;
    +114      this.parent = "";
    +115      this.depth = CharMatcher.is('.').countIn(path);
    +116    }
    +117
    +118    /**
    +119     * Wrap the field at the specified name on the provided model.
    +120     *
    +121     * @param model Descriptor for a protocol buffer model.
    +122     * @param name Name of a field to get from the provided buffer model.
    +123     * @return Field pointer wrapping the provided information.
    +124     */
    +125    public static @Nonnull FieldPointer fieldAtName(@Nonnull Descriptor model,
    +126                                                    @Nonnull String name) {
    +127      return new FieldPointer(
    +128          model,
    +129          name,
    +130          model.findFieldByName(name)
    +131      );
    +132    }
    +133
    +134    /** {@inheritDoc} */
    +135    @Override
    +136    public boolean equals(Object o) {
    +137      if (this == o) return true;
    +138      if (o == null || getClass() != o.getClass()) return false;
    +139      FieldPointer that = (FieldPointer) o;
    +140      return this.depth.equals(that.depth)
    +141        && com.google.common.base.Objects.equal(path, that.path)
    +142        && com.google.common.base.Objects.equal(base.getFullName(), that.base.getFullName());
    +143    }
    +144
    +145    /** {@inheritDoc} */
    +146    @Override
    +147    public int hashCode() {
    +148      return com.google.common.base.Objects
    +149        .hashCode(path, base.getFullName());
    +150    }
    +151
    +152    /** {@inheritDoc} */
    +153    @Override
    +154    public int compareTo(@Nonnull FieldPointer other) {
    +155      return this.path.compareTo(other.path);
    +156    }
    +157
    +158    /** {@inheritDoc} */
    +159    @Override
    +160    public String toString() {
    +161      return "FieldPointer{" +
    +162        "base='" + base.getName() + '\'' +
    +163        ", path=" + path +
    +164        '}';
    +165    }
    +166
    +167    /** @return Path to the specified field. */
    +168    public @Nonnull String getParent() {
    +169      return parent;
    +170    }
    +171
    +172    /** @return Path to the specified field. */
    +173    public boolean hasParent() {
    +174      return !parent.isEmpty() && !parent.isBlank();
    +175    }
    +176
    +177    /** @return Path to the specified field. */
    +178    public @Nonnull String getPath() {
    +179      return path;
    +180    }
    +181
    +182    /** @return Simple proto name for the field. */
    +183    public @Nonnull String getName() {
    +184      return field.getName();
    +185    }
    +186
    +187    /** @return Simple JSON name for the field. */
    +188    public @Nonnull String getJsonName() {
    +189      return field.getJsonName();
    +190    }
    +191
    +192    /** @return Base model type where the specified path begins. */
    +193    public @Nonnull Descriptor getBase() {
    +194      return base;
    +195    }
    +196
    +197    /** @return Descriptor for the targeted field. */
    +198    public @Nonnull FieldDescriptor getField() {
    +199      return field;
    +200    }
    +201  }
    +202
    +203  /** Utility class that holds a {@link FieldPointer} and matching field value. */
    +204  public final static class FieldContainer<V> implements Serializable, Comparable<FieldContainer<V>> {
    +205    /** Pointer to the field which holds this value. */
    +206    private final @Nonnull FieldPointer field;
    +207
    +208    /** Value for the field, if found. */
    +209    private final @Nonnull Optional<V> value;
    +210
    +211    /**
    +212     * Setup a new field pointer - generally kept private and resolved via {@link ModelMetadata}.
    +213     *
    +214     * @param field Pointer to the field that holds this value.
    +215     * @param value Value extracted for the specified field.
    +216     */
    +217    FieldContainer(@Nonnull FieldPointer field,
    +218                   @Nonnull Optional<V> value) {
    +219      this.field = field;
    +220      this.value = value;
    +221    }
    +222
    +223    /** {@inheritDoc} */
    +224    @Override
    +225    public boolean equals(Object o) {
    +226      if (this == o) return true;
    +227      if (o == null || getClass() != o.getClass()) return false;
    +228      FieldContainer<?> that = (FieldContainer<?>) o;
    +229      return field.equals(that.field)
    +230        && (value.isPresent() == that.value.isPresent())
    +231        && (value.equals(that.value));
    +232    }
    +233
    +234    /** {@inheritDoc} */
    +235    @Override
    +236    public int hashCode() {
    +237      return com.google.common.base.Objects
    +238        .hashCode(field, value);
    +239    }
    +240
    +241    /** {@inheritDoc} */
    +242    @Override
    +243    public int compareTo(@Nonnull FieldContainer<V> other) {
    +244      return this.field.compareTo(other.field);
    +245    }
    +246
    +247    /** {@inheritDoc} */
    +248    @Override
    +249    public String toString() {
    +250      return "FieldContainer{" +
    +251        "" + field.base.getName() +
    +252        ", path=" + field.path +
    +253        ", hasValue=" + value.isPresent() +
    +254        '}';
    +255    }
    +256
    +257    /** Pointer to the field holding the specified value. */
    +258    public @Nonnull FieldPointer getField() {
    +259      return field;
    +260    }
    +261
    +262    /** Value associated with the specified field, or {@link Optional#empty()} if the field has no initialized value. */
    +263    public @Nonnull Optional<V> getValue() {
    +264      return value;
    +265    }
    +266  }
    +267
    +268  // -- Internals -- //
    +269
    +270  /**
    +271   * Match an annotation to a field. If the field is not annotated as such, the method returns `false`.
    +272   *
    +273   * @param field Field to check for the provided annotation.
    +274   * @param annotation Annotation to check for.
    +275   * @return Whether the field is annotated with the provided annotation.
    +276   */
    +277  public static boolean matchFieldAnnotation(@Nonnull FieldDescriptor field, @Nonnull FieldType annotation) {
    +278    if (field.getOptions().hasExtension(Datamodel.field)) {
    +279      var extension = field.getOptions().getExtension(Datamodel.field);
    +280      return annotation.equals(extension.getType());
    +281    }
    +282    return false;
    +283  }
    +284
    +285  /**
    +286   * Match a collection annotation. If the field or model is not annotated as such, the method returns `false`.
    +287   *
    +288   * @param field Field to check for the provided annotation.
    +289   * @param mode Collection mode to check for.
    +290   * @return Whether the field is annotated for the provided collection mode.
    +291   */
    +292  @SuppressWarnings("SameParameterValue")
    +293  public static boolean matchCollectionAnnotation(@Nonnull FieldDescriptor field, @Nonnull CollectionMode mode) {
    +294    if (field.getOptions().hasExtension(Datamodel.collection)) {
    +295      var extension = field.getOptions().getExtension(Datamodel.collection);
    +296      return mode.equals(extension.getMode());
    +297    }
    +298    return false;
    +299  }
    +300
    +301  /**
    +302   * Resolve a model field within the tree of {@code descriptor}, where an instance of annotation data of type
    +303   * {@code ext} is affixed to the field. If the (optional) provided {@code filter} function agrees, the item is
    +304   * returned to the caller in a {@link FieldPointer}.
    +305   *
    +306   * <p>If the field cannot be found, no exception is raised, and {@link Optional#empty()} is returned. The search may
    +307   * also be conducted in {@code recursive} mode, which proceeds to examine sub-messages if the requested field cannot
    +308   * be located on the top-level {@code descriptor}.</p>
    +309   *
    +310   * @param descriptor Descriptor where we should begin our search for the desired property.
    +311   * @param ext Extension the field is annotated with. Only fields annotated with this extension are eligible.
    +312   * @param recursive Whether to search recursively, or just on the top-level instance.
    +313   * @param filter Filter function to dispatch per-found-field. The first one to return {@code true} wins.
    +314   * @param stack Property stack, filled out as we recursively descend.
    +315   * @param <E> Generic type of the model extension object.
    +316   * @return Optional, containing either a resolved {@link FieldPointer}, or empty.
    +317   */
    +318  @VisibleForTesting
    +319  static @Nonnull <E> Optional<FieldPointer> resolveAnnotatedField(@Nonnull Descriptor descriptor,
    +320                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext,
    +321                                                                   @Nonnull Boolean recursive,
    +322                                                                   @Nonnull Optional<Function<E, Boolean>> filter,
    +323                                                                   @Nonnull String stack) {
    +324    Objects.requireNonNull(descriptor, "Cannot resolve field from `null` descriptor.");
    +325    Objects.requireNonNull(ext, "Cannot resolve field from `null` descriptor.");
    +326    Objects.requireNonNull(recursive, "Cannot pass `null` for `recursive` flag.");
    +327    Objects.requireNonNull(filter, "Pass empty optional, not `null`, for field filter parameter.");
    +328    Objects.requireNonNull(stack, "Recursive property stack should not be `null`.");
    +329
    +330    for (FieldDescriptor field : descriptor.getFields()) {
    +331      if (field.getOptions().hasExtension(ext)) {
    +332        var extension = field.getOptions().getExtension(ext);
    +333        if (filter.isEmpty() || filter.get().apply(extension))
    +334          return Optional.of(new FieldPointer(
    +335            descriptor,
    +336            stack.isEmpty() ? field.getName() : stack + "." + field.getName(),
    +337            field));
    +338      }
    +339
    +340      // should we recurse?
    +341      if (recursive && field.getType() == FieldDescriptor.Type.MESSAGE) {
    +342        // if so, append the current prop to the stack and give it a shot
    +343        //noinspection ConstantConditions
    +344        var sub = resolveAnnotatedField(
    +345          field.getMessageType(),
    +346          ext,
    +347          recursive,
    +348          filter,
    +349          stack.isEmpty() ? field.getName() : stack + "." + field.getName());
    +350        if (sub.isPresent())
    +351          return sub;
    +352      }
    +353    }
    +354    return Optional.empty();
    +355  }
    +356
    +357  /**
    +358   * Resolve a model field within the tree of {@code descriptor}, identified by the specified deep {@code path}. If the
    +359   * (optional) provided {@code filter} function agrees, the item is returned to the caller in a {@link FieldPointer}.
    +360   *
    +361   * <p>If the field cannot be found, no exception is raised, and {@link Optional#empty()} is returned. The search may
    +362   * also be conducted in {@code recursive} mode, which proceeds to examine sub-messages if the requested field cannot
    +363   * be located on the top-level {@code descriptor}.</p>
    +364   *
    +365   * @param original Top-level descriptor where we should begin our search for the desired property.
    +366   * @param descriptor Current-level descriptor we are scanning (for recursion).
    +367   * @param path Deep dotted-path to the field we are being asked to resolve.
    +368   * @param remaining Remaining segments of {@code path} to follow/compute.
    +369   * @return Optional, containing either a resolved {@link FieldPointer}, or empty.
    +370   * @throws IllegalArgumentException If the provided path is syntactically invalid.
    +371   * @throws IllegalArgumentException If an attempt is made to access a property on a primitive field.
    +372   */
    +373  @VisibleForTesting
    +374  static @Nonnull Optional<FieldPointer> resolveArbitraryField(@Nonnull Descriptor original,
    +375                                                               @Nonnull Descriptor descriptor,
    +376                                                               @Nonnull String path,
    +377                                                               @Nonnull String remaining) {
    +378    Objects.requireNonNull(original, "Cannot resolve field from `null` descriptor.");
    +379    Objects.requireNonNull(descriptor, "Cannot resolve field from `null` descriptor.");
    +380    Objects.requireNonNull(path, "Cannot resolve field from `null` path.");
    +381    Objects.requireNonNull(remaining, "Recursive remaining stack should not be `null`.");
    +382
    +383    if (remaining.startsWith(".") || remaining.endsWith(".") || remaining.contains(" "))
    +384      throw new IllegalArgumentException(String.format("Invalid deep-field path '%s'.", path));
    +385    if (!remaining.contains(".")) {
    +386      // maybe we're lucky and don't need to recurse
    +387      for (FieldDescriptor field : descriptor.getFields()) {
    +388        if (remaining.equals(field.getName())) {
    +389          return Optional.of(new FieldPointer(
    +390            original,
    +391            path,
    +392            field));
    +393        }
    +394      }
    +395    } else {
    +396      // need to recurse
    +397      String segment = remaining.substring(0, remaining.indexOf('.'));
    +398      var messageField = descriptor.findFieldByName(segment);
    +399      if (messageField != null && messageField.getType() == FieldDescriptor.Type.MESSAGE) {
    +400        // found the next tier
    +401        var subType = messageField.getMessageType();
    +402        String newRemainder = remaining.substring(remaining.indexOf('.') + 1);
    +403        return resolveArbitraryField(
    +404          original,
    +405          subType,
    +406          path,
    +407          newRemainder);
    +408      } else if (messageField != null) {
    +409        // it's not a message :(
    +410        throw new IllegalArgumentException(
    +411          String.format(
    +412            "Cannot access sub-field of primitive leaf field, at '%s' on model type '%s'.",
    +413            path,
    +414            original.getName()));
    +415      }
    +416    }
    +417    // property not found
    +418    return Optional.empty();
    +419  }
    +420
    +421  /**
    +422   * Splice an arbitrary field {@code value} at {@code path} into the provided {@code builder}. If an empty value
    +423   * ({@link Optional#empty()}) is provided, clear any existing value residing at {@code path}. In all cases, mutate the
    +424   * existing {@code builder} rather than returning a copy.
    +425   *
    +426   * @param original Top-level builder, which we hand back at the end.
    +427   * @param builder Builder to splice the value into and return.
    +428   * @param path Path at which the target property resides.
    +429   * @param value Value which we should set the target property to, or clear (if passed {@link Optional#empty()}).
    +430   * @param remaining Remaining properties to recurse down to. Internal use only.
    +431   * @param <Builder> Builder type which we are operating on for this splice.
    +432   * @param <Value> Value type which we are operating with for this splice.
    +433   * @return Provided {@code builder} after being mutated with the specified property value.
    +434   */
    +435  @VisibleForTesting
    +436  static <Builder extends Message.Builder, Value> Builder spliceArbitraryField(@Nonnull Message.Builder original,
    +437                                                                               @Nonnull Message.Builder builder,
    +438                                                                               @Nonnull String path,
    +439                                                                               @Nonnull Optional<Value> value,
    +440                                                                               @Nonnull String remaining) {
    +441    Objects.requireNonNull(original, "Cannot splice field into `null` original builder.");
    +442    Objects.requireNonNull(builder, "Cannot splice field into `null` builder.");
    +443    Objects.requireNonNull(path, "Cannot resolve field from `null` path.");
    +444    Objects.requireNonNull(value, "Pass an empty optional, not `null`, for value.");
    +445    Objects.requireNonNull(remaining, "Recursive remaining stack should not be `null`.");
    +446    if (path.startsWith("."))
    +447      throw new IllegalArgumentException(String.format(
    +448        "Cannot splice path that starts with `.` (got: '%s').", path));
    +449    if (remaining.startsWith("."))
    +450      throw new IllegalArgumentException(String.format(
    +451        "Cannot splice path that starts with `.` (got: '%s').", remaining));
    +452
    +453    var descriptor = builder.getDescriptorForType();
    +454    if (!remaining.isEmpty() && !remaining.contains(".")) {
    +455      // thankfully, no need to recurse
    +456      var field = Objects.requireNonNull(
    +457        descriptor.findFieldByName(remaining), String.format("failed to locate field %s", remaining));
    +458      if (value.isPresent()) {
    +459        try {
    +460          builder.setField(field, value.get());
    +461        } catch (IllegalArgumentException iae) {
    +462          throw new ClassCastException(String.format(
    +463            "Failed to set field '%s': value type mismatch.",
    +464            path));
    +465        }
    +466      } else {
    +467        builder.clearField(field);
    +468      }
    +469      //noinspection unchecked
    +470      return (Builder)original;
    +471    } else {
    +472      // we have a sub-message that is initialized, so we need to recurse.
    +473      String segment = remaining.substring(0, remaining.indexOf('.'));
    +474      String newRemainder = remaining.substring(remaining.indexOf('.') + 1);
    +475      return spliceArbitraryField(
    +476        original,
    +477        builder.getFieldBuilder(Objects.requireNonNull(
    +478          descriptor.findFieldByName(segment),
    +479          String.format(
    +480            "Failed to locate sub-builder at path '%s' on model '%s'.",
    +481            segment,
    +482            builder.getDescriptorForType().getFullName()
    +483          )
    +484        )),
    +485        path,
    +486        value,
    +487        newRemainder
    +488      );
    +489    }
    +490  }
    +491
    +492  @VisibleForTesting
    +493  static @Nonnull <V> FieldContainer<V> pluckFieldRecursive(@Nonnull Message original,
    +494                                                            @Nonnull Message instance,
    +495                                                            @Nonnull String path,
    +496                                                            @Nonnull String remaining) {
    +497    Objects.requireNonNull(original, "Cannot resolve field from `null` descriptor.");
    +498    Objects.requireNonNull(instance, "Cannot resolve field from `null` instance.");
    +499    Objects.requireNonNull(path, "Cannot resolve field from `null` path.");
    +500    Objects.requireNonNull(remaining, "Recursive remaining stack should not be `null`.");
    +501
    +502    var descriptor = instance.getDescriptorForType();
    +503    if (remaining.startsWith(".") || remaining.endsWith(".") || remaining.contains(" ")) {
    +504      throw new IllegalArgumentException("Cannot begin or end model property path with `.`");
    +505    } else if (!remaining.isEmpty() && !remaining.contains(".")) {
    +506      // we got lucky, no need to recurse
    +507      var field = descriptor.findFieldByName(remaining);
    +508      if (field != null) {
    +509        if (field.getType() == FieldDescriptor.Type.MESSAGE) {
    +510          Message modelInstance = (Message)instance.getField(field);
    +511          //noinspection unchecked
    +512          return new FieldContainer<>(
    +513            new FieldPointer(descriptor, path, field),
    +514            !modelInstance.getAllFields().isEmpty() ? Optional.of((V)modelInstance) : Optional.empty());
    +515        } else {
    +516          //noinspection unchecked
    +517          return new FieldContainer<>(
    +518            new FieldPointer(descriptor, path, field),
    +519            Optional.of((V) instance.getField(field)));
    +520        }
    +521      }
    +522    } else {
    +523      // find next segment
    +524      String segment = remaining.substring(0, remaining.indexOf('.'));
    +525      var messageField = descriptor.findFieldByName(segment);
    +526      if (messageField != null && messageField.getType() == FieldDescriptor.Type.MESSAGE) {
    +527        if (!instance.hasField(messageField)) {
    +528          // there is a sub-message that is not initialized. so the field is technically empty.
    +529          return new FieldContainer<>(
    +530            new FieldPointer(original.getDescriptorForType(), path, messageField),
    +531            Optional.empty());
    +532        } else {
    +533          // we have a sub-message that is initialized, so we need to recurse.
    +534          String newRemainder = remaining.substring(remaining.indexOf('.') + 1);
    +535          return pluckFieldRecursive(
    +536            original,
    +537            (Message)instance.getField(messageField),
    +538            path,
    +539            newRemainder);
    +540        }
    +541      } else if (messageField != null) {
    +542        // it's not a message :(
    +543        throw new IllegalArgumentException(
    +544          String.format(
    +545            "Cannot access sub-field of primitive leaf field, at '%s' on model type '%s'.",
    +546            path,
    +547            original.getDescriptorForType().getName()));
    +548      }
    +549    }
    +550    throw new IllegalArgumentException(
    +551      String.format("Failed to locate field '%s' on model type '%s'.", path, descriptor.getName()));
    +552  }
    +553
    +554  // -- Metadata: Qualified Names -- //
    +555
    +556  /**
    +557   * Resolve the fully-qualified type path, or name, for the provided datamodel type descriptor. This is essentially
    +558   * syntactic sugar.
    +559   *
    +560   * @param descriptor Model descriptor to resolve a fully-qualified name for.
    +561   * @return Fully-qualified model type name.
    +562   */
    +563  public static @Nonnull String fullyQualifiedName(@Nonnull Descriptor descriptor) {
    +564    return descriptor.getFullName();
    +565  }
    +566
    +567  /**
    +568   * Resolve the fully-qualified type path, or name, for the provided datamodel instance. This method is essentially
    +569   * syntactic sugar for accessing the model instance's descriptor, and then grabbing the fully-qualified name.
    +570   *
    +571   * @param model Model instance to resolve a fully-qualified name for.
    +572   * @return Fully-qualified model type name.
    +573   */
    +574  public static @Nonnull String fullyQualifiedName(@Nonnull Message model) {
    +575    return fullyQualifiedName(model.getDescriptorForType());
    +576  }
    +577
    +578  // -- Metadata: Role Annotations -- //
    +579
    +580  /**
    +581   * Resolve the general type for a given datamodel type descriptor. The type is either set by default, or set by an
    +582   * explicit annotation affixed to the protocol buffer definition that backs the model.
    +583   *
    +584   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +585   * model? A wire model? {@link DatapointType} will tell you.</p>
    +586   *
    +587   * @param descriptor Model descriptor to retrieve a type for.
    +588   * @return Type of the provided datamodel.
    +589   */
    +590  public static @Nonnull DatapointType role(@Nonnull Descriptor descriptor) {
    +591    return modelAnnotation(descriptor, Datamodel.role, false).orElse(DatapointType.OBJECT);
    +592  }
    +593
    +594  /**
    +595   * Resolve the general type for a given datamodel. The type is either set by default, or set by an explicit annotation
    +596   * affixed to the protocol buffer definition that backs the model.
    +597   *
    +598   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +599   * model? A wire model? {@link DatapointType} will tell you.</p>
    +600   *
    +601   * @param model Model to retrieve a type for.
    +602   * @return Type of the provided datamodel.
    +603   */
    +604  public static @Nonnull DatapointType role(@Nonnull Message model) {
    +605    Objects.requireNonNull(model, "Cannot resolve type for `null` model.");
    +606    return role(model.getDescriptorForType());
    +607  }
    +608
    +609  /**
    +610   * Enforce that a particular datamodel type matches <b>any</b> of the provided {@link DatapointType} annotations.
    +611   *
    +612   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +613   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +614   * a set of types for you.</p>
    +615   *
    +616   * @param model Model to validate against the provided set of types.
    +617   * @param type Type to enforce for the provided model.
    +618   * @return Whether the provided model is a <i>member-of</i> (annotated-by) any of the provided {@code types}.
    +619   */
    +620  public static boolean matchRole(@Nonnull Message model, @Nonnull DatapointType type) {
    +621    Objects.requireNonNull(type, "Cannot match `null` model type.");
    +622    return type.equals(role(model));
    +623  }
    +624
    +625  /**
    +626   * Enforce that a particular datamodel schema {@code descriptor} matches <b>any</b> of the provided
    +627   * {@link DatapointType} annotations.
    +628   *
    +629   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +630   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +631   * a set of types for you.</p>
    +632   *
    +633   * @param descriptor Schema descriptor to validate against the provided set of types.
    +634   * @param type Type to enforce for the provided model.
    +635   * @return Whether the provided model is a <i>member-of</i> (annotated-by) any of the provided {@code types}.
    +636   */
    +637  public static boolean matchRole(@Nonnull Descriptor descriptor, @Nonnull DatapointType type) {
    +638    Objects.requireNonNull(type, "Cannot match `null` descriptor type.");
    +639    return type.equals(role(descriptor));
    +640  }
    +641
    +642  /**
    +643   * <b>Check</b> that a particular datamodel type matches <b>any</b> of the provided {@link DatapointType} annotations.
    +644   *
    +645   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +646   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +647   * a set of types for you.</p>
    +648   *
    +649   * @param model Model to validate against the provided set of types.
    +650   * @param types Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +651   * @return Whether the provided model is a <i>member-of</i> (annotated-by) any of the provided {@code types}.
    +652   */
    +653  public static boolean matchAnyRole(@Nonnull Message model, @Nonnull DatapointType ...types) {
    +654    Objects.requireNonNull(types, "Cannot match `null` model types.");
    +655    return EnumSet.copyOf(Arrays.asList(types)).contains(role(model));
    +656  }
    +657
    +658  /**
    +659   * <b>Check</b> that a particular schema {@code descriptor} matches <b>any</b> of the provided {@link DatapointType}
    +660   * annotations.
    +661   *
    +662   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +663   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +664   * a set of types for you.</p>
    +665   *
    +666   * @param descriptor Schema descriptor to validate against the provided set of types.
    +667   * @param types Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +668   * @return Whether the provided model is a <i>member-of</i> (annotated-by) any of the provided {@code types}.
    +669   */
    +670  public static boolean matchAnyRole(@Nonnull Descriptor descriptor, @Nonnull DatapointType ...types) {
    +671    Objects.requireNonNull(types, "Cannot match `null` model types.");
    +672    return EnumSet.copyOf(Arrays.asList(types)).contains(role(descriptor));
    +673  }
    +674
    +675  /**
    +676   * <b>Enforce</b> that a particular {@code model} instance matches the provided {@link DatapointType} annotation.
    +677   *
    +678   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +679   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +680   * a set of types for you.</p>
    +681   *
    +682   * @param model Model to validate against the provided set of types.
    +683   * @param type Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +684   * @throws InvalidModelType If the specified model's type is not included in {@code types}.
    +685   */
    +686  public static void enforceRole(@Nonnull Message model, @Nonnull DatapointType type) throws InvalidModelType {
    +687    if (!matchRole(model, type)) throw InvalidModelType.from(model, EnumSet.of(type));
    +688  }
    +689
    +690  /**
    +691   * <b>Enforce</b> that a particular datamodel schema {@code descriptor} matches the provided {@link DatapointType}
    +692   * annotation.
    +693   *
    +694   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +695   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +696   * a set of types for you.</p>
    +697   *
    +698   * @param descriptor Descriptor to validate against the provided set of types.
    +699   * @param type Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +700   * @throws InvalidModelType If the specified model's type is not included in {@code types}.
    +701   */
    +702  public static void enforceRole(@Nonnull Descriptor descriptor, @Nonnull DatapointType type) throws InvalidModelType {
    +703    if (!matchRole(descriptor, type)) throw InvalidModelType.from(descriptor, EnumSet.of(type));
    +704  }
    +705
    +706  /**
    +707   * <b>Enforce</b> that a particular datamodel type matches <b>any</b> of the provided {@link DatapointType}
    +708   * annotations.
    +709   *
    +710   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +711   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +712   * a set of types for you.</p>
    +713   *
    +714   * @param model Model to validate against the provided set of types.
    +715   * @param types Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +716   * @throws InvalidModelType If the specified model's type is not included in {@code types}.
    +717   */
    +718  public static void enforceAnyRole(@Nonnull Message model, @Nonnull DatapointType ...types) throws InvalidModelType {
    +719    if (!matchAnyRole(model, types)) throw InvalidModelType.from(model, EnumSet.copyOf(Arrays.asList(types)));
    +720  }
    +721
    +722  /**
    +723   * <b>Enforce</b> that a particular schema {@code descriptor} matches <b>any</b> of the provided {@link DatapointType}
    +724   * annotations.
    +725   *
    +726   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +727   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +728   * a set of types for you.</p>
    +729   *
    +730   * @param descriptor Schema descriptor to validate against the provided set of types.
    +731   * @param types Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +732   * @throws InvalidModelType If the specified model's type is not included in {@code types}.
    +733   */
    +734  public static void enforceAnyRole(@Nonnull Descriptor descriptor,
    +735                                    @Nonnull DatapointType ...types) throws InvalidModelType {
    +736    if (!matchAnyRole(descriptor, types)) throw InvalidModelType.from(descriptor, EnumSet.copyOf(Arrays.asList(types)));
    +737  }
    +738
    +739  // -- Metadata: Field Resolution -- //
    +740
    +741  /**
    +742   * Resolve an arbitrary field pointer from the provided model {@code instance}, specified by the given {@code path} to
    +743   * the property. If the property cannot be found, {@link Optional#empty()} is returned.
    +744   *
    +745   * <p>This method is <b>safe</b>, in that, unlike other util methods for model metadata, it will not throw if the
    +746   * provided {@code path} is invalid.</p>
    +747   *
    +748   * @param instance Model instance on which to resolve the specified field.
    +749   * @param path Dotted deep-path to the desired field.
    +750   * @return Resolved field pointer for the requested field, or {@link Optional#empty()}.
    +751   */
    +752  public static @Nonnull Optional<FieldPointer> resolveField(@Nonnull Message instance, @Nonnull String path) {
    +753    return resolveField(instance.getDescriptorForType(), path);
    +754  }
    +755
    +756  /**
    +757   * Resolve an arbitrary field pointer from the provided model type {@code escriptor}, specified by the given
    +758   * {@code path} to the property. If the property cannot be found, {@link Optional#empty()} is returned.
    +759   *
    +760   * <p>This method is <b>safe</b>, in that, unlike other util methods for model metadata, it will not throw if the
    +761   * provided {@code path} is invalid.</p>
    +762   *
    +763   * @param descriptor Model type descriptor on which to resolve the specified field.
    +764   * @param path Dotted deep-path to the desired field.
    +765   * @return Resolved field pointer for the requested field, or {@link Optional#empty()}.
    +766   */
    +767  public static @Nonnull Optional<FieldPointer> resolveField(@Nonnull Descriptor descriptor, @Nonnull String path) {
    +768    return resolveArbitraryField(descriptor, descriptor, path, path);
    +769  }
    +770
    +771  // -- Metadata: Model Annotations -- //
    +772
    +773  /**
    +774   * Retrieve a model-level annotation, from {@code instance}, structured by {@code ext}. If no instance of the
    +775   * requested model annotation can be found, {@link Optional#empty()} is returned. Search recursively is supported as
    +776   * well, which descends the search to sub-messages to search for the desired annotation.
    +777   *
    +778   * @param instance Message instance to scan for the specified annotation.
    +779   * @param ext Extension to fetch from the subject model, or any sub-model (if {@code recursive} is {@code true}).
    +780   * @param recursive Whether to search recursively for the desired extension.
    +781   * @param <E> Generic type of extension we are looking for.
    +782   * @return Optional, either {@link Optional#empty()}, or wrapping the found extension data instance.
    +783   */
    +784  public static @Nonnull <E> Optional<E> modelAnnotation(@Nonnull Message instance,
    +785                                                         @Nonnull GeneratedExtension<MessageOptions, E> ext,
    +786                                                         @Nonnull Boolean recursive) {
    +787    return modelAnnotation(instance.getDescriptorForType(), ext, recursive);
    +788  }
    +789
    +790  /**
    +791   * Retrieve a model-level annotation, from the provided model schema {@code descriptor}, structured by {@code ext}. If
    +792   * no instance of the requested model annotation can be found, {@link Optional#empty()} is returned. Search
    +793   * recursively is supported as well, which descends the search to sub-messages to search for the desired annotation.
    +794   *
    +795   * @param descriptor Schema descriptor for a model type.
    +796   * @param ext Extension to fetch from the subject model, or any sub-model (if {@code recursive} is {@code true}).
    +797   * @param recursive Whether to search recursively for the desired extension.
    +798   * @param <E> Generic type of extension we are looking for.
    +799   * @return Optional, either {@link Optional#empty()}, or wrapping the found extension data instance.
    +800   */
    +801  public static @Nonnull <E> Optional<E> modelAnnotation(@Nonnull Descriptor descriptor,
    +802                                                         @Nonnull GeneratedExtension<MessageOptions, E> ext,
    +803                                                         @Nonnull Boolean recursive) {
    +804    Objects.requireNonNull(descriptor, "Cannot resolve type for `null` descriptor.");
    +805    if (descriptor.getOptions().hasExtension(ext))
    +806      return Optional.of(descriptor.getOptions().getExtension(ext));
    +807    if (recursive) {
    +808      // loop through fields. gather any sub-messages, and check procedurally if any of them match. if we find one that
    +809      // does, we return immediately.
    +810      for (FieldDescriptor field : descriptor.getFields()) {
    +811        if (field.getType() == FieldDescriptor.Type.MESSAGE) {
    +812          //noinspection ConstantConditions
    +813          var subresult = modelAnnotation(field.getMessageType(), ext, recursive);
    +814          if (subresult.isPresent())
    +815            return subresult;
    +816        }
    +817      }
    +818    }
    +819    return Optional.empty();
    +820  }
    +821
    +822  // -- Metadata: Field Annotations -- //
    +823
    +824  /**
    +825   * Resolve a {@link FieldPointer} within the scope of {@code instance}, that holds values for the specified metadata
    +826   * annotation {@code ext}. By default, this method searches recursively.
    +827   *
    +828   * @see #annotatedField(Descriptor, GeneratedExtension) variant if a descriptor is on-hand
    +829   * @see #annotatedField(Descriptor, GeneratedExtension, Boolean, Optional) full-spec variant.
    +830   * @param instance Model instance to search for the specified annotated field on.
    +831   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +832   * @param <E> Extension generic type.
    +833   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +834   */
    +835  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Message instance,
    +836                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext) {
    +837    return annotatedField(instance, ext, true);
    +838  }
    +839
    +840  /**
    +841   * Resolve a {@link FieldPointer} within the scope of {@code instance}, that holds values for the specified metadata
    +842   * annotation {@code ext}.
    +843   *
    +844   * <p>This method variant also allows specifying a <b>recursive</b> flag, which, if specified, causes the search to
    +845   * proceed to sub-models (recursively) until a matching field is found. If <b>recursive</b> is passed as {@code false}
    +846   * then the search will only occur at the top-level of {@code instance}.</p>
    +847   *
    +848   * @see #annotatedField(Message, GeneratedExtension, Boolean, Optional) Variant that supports a filter
    +849   * @see #annotatedField(Descriptor, GeneratedExtension, Boolean, Optional) full-spec variant.
    +850   * @param instance Model instance to search for the specified annotated field on.
    +851   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +852   * @param recursive Whether to conduct this search recursively, or just at the top-level.
    +853   * @param <E> Extension generic type.
    +854   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +855   */
    +856  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Message instance,
    +857                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext,
    +858                                                                   @Nonnull Boolean recursive) {
    +859    return annotatedField(instance, ext, recursive, Optional.empty());
    +860  }
    +861
    +862  /**
    +863   * Resolve a {@link FieldPointer} within the scope of {@code instance}, that holds values for the specified metadata
    +864   * annotation {@code ext}.
    +865   *
    +866   * <p>This method variant also allows specifying a <b>filter</b>, which will be run for each property encountered with
    +867   * the annotation present. If the filter returns {@code true}, the field will be selected, otherwise, the search
    +868   * continues until all properties are exhausted (depending on {@code recursive}).</p>
    +869   *
    +870   * @param instance Model instance to search for the specified annotated field on.
    +871   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +872   * @param recursive Whether to conduct this search recursively, or just at the top-level.
    +873   * @param <E> Extension generic type.
    +874   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +875   */
    +876  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Message instance,
    +877                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext,
    +878                                                                   @Nonnull Boolean recursive,
    +879                                                                   @Nonnull Optional<Function<E, Boolean>> filter) {
    +880    return annotatedField(instance.getDescriptorForType(), ext, recursive, filter);
    +881  }
    +882
    +883  /**
    +884   * Resolve a {@link FieldPointer} within the scope of the provided model {@code descriptor}, that holds values for the
    +885   * specified metadata annotation {@code ext}. By default, this search occurs recursively, examining all nested sub-
    +886   * models on the provided instance.
    +887   *
    +888   * @param descriptor Model object descriptor to search for the specified annotated field on.
    +889   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +890   * @param <E> Extension generic type.
    +891   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +892   */
    +893  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Descriptor descriptor,
    +894                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext) {
    +895    return annotatedField(descriptor, ext, Optional.empty());
    +896  }
    +897
    +898  /**
    +899   * Resolve a {@link FieldPointer} within the scope of the provided model {@code descriptor}, that holds values for the
    +900   * specified metadata annotation {@code ext}. By default, this search occurs recursively, examining all nested sub-
    +901   * models on the provided instance.
    +902   *
    +903   * <p>This method variant also allows specifying a <b>filter</b>, which will be run for each property encountered with
    +904   * the annotation present. If the filter returns {@code true}, the field will be selected, otherwise, the search
    +905   * continues until all properties are exhausted (depending on {@code recursive}).</p>
    +906   *
    +907   * @param descriptor Model object descriptor to search for the specified annotated field on.
    +908   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +909   * @param <E> Extension generic type.
    +910   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +911   */
    +912  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Descriptor descriptor,
    +913                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext,
    +914                                                                   @Nonnull Optional<Function<E, Boolean>> filter) {
    +915    return annotatedField(descriptor, ext, true, filter);
    +916  }
    +917
    +918  /**
    +919   * Resolve a {@link FieldPointer} within the scope of the provided model {@code descriptor}, that holds values for the
    +920   * specified metadata annotation {@code ext}. Using the {@code recursive} parameter, the invoking developer may opt to
    +921   * search for the annotated field recursively.
    +922   *
    +923   * <p>This method variant also allows specifying a <b>filter</b>, which will be run for each property encountered with
    +924   * the annotation present. If the filter returns {@code true}, the field will be selected, otherwise, the search
    +925   * continues until all properties are exhausted (depending on {@code recursive}).</p>
    +926   *
    +927   * @param descriptor Model object descriptor to search for the specified annotated field on.
    +928   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +929   * @param <E> Extension generic type.
    +930   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +931   */
    +932  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Descriptor descriptor,
    +933                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext,
    +934                                                                   @Nonnull Boolean recursive,
    +935                                                                   @Nonnull Optional<Function<E, Boolean>> filter) {
    +936    return resolveAnnotatedField(descriptor, ext, recursive, filter, "");
    +937  }
    +938
    +939  /**
    +940   * Retrieve a field-level annotation, from the provided field schema {@code descriptor}, structured by {@code ext}. If
    +941   * no instance of the requested field annotation can be found, {@link Optional#empty()} is returned.
    +942   *
    +943   * @param descriptor Schema descriptor for a field on a model type.
    +944   * @param ext Extension to fetch from the subject field.
    +945   * @param <E> Generic type of extension we are looking for.
    +946   * @return Optional, either {@link Optional#empty()}, or wrapping the found extension data instance.
    +947   */
    +948  public static @Nonnull <E> Optional<E> fieldAnnotation(@Nonnull FieldDescriptor descriptor,
    +949                                                         @Nonnull GeneratedExtension<FieldOptions, E> ext) {
    +950    Objects.requireNonNull(descriptor, "Cannot resolve type for `null` field descriptor.");
    +951    if (descriptor.getOptions().hasExtension(ext))
    +952      return Optional.of(descriptor.getOptions().getExtension(ext));
    +953    return Optional.empty();
    +954  }
    +955
    +956  // -- Metadata: ID Fields -- //
    +957
    +958  /**
    +959   * Resolve a pointer to the provided model {@code instance}'s ID field, whether or not it has a value. If there is no
    +960   * ID-annotated field at all, {@link Optional#empty()} is returned. Alternatively, if the model is not compatible with
    +961   * ID fields, an exception is raised (see below).
    +962   *
    +963   * @param instance Model instance for which an ID field is being resolved.
    +964   * @return Optional, either {@link Optional#empty()} or containing a {@link FieldPointer} to the resolved ID field.
    +965   * @throws InvalidModelType If the specified model does not support IDs. Only objects of type {@code OBJECT} can be
    +966   *         used with this interface.
    +967   */
    +968  public static @Nonnull Optional<FieldPointer> idField(@Nonnull Message instance) throws InvalidModelType {
    +969    return idField(instance.getDescriptorForType());
    +970  }
    +971
    +972  /**
    +973   * Resolve a pointer to the provided schema type {@code descriptor}'s ID field, whether or not it has a value. If
    +974   * there is no ID-annotated field at all, {@link Optional#empty()} is returned. Alternatively, if the model is not
    +975   * compatible with ID fields, an exception is raised (see below).
    +976   *
    +977   * @param descriptor Model instance for which an ID field is being resolved.
    +978   * @return Optional, either {@link Optional#empty()} or containing a {@link FieldPointer} to the resolved ID field.
    +979   * @throws InvalidModelType If the specified model does not support IDs. Only objects of type {@code OBJECT} can be
    +980   *         used with this interface.
    +981   */
    +982  public static @Nonnull Optional<FieldPointer> idField(@Nonnull Descriptor descriptor) throws InvalidModelType {
    +983    enforceAnyRole(Objects.requireNonNull(descriptor), DatapointType.OBJECT, DatapointType.OBJECT_KEY);
    +984    var topLevelId = Objects.requireNonNull(annotatedField(
    +985      descriptor,
    +986      Datamodel.field,
    +987      false,
    +988      Optional.of((field) -> field.getType() == FieldType.ID)));
    +989
    +990    if (topLevelId.isPresent()) {
    +991      return topLevelId;
    +992    } else {
    +993      // okay. no top level ID. what about keys, which must be top-level?
    +994      var keyBase = keyField(descriptor);
    +995      if (keyBase.isPresent()) {
    +996        // we found a key, so scan the key for an ID, which is required on keys.
    +997        return Objects.requireNonNull(resolveAnnotatedField(
    +998          keyBase.get().field.getMessageType(),
    +999          Datamodel.field,
    +1000          false,
    +1001          Optional.of((field) -> field.getType() == FieldType.ID),
    +1002          keyBase.get().getField().getName()));
    +1003      }
    +1004    }
    +1005    // there's no top-level ID, and no top-level key, or the key had no ID. we're done here.
    +1006    return Optional.empty();
    +1007  }
    +1008
    +1009  // -- Metadata: Key Fields -- //
    +1010
    +1011  /**
    +1012   * Resolve a pointer to the provided schema type {@code descriptor}'s {@code KEY} field, whether or not it has a value
    +1013   * assigned. If there is no key-annotated field at all, {@link Optional#empty()} is returned. Alternatively, if the
    +1014   * model is not compatible with key fields, an exception is raised (see below).
    +1015   *
    +1016   * @param instance Model instance for which a key field is being resolved.
    +1017   * @return Optional, either {@link Optional#empty()} or containing a {@link FieldPointer} to the resolved key field.
    +1018   * @throws InvalidModelType If the specified model does not support keys. Only objects of type {@code OBJECT} can be
    +1019   *         used with this interface.
    +1020   */
    +1021  public static @Nonnull Optional<FieldPointer> keyField(@Nonnull Message instance) throws InvalidModelType {
    +1022    return keyField(instance.getDescriptorForType());
    +1023  }
    +1024
    +1025  /**
    +1026   * Resolve a pointer to the provided schema type {@code descriptor}'s {@code KEY} field, whether or not it has a value
    +1027   * assigned. If there is no key-annotated field at all, {@link Optional#empty()} is returned. Alternatively, if the
    +1028   * model is not compatible with key fields, an exception is raised (see below).
    +1029   *
    +1030   * @param descriptor Model type descriptor for which a key field is being resolved.
    +1031   * @return Optional, either {@link Optional#empty()} or containing a {@link FieldPointer} to the resolved key field.
    +1032   * @throws InvalidModelType If the specified model does not support keys. Only objects of type {@code OBJECT} can be
    +1033   *         used with this interface.
    +1034   */
    +1035  public static @Nonnull Optional<FieldPointer> keyField(@Nonnull Descriptor descriptor) throws InvalidModelType {
    +1036    enforceAnyRole(Objects.requireNonNull(descriptor), DatapointType.OBJECT);
    +1037    return Objects.requireNonNull(annotatedField(
    +1038      Objects.requireNonNull(descriptor),
    +1039      Datamodel.field,
    +1040      false,
    +1041      Optional.of((field) -> field.getType() == FieldType.KEY)));
    +1042  }
    +1043
    +1044  // -- Metadata: Value Pluck -- //
    +1045
    +1046  /**
    +1047   * Pluck a field value, addressed by a {@link FieldPointer}, from the provided {@code instance}. If the referenced
    +1048   * field is a message, a message instance will be handed back only if there is an initialized value. Leaf fields
    +1049   * return their raw value, if set. In all cases, if there is no initialized value, {@link Optional#empty()} is
    +1050   * returned.
    +1051   *
    +1052   * @param instance Model instance from which to pluck the property.
    +1053   * @param fieldPointer Pointer to the field we wish to fetch.
    +1054   * @param <V> Generic type of data returned by this operation.
    +1055   * @return Optional wrapping the resolved value, or {@link Optional#empty()} if no value could be resolved.
    +1056   * @throws IllegalStateException If the referenced property is not found, despite witnessing matching types.
    +1057   * @throws IllegalArgumentException If the specified field does not have a matching base type with {@code instance}.
    +1058   */
    +1059  public static @Nonnull <V> FieldContainer<V> pluck(@Nonnull Message instance, @Nonnull FieldPointer fieldPointer) {
    +1060    return pluck(instance, fieldPointer.path);
    +1061  }
    +1062
    +1063  /**
    +1064   * Return a single field value container, plucked from the specified deep {@code path}, in dot form, using the regular
    +1065   * protobuf-definition names for each field. If a referenced field is a message, a message instance will be returned
    +1066   * only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no
    +1067   * initialized value, {@link Optional#empty()} is supplied in place.
    +1068   *
    +1069   * @param instance Model instance to pluck the specified property from.
    +1070   * @param path Deep path for the property value we wish to pluck.
    +1071   * @param <V> Expected type for the property. If types do not match, a {@link ClassCastException} will be raised.
    +1072   * @return Field container, either empty, or containing the plucked value.
    +1073   * @throws IllegalArgumentException If the provided path is syntactically invalid, or the field does not exist.
    +1074   */
    +1075  public static @Nonnull <V> FieldContainer<V> pluck(@Nonnull Message instance, @Nonnull String path) {
    +1076    return pluckFieldRecursive(instance, instance, path, path);
    +1077  }
    +1078
    +1079  /**
    +1080   * Return an iterable containing plucked value containers for each field mentioned in {@code mask}, that is present on
    +1081   * {@code instance} with an initialized value. If a referenced field is a message, a message instance will be included
    +1082   * only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no
    +1083   * initialized value, {@link Optional#empty()} is supplied in place.
    +1084   *
    +1085   * <p>If a field cannot be found, {@link Optional#empty()} is supplied in its place, so that the output order matches
    +1086   * path iteration order on the supplied {@code mask}. This method is therefore safe with regard to path access.</p>
    +1087   *
    +1088   * @param instance Model instance to pluck the specified properties from.
    +1089   * @param mask Mask of properties to pluck from the model instance.
    +1090   * @return Stream which emits each field container, with a generic {@code Object} for each value.
    +1091   */
    +1092  public static @Nonnull SortedSet<FieldContainer<Object>> pluckAll(@Nonnull Message instance, @Nonnull FieldMask mask) {
    +1093    return pluckAll(instance, mask, true);
    +1094  }
    +1095
    +1096  /**
    +1097   * Return an iterable containing plucked value containers for each field mentioned in {@code mask}, that is present on
    +1098   * {@code instance} with an initialized value. If a referenced field is a message, a message instance will be included
    +1099   * only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no
    +1100   * initialized value, {@link Optional#empty()} is supplied in place.
    +1101   *
    +1102   * <p>If a field cannot be found, {@link Optional#empty()} is supplied in its place, so that the output order matches
    +1103   * path iteration order on the supplied {@code mask}. This method is therefore safe with regard to path access. If
    +1104   * {@code normalize} is activated (the default for {@link #pluckAll(Message, FieldMask)}), the field mask will be
    +1105   * sorted and de-duplicated before processing.</p>
    +1106   *
    +1107   * <p>Sort order of the return value is based on the full path of properties selected - i.e. field containers are
    +1108   * returned in lexicographic sort order matching their underlying property paths.</p>
    +1109   *
    +1110   * @param instance Model instance to pluck the specified properties from.
    +1111   * @param mask Mask of properties to pluck from the model instance.
    +1112   * @param normalize Whether to normalize the field mask before plucking fields.
    +1113   * @return Stream which emits each field container, with a generic {@code Object} for each value.
    +1114   */
    +1115  public static @Nonnull SortedSet<FieldContainer<Object>> pluckAll(@Nonnull Message instance,
    +1116                                                                    @Nonnull FieldMask mask,
    +1117                                                                    @Nonnull Boolean normalize) {
    +1118    return ImmutableSortedSet.copyOfSorted(pluckStream(instance, mask, normalize)
    +1119      .collect(Collectors.toCollection(ConcurrentSkipListSet::new)));
    +1120  }
    +1121
    +1122  /**
    +1123   * Return a stream which emits plucked value containers for each field mentioned in {@code mask}, that is present on
    +1124   * {@code instance} with an initialized value. If a referenced field is a message, a message instance will be emitted
    +1125   * only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no
    +1126   * initialized value, {@link Optional#empty()} is supplied in place.
    +1127   *
    +1128   * <p>If a field cannot be found, {@link Optional#empty()} is supplied in its place, so that the output order matches
    +1129   * path iteration order on the supplied {@code mask}. This method is therefore safe with regard to path access.</p>
    +1130   *
    +1131   * <p><b>Performance note:</b> the {@link Stream} returned by this method is explicitly parallel-capable, because
    +1132   * reading descriptor schema is safely concurrent.</p>
    +1133   *
    +1134   * @param instance Model instance to pluck the specified properties from.
    +1135   * @param mask Mask of properties to pluck from the model instance.
    +1136   * @return Stream which emits each field container, with a generic {@code Object} for each value.
    +1137   */
    +1138  public static @Nonnull Stream<FieldContainer<Object>> pluckStream(@Nonnull Message instance,
    +1139                                                                    @Nonnull FieldMask mask) {
    +1140    return pluckStream(instance, mask, true);
    +1141  }
    +1142
    +1143  /**
    +1144   * Return a stream which emits plucked value containers for each field mentioned in {@code mask}, that is present on
    +1145   * {@code instance} with an initialized value. If a referenced field is a message, a message instance will be emitted
    +1146   * only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no
    +1147   * initialized value, {@link Optional#empty()} is supplied in place.
    +1148   *
    +1149   * <p>If a field cannot be found, {@link Optional#empty()} is supplied in its place, so that the output order matches
    +1150   * path iteration order on the supplied {@code mask}. This method is therefore safe with regard to path access. If
    +1151   * {@code normalize} is activated (the default for {@link #pluckStream(Message, FieldMask)}), the field mask will be
    +1152   * sorted and de-duplicated before processing.</p>
    +1153   *
    +1154   * <p><b>Performance note:</b> the {@link Stream} returned by this method is explicitly parallel-capable, because
    +1155   * reading descriptor schema is safely concurrent.</p>
    +1156   *
    +1157   * @param instance Model instance to pluck the specified properties from.
    +1158   * @param mask Mask of properties to pluck from the model instance.
    +1159   * @param normalize Whether to normalize the field mask before plucking fields.
    +1160   * @return Stream which emits each field container, with a generic {@code Object} for each value.
    +1161   */
    +1162  public static @Nonnull Stream<FieldContainer<Object>> pluckStream(@Nonnull Message instance,
    +1163                                                                    @Nonnull FieldMask mask,
    +1164                                                                    @Nonnull Boolean normalize) {
    +1165    return (new TreeSet<>((normalize ? FieldMaskUtil.normalize(mask) : mask).getPathsList()))
    +1166      .parallelStream()
    +1167      .map((fieldPath) -> pluck(instance, fieldPath));
    +1168  }
    +1169
    +1170  // -- Metadata: ID/Key Value Pluck -- //
    +1171
    +1172  /**
    +1173   * Resolve the provided model instance's assigned ID, by walking the property structure for the entity, and returning
    +1174   * either the first {@code id}-annotated field's value at the top-level, or the first {@code id}-annotated field value
    +1175   * on the first {@code key}-annotated message field at the top level of the provided message.
    +1176   *
    +1177   * <p>If no ID field <i>value</i> can be resolved, {@link Optional#empty()} is returned. On the other hand, if the
    +1178   * model is not a business object or does not have an ID annotation at all, an exception is raised (see below).</p>
    +1179   *
    +1180   * @param <ID> Type for the ID value we are resolving.
    +1181   * @param instance Model instance for which an ID value is desired.
    +1182   * @return Optional wrapping the value of the model instance's ID, or an empty optional if no value could be resolved.
    +1183   * @throws InvalidModelType If the supplied model is not a business object and/or does not have an ID field at all.
    +1184   */
    +1185  public static @Nonnull <ID> Optional<ID> id(@Nonnull Message instance) {
    +1186    var descriptor = instance.getDescriptorForType();
    +1187    enforceAnyRole(descriptor, DatapointType.OBJECT, DatapointType.OBJECT_KEY);
    +1188    Optional<FieldPointer> idField = idField(descriptor);
    +1189    if (idField.isEmpty())
    +1190      throw new MissingAnnotatedField(descriptor, FieldType.ID);
    +1191    return ModelMetadata.<ID>pluck(instance, idField.get()).getValue();
    +1192  }
    +1193
    +1194  /**
    +1195   * Resolve the provided model instance's assigned {@code KEY} instance, by walking the property structure for the
    +1196   * entity, and returning the first {@code key}-annotated field's value at the top-level of the provided message.
    +1197   *
    +1198   * <p>If no key field <i>value</i> can be resolved, {@link Optional#empty()} is returned. On the other hand, if the
    +1199   * model is not a business object or does not support key annotations at all, an exception is raised (see below).</p>
    +1200   *
    +1201   * @param <Key> Type for the key we are resolving.
    +1202   * @param instance Model instance for which an key value is desired.
    +1203   * @return Optional wrapping the value of the model instance's key, or an empty optional if none could be resolved.
    +1204   * @throws InvalidModelType If the supplied model is not a business object and/or does not have an key field at all.
    +1205   */
    +1206  public static @Nonnull <Key> Optional<Key> key(@Nonnull Message instance) {
    +1207    Descriptor descriptor = instance.getDescriptorForType();
    +1208    enforceRole(descriptor, DatapointType.OBJECT);
    +1209    Optional<FieldPointer> keyField = annotatedField(
    +1210      descriptor,
    +1211      Datamodel.field,
    +1212      false,
    +1213      Optional.of((field) -> field.getType() == FieldType.KEY));
    +1214
    +1215    if (keyField.isEmpty())
    +1216      throw new MissingAnnotatedField(descriptor, FieldType.KEY);
    +1217    //noinspection unchecked
    +1218    return (Optional<Key>)pluck(instance, keyField.get()).getValue();
    +1219  }
    +1220
    +1221  // -- Metadata: Value Splice -- //
    +1222
    +1223  /**
    +1224   * Splice the provided optional value (or clear any existing value) at the field {@code path} in the provided model
    +1225   * {@code instance}. Return a re-built message after the splice.
    +1226   *
    +1227   * <p>If {@link Optional#empty()} is passed for the {@code value} to set, any existing value placed in that field
    +1228   * will be cleared. This method works identically for primitive leaf fields and message fields.</p>
    +1229   *
    +1230   * @param instance Model instance to splice the value into.
    +1231   * @param path Deep path at which to splice the value.
    +1232   * @param val Value to splice into the model, or {@link Optional#empty()} to clear any existing value.
    +1233   * @param <Model> Model type which we are working with for this splice operation.
    +1234   * @param <Value> Value type which we are splicing in, if applicable.
    +1235   * @return Re-built model, after the splice operation.
    +1236   */
    +1237  public static @Nonnull <Model extends Message, Value> Model splice(@Nonnull Message instance,
    +1238                                                                     @Nonnull String path,
    +1239                                                                     @Nonnull Optional<Value> val) {
    +1240
    +1241    return splice(
    +1242      instance,
    +1243      resolveField(instance, path)
    +1244        .orElseThrow(() -> new IllegalArgumentException(String.format(
    +1245          "Failed to resolve path '%s' on model instance of type '%s' for value splice.",
    +1246          path,
    +1247          instance.getDescriptorForType().getName()))),
    +1248      val);
    +1249  }
    +1250
    +1251  /**
    +1252   * Splice the provided optional value (or clear any existing value) at the specified {@code field} pointer, in the
    +1253   * provided model {@code instance}. Return a re-built message after the splice.
    +1254   *
    +1255   * <p>If {@link Optional#empty()} is passed for the {@code value} to set, any existing value placed in that field
    +1256   * will be cleared. This method works identically for primitive leaf fields and message fields.</p>
    +1257   *
    +1258   * @param instance Model instance to splice the value into.
    +1259   * @param field Resolved and validated field pointer for the field to splice.
    +1260   * @param val Value to splice into the model, or {@link Optional#empty()} to clear any existing value.
    +1261   * @param <Model> Model type which we are working with for this splice operation.
    +1262   * @param <Value> Value type which we are splicing in, if applicable.
    +1263   * @return Re-built model, after the splice operation.
    +1264   */
    +1265  public static @Nonnull <Model extends Message, Value> Model splice(@Nonnull Message instance,
    +1266                                                                     @Nonnull FieldPointer field,
    +1267                                                                     @Nonnull Optional<Value> val) {
    +1268    //noinspection unchecked
    +1269    return (Model)spliceBuilder(instance.toBuilder(), field, val).build();
    +1270  }
    +1271
    +1272  /**
    +1273   * Splice the provided optional value (or clear any existing value) at the specified {@code field} pointer, in the
    +1274   * provided model {@code instance}. Return the provided builder after the splice operation. The return value may be
    +1275   * ignored if the developer so wishes (the provided {@code builder} is mutated in place).
    +1276   *
    +1277   * <p>If {@link Optional#empty()} is passed for the {@code value} to set, any existing value placed in that field
    +1278   * will be cleared. This method works identically for primitive leaf fields and message fields.</p>
    +1279   *
    +1280   * @param builder Model builder to splice the value into.
    +1281   * @param field Resolved and validated field pointer for the field to splice.
    +1282   * @param val Value to splice into the model, or {@link Optional#empty()} to clear any existing value.
    +1283   * @param <Builder> Model builder type which we are working with for this splice operation.
    +1284   * @param <Value> Value type which we are splicing in, if applicable.
    +1285   * @return Model {@code builder}, after the splice operation.
    +1286   */
    +1287  @CanIgnoreReturnValue
    +1288  public static @Nonnull <Builder extends Message.Builder, Value> Builder spliceBuilder(
    +1289    @Nonnull Message.Builder builder,
    +1290    @Nonnull FieldPointer field,
    +1291    @Nonnull Optional<Value> val) {
    +1292    var noPrefixPath = field.path.startsWith(".") ? field.path.substring(1) : field.path;
    +1293    return spliceArbitraryField(
    +1294      builder,
    +1295      builder,
    +1296      noPrefixPath,
    +1297      val,
    +1298      noPrefixPath
    +1299    );
    +1300  }
    +1301
    +1302  // -- Metadata: ID/Key Splice -- //
    +1303
    +1304  /**
    +1305   * Splice the provided value at {@code val}, into the ID field value for {@code instance}. If an ID-annotated property
    +1306   * cannot be located, or the model is not of a suitable/type role for use with IDs, an exception is raised (see below
    +1307   * for more info).
    +1308   *
    +1309   * <p>If an existing value exists for the model's ID, <b>it will be replaced</b>. In most object-based storage engines
    +1310   * this will end up copying the object, rather than mutating an ID. Be careful of this behavior. Passing
    +1311   * {@link Optional#empty()} will clear any existing ID on the model.</p>
    +1312   *
    +1313   * @param instance Model instance to splice the value into. Because models are immutable, this involves converting the
    +1314   *                 model to a builder, splicing in the value, and then re-building the model. As such, the model
    +1315   *                 returned will be a <i>different object instance</i>, but will otherwise be untouched.
    +1316   * @param val Value we should splice-into the ID field for the record. It is expected that the generic type of this
    +1317   *            value will line up with the ID field type, otherwise a {@link ClassCastException} will be thrown.
    +1318   * @param <Model> Type of model we are splicing an ID value into.
    +1319   * @param <Value> Type of ID value we are splicing into the model.
    +1320   * @return Model instance, rebuilt, after splicing in the provided value, at the model's ID-annotated field.
    +1321   * @throws InvalidModelType If the specified model is not suitable for use with IDs at all.
    +1322   * @throws ClassCastException If the {@code Value} generic type does not match the ID field primitive type.
    +1323   * @throws MissingAnnotatedField If the provided {@code instance} is not of the correct type, or has no ID field.
    +1324   */
    +1325  public static @Nonnull <Model extends Message, Value> Model spliceId(@Nonnull Message instance,
    +1326                                                                       @Nonnull Optional<Value> val) {
    +1327    //noinspection unchecked
    +1328    return (Model)spliceIdBuilder(instance.toBuilder(), val).build();
    +1329  }
    +1330
    +1331  /**
    +1332   * Splice the provided value at {@code val}, into the ID field value for the provided model {@code builder}. If an ID-
    +1333   * annotated property cannot be located, or the model is not of a suitable/type role for use with IDs, an exception is
    +1334   * raised (see below for more info).
    +1335   *
    +1336   * <p>If an existing value exists for the model's ID, <b>it will be replaced</b>. In most object-based storage engines
    +1337   * this will end up copying the object, rather than mutating an ID. Be careful of this behavior. Passing
    +1338   * {@link Optional#empty()} will clear any existing ID on the model.</p>
    +1339   *
    +1340   * @param builder Model instance builder to splice the value into. The builder provided is <i>mutated in place</i>, so
    +1341   *                it will be an identical object instance to the one provided, but with the ID property filled in.
    +1342   * @param val Value we should splice-into the ID field for the record. It is expected that the generic type of this
    +1343   *            value will line up with the ID field type, otherwise a {@link ClassCastException} will be thrown.
    +1344   * @param <Builder> Type of model builder we are splicing an ID value into.
    +1345   * @param <Value> Type of ID value we are splicing into the model.
    +1346   * @return Model builder, after splicing in the provided value, at the model's ID-annotated field.
    +1347   * @throws InvalidModelType If the specified model is not suitable for use with IDs at all.
    +1348   * @throws ClassCastException If the {@code Value} generic type does not match the ID field primitive type.
    +1349   * @throws MissingAnnotatedField If the provided {@code builder} is not of the correct type, or has no ID field.
    +1350   */
    +1351  public static @Nonnull <Builder extends Message.Builder, Value> Builder spliceIdBuilder(
    +1352    @Nonnull Message.Builder builder,
    +1353    @Nonnull Optional<Value> val) {
    +1354    // resolve descriptor and field
    +1355    if (val.isPresent() && val.get() instanceof Message)
    +1356      throw new IllegalArgumentException("Cannot set messages as ID values.");
    +1357    var descriptor = builder.getDescriptorForType();
    +1358    enforceAnyRole(descriptor, DatapointType.OBJECT, DatapointType.OBJECT_KEY);
    +1359    var fieldPath = idField(descriptor)
    +1360      .orElseThrow(() -> new MissingAnnotatedField(descriptor, FieldType.ID))
    +1361      .getPath();
    +1362
    +1363    return spliceArbitraryField(
    +1364      builder,
    +1365      builder,
    +1366      fieldPath,
    +1367      val,
    +1368      fieldPath);
    +1369  }
    +1370
    +1371  /**
    +1372   * Splice the provided value at {@code val}, into the key message value for {@code instance}. If a key-annotated
    +1373   * property cannot be located, or the model is not of a suitable/type role for use with keys, an exception is raised
    +1374   * (see below for more info).
    +1375   *
    +1376   * <p>If an existing value is set for the model's key, <b>it will be replaced</b>. In most object-based storage
    +1377   * engines this will end up copying the object, rather than mutating a key. Keys are usually immutable for this
    +1378   * reason, so use this method with care. Passing {@link Optional#empty()} will clear any existing key message
    +1379   * currently affixed to the model {@code instance}.</p>
    +1380   *
    +1381   * @param instance Model instance to splice the value into. Because models are immutable, this involves converting the
    +1382   *                 model to a builder, splicing in the value, and then re-building the model. As such, the model
    +1383   *                 returned will be a <i>different object instance</i>, but will otherwise be untouched.
    +1384   * @param val Value we should splice-into the ID field for the record. It is expected that the generic type of this
    +1385   *            value will line up with the ID field type, otherwise a {@link ClassCastException} will be thrown.
    +1386   * @param <Model> Type of model we are splicing an ID value into.
    +1387   * @param <Key> Type of key message we are splicing into the model.
    +1388   * @return Model instance, rebuilt, after splicing in the provided value, at the model's ID-annotated field.
    +1389   * @throws InvalidModelType If the specified model is not suitable for use with IDs at all.
    +1390   * @throws ClassCastException If the {@code Value} generic type does not match the ID field primitive type.
    +1391   * @throws MissingAnnotatedField If the provided {@code builder} is not of the correct type, or has no ID field.
    +1392   */
    +1393  public static @Nonnull <Model extends Message, Key extends Message> Model spliceKey(@Nonnull Message instance,
    +1394                                                                                      @Nonnull Optional<Key> val) {
    +1395    //noinspection unchecked
    +1396    return (Model)spliceKeyBuilder(instance.toBuilder(), val).build();
    +1397  }
    +1398
    +1399  /**
    +1400   * Splice the provided value at {@code val}, into the key message value for the supplied {@code builder}. If a
    +1401   * key-annotated property cannot be located, or the model is not of a suitable/type role for use with keys, an
    +1402   * exception is raised (see below for more info).
    +1403   *
    +1404   * <p>If an existing value is set for the model's key, <b>it will be replaced</b>. In most object-based storage
    +1405   * engines this will end up copying the object, rather than mutating a key. Keys are usually immutable for this
    +1406   * reason, so use this method with care. Passing {@link Optional#empty()} will clear any existing key message
    +1407   * currently affixed to the model {@code instance}.</p>
    +1408   *
    +1409   * @param builder Model instance builder to splice the value into. The builder provided is <i>mutated in place</i>, so
    +1410   *                it will be an identical object instance to the one provided, but with the key property filled in.
    +1411   * @param val Value we should splice-into the key field for the record. It is expected that the generic type of this
    +1412   *            value will line up with the key message type, otherwise a {@link ClassCastException} will be thrown.
    +1413   * @param <Builder> Type of model builder we are splicing a key value into.
    +1414   * @param <Key> Type of key message we are splicing into the model.
    +1415   * @return Model builder, after splicing in the provided message, at the model's key-annotated field.
    +1416   * @throws InvalidModelType If the specified model is not suitable for use with keys at all.
    +1417   * @throws ClassCastException If the {@code Value} generic type does not match the key field primitive type.
    +1418   * @throws MissingAnnotatedField If the provided {@code builder} is not of the correct type, or has no key field.
    +1419   */
    +1420  public static @Nonnull <Builder extends Message.Builder, Key extends Message> Builder spliceKeyBuilder(
    +1421    @Nonnull Message.Builder builder,
    +1422    @Nonnull Optional<Key> val) {
    +1423    // resolve descriptor and key message field
    +1424    var descriptor = builder.getDescriptorForType();
    +1425    enforceRole(descriptor, DatapointType.OBJECT);
    +1426    var fieldPath = keyField(descriptor)
    +1427      .orElseThrow(() -> new MissingAnnotatedField(descriptor, FieldType.KEY))
    +1428      .getPath();
    +1429
    +1430    return spliceArbitraryField(
    +1431      builder,
    +1432      builder,
    +1433      fieldPath,
    +1434      val,
    +1435      fieldPath);
    +1436  }
    +1437
    +1438  /**
    +1439   * Crawl all fields, recursively, on the descriptor provided. This data may also be accessed via a Java stream via the
    +1440   * method variants listed below. Variants of this method also allow predicate-based filtering or control of recursion.
    +1441   *
    +1442   * @see #allFields(Descriptor, Optional) to provide an optional filtering predicate.
    +1443   * @see #allFields(Descriptor, Optional, Boolean) to provide an optional predicate, and/or control recursion.
    +1444   *
    +1445   * @param descriptor Schema descriptor to crawl model definitions on.
    +1446   * @return Iterable of all fields, recursively, from the descriptor.
    +1447   */
    +1448  public static @Nonnull Iterable<FieldPointer> allFields(@Nonnull Descriptor descriptor) {
    +1449    return allFields(descriptor, Optional.empty(), true);
    +1450  }
    +1451
    +1452  /**
    +1453   * Crawl all fields, recursively, on the descriptor provided. For each field encountered, run `predicate` to determine
    +1454   * whether to include the field, filtering the returned iterable accordingly. This data may also be accessed via a
    +1455   * Java stream via the method variants listed below.
    +1456   *
    +1457   * @see #allFields(Descriptor, Optional, Boolean) to additionally control recursion.
    +1458   *
    +1459   * @param descriptor Schema descriptor to crawl model definitions on.
    +1460   * @param predicate Filter predicate function, if applicable.
    +1461   * @return Iterable of all fields, recursively, from the descriptor, filtered by `predicate`.
    +1462   */
    +1463  public static @Nonnull Iterable<FieldPointer> allFields(@Nonnull Descriptor descriptor,
    +1464                                                          @Nonnull Optional<Predicate<FieldPointer>> predicate) {
    +1465    return allFields(descriptor, predicate, true);
    +1466  }
    +1467
    +1468  /**
    +1469   * Crawl all fields, recursively, on the descriptor provided. For each field encountered, run `predicate` to determine
    +1470   * whether to include the field, filtering the returned iterable accordingly. This data may also be accessed via a
    +1471   * Java stream via the method variants listed below.
    +1472   *
    +1473   * @see #streamFields(Descriptor, Optional, Boolean) to access a stream of fields instead.
    +1474   *
    +1475   * @param descriptor Schema descriptor to crawl model definitions on.
    +1476   * @param predicate Filter predicate function, if applicable.
    +1477   * @return Iterable of all fields, optionally recursively, from the descriptor, filtered by `predicate`.
    +1478   */
    +1479  public static @Nonnull Iterable<FieldPointer> allFields(@Nonnull Descriptor descriptor,
    +1480                                                          @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1481                                                          @Nonnull Boolean recursive) {
    +1482    return streamFields(
    +1483      descriptor,
    +1484      predicate,
    +1485      recursive
    +1486    ).collect(Collectors.toUnmodifiableList());
    +1487  }
    +1488
    +1489  /**
    +1490   * Crawl all fields, recursively, on the descriptor provided. For each field encountered, run `predicate` to determine
    +1491   * whether to include the field, filtering the returned iterable accordingly. This data may also be accessed via a
    +1492   * Java stream via the method variants listed below.
    +1493   *
    +1494   * <p>If a `MESSAGE` field is encountered and the algorithm needs to decide whether to recurse, this variant includes
    +1495   * support for the `decider` function. `decider` is invoked to decide whether to recurse for opportunity to do so.</p>
    +1496   *
    +1497   * @see #streamFields(Descriptor, Optional, Boolean) to access a stream of fields instead.
    +1498   *
    +1499   * @param descriptor Schema descriptor to crawl model definitions on.
    +1500   * @param predicate Filter predicate function, if applicable.
    +1501   * @param decider Function which decides whether to recurse, for each opportunity to do so.
    +1502   * @return Iterable of all fields, optionally recursively, from the descriptor, filtered by `predicate`.
    +1503   */
    +1504  public static @Nonnull Iterable<FieldPointer> allFields(@Nonnull Descriptor descriptor,
    +1505                                                          @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1506                                                          @Nonnull Predicate<FieldPointer> decider) {
    +1507    return streamFields(
    +1508      descriptor,
    +1509      predicate,
    +1510      decider
    +1511    ).collect(Collectors.toUnmodifiableList());
    +1512  }
    +1513
    +1514  /**
    +1515   * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run
    +1516   * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. This
    +1517   * method variant runs each operation serially.
    +1518   *
    +1519   * <p>This method variant does not allow the invoking user to crawl recursively.</p>
    +1520   *
    +1521   * @see #streamFields(Descriptor) for the cleanest invocation of this method.
    +1522   *
    +1523   * @param descriptor Schema descriptor to crawl model definitions on.
    +1524   * @param predicate Filter predicate function, if applicable.
    +1525   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1526   */
    +1527  public static @Nonnull Stream<FieldPointer> forEachField(@Nonnull Descriptor descriptor,
    +1528                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate) {
    +1529    Objects.requireNonNull(descriptor);
    +1530    Objects.requireNonNull(predicate);
    +1531
    +1532    return streamFieldsRecursive(
    +1533            descriptor,
    +1534            descriptor,
    +1535            predicate,
    +1536            (field) -> false,
    +1537            "",
    +1538            false
    +1539    );
    +1540  }
    +1541
    +1542  /**
    +1543   * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run
    +1544   * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. This
    +1545   * method variant runs each operation serially.
    +1546   *
    +1547   * <p>This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search
    +1548   * is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate
    +1549   * is provided, the entire set of recursive effective fields is returned from the provided descriptor.</p>
    +1550   *
    +1551   * @see #streamFields(Descriptor) for the cleanest invocation of this method.
    +1552   *
    +1553   * @param descriptor Schema descriptor to crawl model definitions on.
    +1554   * @param predicate Filter predicate function, if applicable.
    +1555   * @param recursive Whether to perform recursion down to sub-messages.
    +1556   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1557   */
    +1558  public static @Nonnull Stream<FieldPointer> forEachField(@Nonnull Descriptor descriptor,
    +1559                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1560                                                           boolean recursive) {
    +1561    Objects.requireNonNull(descriptor);
    +1562    Objects.requireNonNull(predicate);
    +1563
    +1564    return streamFieldsRecursive(
    +1565            descriptor,
    +1566            descriptor,
    +1567            predicate,
    +1568            (field) -> recursive,
    +1569            "",
    +1570            false
    +1571    );
    +1572  }
    +1573
    +1574  /**
    +1575   * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run
    +1576   * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. This
    +1577   * method variant runs each operation serially.
    +1578   *
    +1579   * <p>If a `MESSAGE` field is encountered and the algorithm needs to decide whether to recurse, this variant includes
    +1580   * support for the `decider` function. `decider` is invoked to decide whether to recurse for opportunity to do so.</p>
    +1581   *
    +1582   * <p>This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search
    +1583   * is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate
    +1584   * is provided, the entire set of recursive effective fields is returned from the provided descriptor.</p>
    +1585   *
    +1586   * @see #streamFields(Descriptor) for the cleanest invocation of this method.
    +1587   *
    +1588   * @param descriptor Schema descriptor to crawl model definitions on.
    +1589   * @param predicate Filter predicate function, if applicable.
    +1590   * @param decider Function that decides whether to recurse.
    +1591   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1592   */
    +1593  public static @Nonnull Stream<FieldPointer> forEachField(@Nonnull Descriptor descriptor,
    +1594                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1595                                                           @Nonnull Predicate<FieldPointer> decider) {
    +1596    Objects.requireNonNull(descriptor);
    +1597    Objects.requireNonNull(predicate);
    +1598
    +1599    return streamFieldsRecursive(
    +1600            descriptor,
    +1601            descriptor,
    +1602            predicate,
    +1603            decider,
    +1604            "",
    +1605            false
    +1606    );
    +1607  }
    +1608
    +1609  /**
    +1610   * Crawl all fields, recursively, on the descriptor associated with the provided model instance, and return them in
    +1611   * a stream.
    +1612   *
    +1613   * <p>This method crawls recursively by default, but this behavior can be customized via the alternate method variants
    +1614   * listed below. Other variants also allow applying a predicate to filter the returned fields.</p>
    +1615   *
    +1616   * @see #streamFields(Descriptor, Optional) for the opportunity to provide a filter predicate.
    +1617   * @see #streamFields(Descriptor, Optional, Boolean) for the opportunity to control recursive crawling, and provide a
    +1618   *      filter predicate.
    +1619   *
    +1620   * @param descriptor Schema descriptor to crawl model definitions on.
    +1621   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1622   */
    +1623  public static @Nonnull <M extends Message> Stream<FieldPointer> streamFields(@Nonnull Descriptor descriptor) {
    +1624    return streamFields(descriptor, Optional.empty());
    +1625  }
    +1626
    +1627  /**
    +1628   * Crawl all fields, recursively, on the descriptor associated with the provided model instance. For each field
    +1629   * encountered, run `predicate` to determine whether to include the field, filtering the returned stream of fields
    +1630   * accordingly.
    +1631   *
    +1632   * <p>This method crawls recursively by default, but this behavior can be customized via the alternate method variants
    +1633   * listed below.</p>
    +1634   *
    +1635   * @see #streamFields(Descriptor, Optional, Boolean) for the opportunity to control recursive crawling.
    +1636   *
    +1637   * @param descriptor Schema descriptor to crawl model definitions on.
    +1638   * @param predicate Filter predicate function, if applicable.
    +1639   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1640   */
    +1641  public static @Nonnull Stream<FieldPointer> streamFields(@Nonnull Descriptor descriptor,
    +1642                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate) {
    +1643    return streamFields(descriptor, predicate, true);
    +1644  }
    +1645
    +1646  /**
    +1647   * Crawl all fields, recursively, on the descriptor associated with the provided model instance. For each field
    +1648   * encountered, run `predicate` to determine whether to include the field, filtering the returned stream of fields
    +1649   * accordingly. In this case, `predicate` is required.
    +1650   *
    +1651   * <p>This method crawls recursively by default, but this behavior can be customized via the alternate method variants
    +1652   * listed below.</p>
    +1653   *
    +1654   * @see #streamFields(Descriptor, Optional, Boolean) for the opportunity to control recursive crawling.
    +1655   *
    +1656   * @param descriptor Schema descriptor to crawl model definitions on.
    +1657   * @param predicate Filter predicate function, if applicable.
    +1658   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1659   */
    +1660  public static @Nonnull Stream<FieldPointer> streamFields(@Nonnull Descriptor descriptor,
    +1661                                                           @Nonnull Predicate<FieldPointer> predicate) {
    +1662    return streamFields(descriptor, Optional.of(predicate), true);
    +1663  }
    +1664
    +1665  /**
    +1666   * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run
    +1667   * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly.
    +1668   *
    +1669   * <p>This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search
    +1670   * is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate
    +1671   * is provided, the entire set of recursive effective fields is returned from the provided descriptor.</p>
    +1672   *
    +1673   * @see #streamFields(Descriptor) for the cleanest invocation of this method.
    +1674   *
    +1675   * @param descriptor Schema descriptor to crawl model definitions on.
    +1676   * @param predicate Filter predicate function, if applicable.
    +1677   * @param recursive Whether to descend to sub-models recursively.
    +1678   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1679   */
    +1680  public static @Nonnull Stream<FieldPointer> streamFields(@Nonnull Descriptor descriptor,
    +1681                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1682                                                           @Nonnull Boolean recursive) {
    +1683    Objects.requireNonNull(recursive, "cannot pass `null` for recursive switch");
    +1684
    +1685    return streamFields(
    +1686      descriptor,
    +1687      predicate,
    +1688      (field) -> recursive
    +1689    );
    +1690  }
    +1691
    +1692  /**
    +1693   * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run
    +1694   * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. By
    +1695   * default, all field streaming methods run in parallel.
    +1696   *
    +1697   * <p>If a `MESSAGE` field is encountered and the algorithm needs to decide whether to recurse, this variant includes
    +1698   * support for the `decider` function. `decider` is invoked to decide whether to recurse for opportunity to do so.</p>
    +1699   *
    +1700   * <p>This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search
    +1701   * is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate
    +1702   * is provided, the entire set of recursive effective fields is returned from the provided descriptor.</p>
    +1703   *
    +1704   * @see #streamFields(Descriptor) for the cleanest invocation of this method.
    +1705   *
    +1706   * @param descriptor Schema descriptor to crawl model definitions on.
    +1707   * @param predicate Filter predicate function, if applicable.
    +1708   * @param decider Function that decides whether to recurse.
    +1709   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1710   */
    +1711  public static @Nonnull Stream<FieldPointer> streamFields(@Nonnull Descriptor descriptor,
    +1712                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1713                                                           @Nonnull Predicate<FieldPointer> decider) {
    +1714    Objects.requireNonNull(descriptor);
    +1715    Objects.requireNonNull(predicate);
    +1716
    +1717    return streamFieldsRecursive(
    +1718      descriptor,
    +1719      descriptor,
    +1720      predicate,
    +1721      decider,
    +1722      "",
    +1723      true
    +1724    );
    +1725  }
    +1726
    +1727  private static @Nonnull Stream<FieldPointer> streamFieldsRecursive(
    +1728    @Nonnull Descriptor base,
    +1729    @Nonnull Descriptor descriptor,
    +1730    @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1731    @Nonnull Predicate<FieldPointer> decider,
    +1732    @Nonnull String parent,
    +1733    @Nonnull Boolean parallel) {
    +1734    return (parallel ? descriptor.getFields().parallelStream() : descriptor.getFields().stream()).flatMap((field) -> {
    +1735      var path = String.format("%s.%s", parent, field.getName());
    +1736      var pointer = new FieldPointer(
    +1737        base,
    +1738        parent,
    +1739        path,
    +1740        field
    +1741      );
    +1742
    +1743      var branch = Stream.of(pointer);
    +1744      if (field.getType() == FieldDescriptor.Type.MESSAGE
    +1745          && !field.getMessageType().getFullName().equals(field.getContainingType().getFullName())
    +1746          && decider.test(pointer)) {
    +1747        return Stream.concat(branch, streamFieldsRecursive(
    +1748          base,
    +1749          descriptor.findFieldByNumber(field.getNumber()).getMessageType(),
    +1750          predicate,
    +1751          decider,
    +1752          path,
    +1753          parallel
    +1754        ));
    +1755      }
    +1756      return branch;
    +1757
    +1758    }).filter((field) ->
    +1759      predicate.map(fieldDescriptorPredicate ->
    +1760        fieldDescriptorPredicate.test(field)).orElse(true)
    +1761
    +1762    );
    +1763  }
    +1764}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/ModelMetadata.html b/docs/java/src-html/gust/backend/model/ModelMetadata.html new file mode 100644 index 000000000..12edad7df --- /dev/null +++ b/docs/java/src-html/gust/backend/model/ModelMetadata.html @@ -0,0 +1,1838 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.common.annotations.VisibleForTesting;
    +016import com.google.common.base.CharMatcher;
    +017import com.google.common.collect.ImmutableSortedSet;
    +018import com.google.errorprone.annotations.CanIgnoreReturnValue;
    +019import com.google.protobuf.DescriptorProtos.FieldOptions;
    +020import com.google.protobuf.DescriptorProtos.MessageOptions;
    +021import com.google.protobuf.Descriptors.Descriptor;
    +022import com.google.protobuf.Descriptors.FieldDescriptor;
    +023import com.google.protobuf.FieldMask;
    +024import com.google.protobuf.GeneratedMessage.GeneratedExtension;
    +025import com.google.protobuf.Message;
    +026import com.google.protobuf.util.FieldMaskUtil;
    +027import tools.elide.core.CollectionMode;
    +028import tools.elide.core.Datamodel;
    +029import tools.elide.core.DatapointType;
    +030import tools.elide.core.FieldType;
    +031
    +032import javax.annotation.Nonnull;
    +033import javax.annotation.concurrent.Immutable;
    +034import javax.annotation.concurrent.ThreadSafe;
    +035import java.io.Serializable;
    +036import java.util.*;
    +037import java.util.concurrent.ConcurrentSkipListSet;
    +038import java.util.function.Function;
    +039import java.util.function.Predicate;
    +040import java.util.stream.Collectors;
    +041import java.util.stream.Stream;
    +042
    +043
    +044/**
    +045 * Utility helper class, which is responsible for resolving metadata (based on the core framework annotations) from
    +046 * arbitrary model definitions.
    +047 *
    +048 * <p>Model "metadata," in this case, refers to annotation-based declarations on the protocol buffer definitions
    +049 * themselves. As such, the source for most (if not all) of the data provided by this helper is the {@link Descriptor}
    +050 * that accompanies a Java-side protobuf model.</p>
    +051 *
    +052 * <p><b>Note:</b> Using this class, or the model layer writ-large, requires the full runtime Protobuf library (the lite
    +053 * runtime for Protobuf in Java does not include descriptors at all, which this class relies on).</p>
    +054 */
    +055@ThreadSafe
    +056@SuppressWarnings({"WeakerAccess", "unused", "OptionalUsedAsFieldOrParameterType"})
    +057public final class ModelMetadata {
    +058  private ModelMetadata() { /* Disallow construction. */ }
    +059
    +060  /** Utility class that points to a specific field, in a specific context. */
    +061  @Immutable
    +062  @ThreadSafe
    +063  public final static class FieldPointer implements Serializable, Comparable<FieldPointer> {
    +064    private static final long serialVersionUID = 20210203L;
    +065
    +066    /** Depth of this field, based on the number of dots in the path. */
    +067    private final @Nonnull Integer depth;
    +068
    +069    /** Access path, minus the leaf field. */
    +070    private final @Nonnull String parent;
    +071
    +072    /** Access path to the field in some context. */
    +073    private final @Nonnull String path;
    +074
    +075    /** Base model type. */
    +076    private final @Nonnull Descriptor base;
    +077
    +078    /** Field descriptor for the field in question. */
    +079    private final @Nonnull FieldDescriptor field;
    +080
    +081    /**
    +082     * Setup a new field pointer - generally kept private and resolved via {@link ModelMetadata}.
    +083     *
    +084     * @param base Base model type where {@code path} begins.
    +085     * @param parent Dotted-path without the leaf field, or just `""` if the field is at the root.
    +086     * @param path Dotted-path to the field in question.
    +087     * @param field Field descriptor for the field in question.
    +088     */
    +089    FieldPointer(@Nonnull Descriptor base,
    +090                 @Nonnull String parent,
    +091                 @Nonnull String path,
    +092                 @Nonnull FieldDescriptor field) {
    +093      this.path = path;
    +094      this.base = base;
    +095      this.field = field;
    +096      this.parent = parent;
    +097      this.depth = CharMatcher.is('.').countIn(path);
    +098    }
    +099
    +100    /**
    +101     * Setup a new field pointer - generally kept private and resolved via {@link ModelMetadata}. This consructor
    +102     * creates a field without a parent set.
    +103     *
    +104     * @param base Base model type where {@code path} begins.
    +105     * @param path Dotted-path to the field in question.
    +106     * @param field Field descriptor for the field in question.
    +107     */
    +108    FieldPointer(@Nonnull Descriptor base,
    +109                 @Nonnull String path,
    +110                 @Nonnull FieldDescriptor field) {
    +111      this.path = path;
    +112      this.base = base;
    +113      this.field = field;
    +114      this.parent = "";
    +115      this.depth = CharMatcher.is('.').countIn(path);
    +116    }
    +117
    +118    /**
    +119     * Wrap the field at the specified name on the provided model.
    +120     *
    +121     * @param model Descriptor for a protocol buffer model.
    +122     * @param name Name of a field to get from the provided buffer model.
    +123     * @return Field pointer wrapping the provided information.
    +124     */
    +125    public static @Nonnull FieldPointer fieldAtName(@Nonnull Descriptor model,
    +126                                                    @Nonnull String name) {
    +127      return new FieldPointer(
    +128          model,
    +129          name,
    +130          model.findFieldByName(name)
    +131      );
    +132    }
    +133
    +134    /** {@inheritDoc} */
    +135    @Override
    +136    public boolean equals(Object o) {
    +137      if (this == o) return true;
    +138      if (o == null || getClass() != o.getClass()) return false;
    +139      FieldPointer that = (FieldPointer) o;
    +140      return this.depth.equals(that.depth)
    +141        && com.google.common.base.Objects.equal(path, that.path)
    +142        && com.google.common.base.Objects.equal(base.getFullName(), that.base.getFullName());
    +143    }
    +144
    +145    /** {@inheritDoc} */
    +146    @Override
    +147    public int hashCode() {
    +148      return com.google.common.base.Objects
    +149        .hashCode(path, base.getFullName());
    +150    }
    +151
    +152    /** {@inheritDoc} */
    +153    @Override
    +154    public int compareTo(@Nonnull FieldPointer other) {
    +155      return this.path.compareTo(other.path);
    +156    }
    +157
    +158    /** {@inheritDoc} */
    +159    @Override
    +160    public String toString() {
    +161      return "FieldPointer{" +
    +162        "base='" + base.getName() + '\'' +
    +163        ", path=" + path +
    +164        '}';
    +165    }
    +166
    +167    /** @return Path to the specified field. */
    +168    public @Nonnull String getParent() {
    +169      return parent;
    +170    }
    +171
    +172    /** @return Path to the specified field. */
    +173    public boolean hasParent() {
    +174      return !parent.isEmpty() && !parent.isBlank();
    +175    }
    +176
    +177    /** @return Path to the specified field. */
    +178    public @Nonnull String getPath() {
    +179      return path;
    +180    }
    +181
    +182    /** @return Simple proto name for the field. */
    +183    public @Nonnull String getName() {
    +184      return field.getName();
    +185    }
    +186
    +187    /** @return Simple JSON name for the field. */
    +188    public @Nonnull String getJsonName() {
    +189      return field.getJsonName();
    +190    }
    +191
    +192    /** @return Base model type where the specified path begins. */
    +193    public @Nonnull Descriptor getBase() {
    +194      return base;
    +195    }
    +196
    +197    /** @return Descriptor for the targeted field. */
    +198    public @Nonnull FieldDescriptor getField() {
    +199      return field;
    +200    }
    +201  }
    +202
    +203  /** Utility class that holds a {@link FieldPointer} and matching field value. */
    +204  public final static class FieldContainer<V> implements Serializable, Comparable<FieldContainer<V>> {
    +205    /** Pointer to the field which holds this value. */
    +206    private final @Nonnull FieldPointer field;
    +207
    +208    /** Value for the field, if found. */
    +209    private final @Nonnull Optional<V> value;
    +210
    +211    /**
    +212     * Setup a new field pointer - generally kept private and resolved via {@link ModelMetadata}.
    +213     *
    +214     * @param field Pointer to the field that holds this value.
    +215     * @param value Value extracted for the specified field.
    +216     */
    +217    FieldContainer(@Nonnull FieldPointer field,
    +218                   @Nonnull Optional<V> value) {
    +219      this.field = field;
    +220      this.value = value;
    +221    }
    +222
    +223    /** {@inheritDoc} */
    +224    @Override
    +225    public boolean equals(Object o) {
    +226      if (this == o) return true;
    +227      if (o == null || getClass() != o.getClass()) return false;
    +228      FieldContainer<?> that = (FieldContainer<?>) o;
    +229      return field.equals(that.field)
    +230        && (value.isPresent() == that.value.isPresent())
    +231        && (value.equals(that.value));
    +232    }
    +233
    +234    /** {@inheritDoc} */
    +235    @Override
    +236    public int hashCode() {
    +237      return com.google.common.base.Objects
    +238        .hashCode(field, value);
    +239    }
    +240
    +241    /** {@inheritDoc} */
    +242    @Override
    +243    public int compareTo(@Nonnull FieldContainer<V> other) {
    +244      return this.field.compareTo(other.field);
    +245    }
    +246
    +247    /** {@inheritDoc} */
    +248    @Override
    +249    public String toString() {
    +250      return "FieldContainer{" +
    +251        "" + field.base.getName() +
    +252        ", path=" + field.path +
    +253        ", hasValue=" + value.isPresent() +
    +254        '}';
    +255    }
    +256
    +257    /** Pointer to the field holding the specified value. */
    +258    public @Nonnull FieldPointer getField() {
    +259      return field;
    +260    }
    +261
    +262    /** Value associated with the specified field, or {@link Optional#empty()} if the field has no initialized value. */
    +263    public @Nonnull Optional<V> getValue() {
    +264      return value;
    +265    }
    +266  }
    +267
    +268  // -- Internals -- //
    +269
    +270  /**
    +271   * Match an annotation to a field. If the field is not annotated as such, the method returns `false`.
    +272   *
    +273   * @param field Field to check for the provided annotation.
    +274   * @param annotation Annotation to check for.
    +275   * @return Whether the field is annotated with the provided annotation.
    +276   */
    +277  public static boolean matchFieldAnnotation(@Nonnull FieldDescriptor field, @Nonnull FieldType annotation) {
    +278    if (field.getOptions().hasExtension(Datamodel.field)) {
    +279      var extension = field.getOptions().getExtension(Datamodel.field);
    +280      return annotation.equals(extension.getType());
    +281    }
    +282    return false;
    +283  }
    +284
    +285  /**
    +286   * Match a collection annotation. If the field or model is not annotated as such, the method returns `false`.
    +287   *
    +288   * @param field Field to check for the provided annotation.
    +289   * @param mode Collection mode to check for.
    +290   * @return Whether the field is annotated for the provided collection mode.
    +291   */
    +292  @SuppressWarnings("SameParameterValue")
    +293  public static boolean matchCollectionAnnotation(@Nonnull FieldDescriptor field, @Nonnull CollectionMode mode) {
    +294    if (field.getOptions().hasExtension(Datamodel.collection)) {
    +295      var extension = field.getOptions().getExtension(Datamodel.collection);
    +296      return mode.equals(extension.getMode());
    +297    }
    +298    return false;
    +299  }
    +300
    +301  /**
    +302   * Resolve a model field within the tree of {@code descriptor}, where an instance of annotation data of type
    +303   * {@code ext} is affixed to the field. If the (optional) provided {@code filter} function agrees, the item is
    +304   * returned to the caller in a {@link FieldPointer}.
    +305   *
    +306   * <p>If the field cannot be found, no exception is raised, and {@link Optional#empty()} is returned. The search may
    +307   * also be conducted in {@code recursive} mode, which proceeds to examine sub-messages if the requested field cannot
    +308   * be located on the top-level {@code descriptor}.</p>
    +309   *
    +310   * @param descriptor Descriptor where we should begin our search for the desired property.
    +311   * @param ext Extension the field is annotated with. Only fields annotated with this extension are eligible.
    +312   * @param recursive Whether to search recursively, or just on the top-level instance.
    +313   * @param filter Filter function to dispatch per-found-field. The first one to return {@code true} wins.
    +314   * @param stack Property stack, filled out as we recursively descend.
    +315   * @param <E> Generic type of the model extension object.
    +316   * @return Optional, containing either a resolved {@link FieldPointer}, or empty.
    +317   */
    +318  @VisibleForTesting
    +319  static @Nonnull <E> Optional<FieldPointer> resolveAnnotatedField(@Nonnull Descriptor descriptor,
    +320                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext,
    +321                                                                   @Nonnull Boolean recursive,
    +322                                                                   @Nonnull Optional<Function<E, Boolean>> filter,
    +323                                                                   @Nonnull String stack) {
    +324    Objects.requireNonNull(descriptor, "Cannot resolve field from `null` descriptor.");
    +325    Objects.requireNonNull(ext, "Cannot resolve field from `null` descriptor.");
    +326    Objects.requireNonNull(recursive, "Cannot pass `null` for `recursive` flag.");
    +327    Objects.requireNonNull(filter, "Pass empty optional, not `null`, for field filter parameter.");
    +328    Objects.requireNonNull(stack, "Recursive property stack should not be `null`.");
    +329
    +330    for (FieldDescriptor field : descriptor.getFields()) {
    +331      if (field.getOptions().hasExtension(ext)) {
    +332        var extension = field.getOptions().getExtension(ext);
    +333        if (filter.isEmpty() || filter.get().apply(extension))
    +334          return Optional.of(new FieldPointer(
    +335            descriptor,
    +336            stack.isEmpty() ? field.getName() : stack + "." + field.getName(),
    +337            field));
    +338      }
    +339
    +340      // should we recurse?
    +341      if (recursive && field.getType() == FieldDescriptor.Type.MESSAGE) {
    +342        // if so, append the current prop to the stack and give it a shot
    +343        //noinspection ConstantConditions
    +344        var sub = resolveAnnotatedField(
    +345          field.getMessageType(),
    +346          ext,
    +347          recursive,
    +348          filter,
    +349          stack.isEmpty() ? field.getName() : stack + "." + field.getName());
    +350        if (sub.isPresent())
    +351          return sub;
    +352      }
    +353    }
    +354    return Optional.empty();
    +355  }
    +356
    +357  /**
    +358   * Resolve a model field within the tree of {@code descriptor}, identified by the specified deep {@code path}. If the
    +359   * (optional) provided {@code filter} function agrees, the item is returned to the caller in a {@link FieldPointer}.
    +360   *
    +361   * <p>If the field cannot be found, no exception is raised, and {@link Optional#empty()} is returned. The search may
    +362   * also be conducted in {@code recursive} mode, which proceeds to examine sub-messages if the requested field cannot
    +363   * be located on the top-level {@code descriptor}.</p>
    +364   *
    +365   * @param original Top-level descriptor where we should begin our search for the desired property.
    +366   * @param descriptor Current-level descriptor we are scanning (for recursion).
    +367   * @param path Deep dotted-path to the field we are being asked to resolve.
    +368   * @param remaining Remaining segments of {@code path} to follow/compute.
    +369   * @return Optional, containing either a resolved {@link FieldPointer}, or empty.
    +370   * @throws IllegalArgumentException If the provided path is syntactically invalid.
    +371   * @throws IllegalArgumentException If an attempt is made to access a property on a primitive field.
    +372   */
    +373  @VisibleForTesting
    +374  static @Nonnull Optional<FieldPointer> resolveArbitraryField(@Nonnull Descriptor original,
    +375                                                               @Nonnull Descriptor descriptor,
    +376                                                               @Nonnull String path,
    +377                                                               @Nonnull String remaining) {
    +378    Objects.requireNonNull(original, "Cannot resolve field from `null` descriptor.");
    +379    Objects.requireNonNull(descriptor, "Cannot resolve field from `null` descriptor.");
    +380    Objects.requireNonNull(path, "Cannot resolve field from `null` path.");
    +381    Objects.requireNonNull(remaining, "Recursive remaining stack should not be `null`.");
    +382
    +383    if (remaining.startsWith(".") || remaining.endsWith(".") || remaining.contains(" "))
    +384      throw new IllegalArgumentException(String.format("Invalid deep-field path '%s'.", path));
    +385    if (!remaining.contains(".")) {
    +386      // maybe we're lucky and don't need to recurse
    +387      for (FieldDescriptor field : descriptor.getFields()) {
    +388        if (remaining.equals(field.getName())) {
    +389          return Optional.of(new FieldPointer(
    +390            original,
    +391            path,
    +392            field));
    +393        }
    +394      }
    +395    } else {
    +396      // need to recurse
    +397      String segment = remaining.substring(0, remaining.indexOf('.'));
    +398      var messageField = descriptor.findFieldByName(segment);
    +399      if (messageField != null && messageField.getType() == FieldDescriptor.Type.MESSAGE) {
    +400        // found the next tier
    +401        var subType = messageField.getMessageType();
    +402        String newRemainder = remaining.substring(remaining.indexOf('.') + 1);
    +403        return resolveArbitraryField(
    +404          original,
    +405          subType,
    +406          path,
    +407          newRemainder);
    +408      } else if (messageField != null) {
    +409        // it's not a message :(
    +410        throw new IllegalArgumentException(
    +411          String.format(
    +412            "Cannot access sub-field of primitive leaf field, at '%s' on model type '%s'.",
    +413            path,
    +414            original.getName()));
    +415      }
    +416    }
    +417    // property not found
    +418    return Optional.empty();
    +419  }
    +420
    +421  /**
    +422   * Splice an arbitrary field {@code value} at {@code path} into the provided {@code builder}. If an empty value
    +423   * ({@link Optional#empty()}) is provided, clear any existing value residing at {@code path}. In all cases, mutate the
    +424   * existing {@code builder} rather than returning a copy.
    +425   *
    +426   * @param original Top-level builder, which we hand back at the end.
    +427   * @param builder Builder to splice the value into and return.
    +428   * @param path Path at which the target property resides.
    +429   * @param value Value which we should set the target property to, or clear (if passed {@link Optional#empty()}).
    +430   * @param remaining Remaining properties to recurse down to. Internal use only.
    +431   * @param <Builder> Builder type which we are operating on for this splice.
    +432   * @param <Value> Value type which we are operating with for this splice.
    +433   * @return Provided {@code builder} after being mutated with the specified property value.
    +434   */
    +435  @VisibleForTesting
    +436  static <Builder extends Message.Builder, Value> Builder spliceArbitraryField(@Nonnull Message.Builder original,
    +437                                                                               @Nonnull Message.Builder builder,
    +438                                                                               @Nonnull String path,
    +439                                                                               @Nonnull Optional<Value> value,
    +440                                                                               @Nonnull String remaining) {
    +441    Objects.requireNonNull(original, "Cannot splice field into `null` original builder.");
    +442    Objects.requireNonNull(builder, "Cannot splice field into `null` builder.");
    +443    Objects.requireNonNull(path, "Cannot resolve field from `null` path.");
    +444    Objects.requireNonNull(value, "Pass an empty optional, not `null`, for value.");
    +445    Objects.requireNonNull(remaining, "Recursive remaining stack should not be `null`.");
    +446    if (path.startsWith("."))
    +447      throw new IllegalArgumentException(String.format(
    +448        "Cannot splice path that starts with `.` (got: '%s').", path));
    +449    if (remaining.startsWith("."))
    +450      throw new IllegalArgumentException(String.format(
    +451        "Cannot splice path that starts with `.` (got: '%s').", remaining));
    +452
    +453    var descriptor = builder.getDescriptorForType();
    +454    if (!remaining.isEmpty() && !remaining.contains(".")) {
    +455      // thankfully, no need to recurse
    +456      var field = Objects.requireNonNull(
    +457        descriptor.findFieldByName(remaining), String.format("failed to locate field %s", remaining));
    +458      if (value.isPresent()) {
    +459        try {
    +460          builder.setField(field, value.get());
    +461        } catch (IllegalArgumentException iae) {
    +462          throw new ClassCastException(String.format(
    +463            "Failed to set field '%s': value type mismatch.",
    +464            path));
    +465        }
    +466      } else {
    +467        builder.clearField(field);
    +468      }
    +469      //noinspection unchecked
    +470      return (Builder)original;
    +471    } else {
    +472      // we have a sub-message that is initialized, so we need to recurse.
    +473      String segment = remaining.substring(0, remaining.indexOf('.'));
    +474      String newRemainder = remaining.substring(remaining.indexOf('.') + 1);
    +475      return spliceArbitraryField(
    +476        original,
    +477        builder.getFieldBuilder(Objects.requireNonNull(
    +478          descriptor.findFieldByName(segment),
    +479          String.format(
    +480            "Failed to locate sub-builder at path '%s' on model '%s'.",
    +481            segment,
    +482            builder.getDescriptorForType().getFullName()
    +483          )
    +484        )),
    +485        path,
    +486        value,
    +487        newRemainder
    +488      );
    +489    }
    +490  }
    +491
    +492  @VisibleForTesting
    +493  static @Nonnull <V> FieldContainer<V> pluckFieldRecursive(@Nonnull Message original,
    +494                                                            @Nonnull Message instance,
    +495                                                            @Nonnull String path,
    +496                                                            @Nonnull String remaining) {
    +497    Objects.requireNonNull(original, "Cannot resolve field from `null` descriptor.");
    +498    Objects.requireNonNull(instance, "Cannot resolve field from `null` instance.");
    +499    Objects.requireNonNull(path, "Cannot resolve field from `null` path.");
    +500    Objects.requireNonNull(remaining, "Recursive remaining stack should not be `null`.");
    +501
    +502    var descriptor = instance.getDescriptorForType();
    +503    if (remaining.startsWith(".") || remaining.endsWith(".") || remaining.contains(" ")) {
    +504      throw new IllegalArgumentException("Cannot begin or end model property path with `.`");
    +505    } else if (!remaining.isEmpty() && !remaining.contains(".")) {
    +506      // we got lucky, no need to recurse
    +507      var field = descriptor.findFieldByName(remaining);
    +508      if (field != null) {
    +509        if (field.getType() == FieldDescriptor.Type.MESSAGE) {
    +510          Message modelInstance = (Message)instance.getField(field);
    +511          //noinspection unchecked
    +512          return new FieldContainer<>(
    +513            new FieldPointer(descriptor, path, field),
    +514            !modelInstance.getAllFields().isEmpty() ? Optional.of((V)modelInstance) : Optional.empty());
    +515        } else {
    +516          //noinspection unchecked
    +517          return new FieldContainer<>(
    +518            new FieldPointer(descriptor, path, field),
    +519            Optional.of((V) instance.getField(field)));
    +520        }
    +521      }
    +522    } else {
    +523      // find next segment
    +524      String segment = remaining.substring(0, remaining.indexOf('.'));
    +525      var messageField = descriptor.findFieldByName(segment);
    +526      if (messageField != null && messageField.getType() == FieldDescriptor.Type.MESSAGE) {
    +527        if (!instance.hasField(messageField)) {
    +528          // there is a sub-message that is not initialized. so the field is technically empty.
    +529          return new FieldContainer<>(
    +530            new FieldPointer(original.getDescriptorForType(), path, messageField),
    +531            Optional.empty());
    +532        } else {
    +533          // we have a sub-message that is initialized, so we need to recurse.
    +534          String newRemainder = remaining.substring(remaining.indexOf('.') + 1);
    +535          return pluckFieldRecursive(
    +536            original,
    +537            (Message)instance.getField(messageField),
    +538            path,
    +539            newRemainder);
    +540        }
    +541      } else if (messageField != null) {
    +542        // it's not a message :(
    +543        throw new IllegalArgumentException(
    +544          String.format(
    +545            "Cannot access sub-field of primitive leaf field, at '%s' on model type '%s'.",
    +546            path,
    +547            original.getDescriptorForType().getName()));
    +548      }
    +549    }
    +550    throw new IllegalArgumentException(
    +551      String.format("Failed to locate field '%s' on model type '%s'.", path, descriptor.getName()));
    +552  }
    +553
    +554  // -- Metadata: Qualified Names -- //
    +555
    +556  /**
    +557   * Resolve the fully-qualified type path, or name, for the provided datamodel type descriptor. This is essentially
    +558   * syntactic sugar.
    +559   *
    +560   * @param descriptor Model descriptor to resolve a fully-qualified name for.
    +561   * @return Fully-qualified model type name.
    +562   */
    +563  public static @Nonnull String fullyQualifiedName(@Nonnull Descriptor descriptor) {
    +564    return descriptor.getFullName();
    +565  }
    +566
    +567  /**
    +568   * Resolve the fully-qualified type path, or name, for the provided datamodel instance. This method is essentially
    +569   * syntactic sugar for accessing the model instance's descriptor, and then grabbing the fully-qualified name.
    +570   *
    +571   * @param model Model instance to resolve a fully-qualified name for.
    +572   * @return Fully-qualified model type name.
    +573   */
    +574  public static @Nonnull String fullyQualifiedName(@Nonnull Message model) {
    +575    return fullyQualifiedName(model.getDescriptorForType());
    +576  }
    +577
    +578  // -- Metadata: Role Annotations -- //
    +579
    +580  /**
    +581   * Resolve the general type for a given datamodel type descriptor. The type is either set by default, or set by an
    +582   * explicit annotation affixed to the protocol buffer definition that backs the model.
    +583   *
    +584   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +585   * model? A wire model? {@link DatapointType} will tell you.</p>
    +586   *
    +587   * @param descriptor Model descriptor to retrieve a type for.
    +588   * @return Type of the provided datamodel.
    +589   */
    +590  public static @Nonnull DatapointType role(@Nonnull Descriptor descriptor) {
    +591    return modelAnnotation(descriptor, Datamodel.role, false).orElse(DatapointType.OBJECT);
    +592  }
    +593
    +594  /**
    +595   * Resolve the general type for a given datamodel. The type is either set by default, or set by an explicit annotation
    +596   * affixed to the protocol buffer definition that backs the model.
    +597   *
    +598   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +599   * model? A wire model? {@link DatapointType} will tell you.</p>
    +600   *
    +601   * @param model Model to retrieve a type for.
    +602   * @return Type of the provided datamodel.
    +603   */
    +604  public static @Nonnull DatapointType role(@Nonnull Message model) {
    +605    Objects.requireNonNull(model, "Cannot resolve type for `null` model.");
    +606    return role(model.getDescriptorForType());
    +607  }
    +608
    +609  /**
    +610   * Enforce that a particular datamodel type matches <b>any</b> of the provided {@link DatapointType} annotations.
    +611   *
    +612   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +613   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +614   * a set of types for you.</p>
    +615   *
    +616   * @param model Model to validate against the provided set of types.
    +617   * @param type Type to enforce for the provided model.
    +618   * @return Whether the provided model is a <i>member-of</i> (annotated-by) any of the provided {@code types}.
    +619   */
    +620  public static boolean matchRole(@Nonnull Message model, @Nonnull DatapointType type) {
    +621    Objects.requireNonNull(type, "Cannot match `null` model type.");
    +622    return type.equals(role(model));
    +623  }
    +624
    +625  /**
    +626   * Enforce that a particular datamodel schema {@code descriptor} matches <b>any</b> of the provided
    +627   * {@link DatapointType} annotations.
    +628   *
    +629   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +630   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +631   * a set of types for you.</p>
    +632   *
    +633   * @param descriptor Schema descriptor to validate against the provided set of types.
    +634   * @param type Type to enforce for the provided model.
    +635   * @return Whether the provided model is a <i>member-of</i> (annotated-by) any of the provided {@code types}.
    +636   */
    +637  public static boolean matchRole(@Nonnull Descriptor descriptor, @Nonnull DatapointType type) {
    +638    Objects.requireNonNull(type, "Cannot match `null` descriptor type.");
    +639    return type.equals(role(descriptor));
    +640  }
    +641
    +642  /**
    +643   * <b>Check</b> that a particular datamodel type matches <b>any</b> of the provided {@link DatapointType} annotations.
    +644   *
    +645   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +646   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +647   * a set of types for you.</p>
    +648   *
    +649   * @param model Model to validate against the provided set of types.
    +650   * @param types Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +651   * @return Whether the provided model is a <i>member-of</i> (annotated-by) any of the provided {@code types}.
    +652   */
    +653  public static boolean matchAnyRole(@Nonnull Message model, @Nonnull DatapointType ...types) {
    +654    Objects.requireNonNull(types, "Cannot match `null` model types.");
    +655    return EnumSet.copyOf(Arrays.asList(types)).contains(role(model));
    +656  }
    +657
    +658  /**
    +659   * <b>Check</b> that a particular schema {@code descriptor} matches <b>any</b> of the provided {@link DatapointType}
    +660   * annotations.
    +661   *
    +662   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +663   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +664   * a set of types for you.</p>
    +665   *
    +666   * @param descriptor Schema descriptor to validate against the provided set of types.
    +667   * @param types Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +668   * @return Whether the provided model is a <i>member-of</i> (annotated-by) any of the provided {@code types}.
    +669   */
    +670  public static boolean matchAnyRole(@Nonnull Descriptor descriptor, @Nonnull DatapointType ...types) {
    +671    Objects.requireNonNull(types, "Cannot match `null` model types.");
    +672    return EnumSet.copyOf(Arrays.asList(types)).contains(role(descriptor));
    +673  }
    +674
    +675  /**
    +676   * <b>Enforce</b> that a particular {@code model} instance matches the provided {@link DatapointType} annotation.
    +677   *
    +678   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +679   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +680   * a set of types for you.</p>
    +681   *
    +682   * @param model Model to validate against the provided set of types.
    +683   * @param type Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +684   * @throws InvalidModelType If the specified model's type is not included in {@code types}.
    +685   */
    +686  public static void enforceRole(@Nonnull Message model, @Nonnull DatapointType type) throws InvalidModelType {
    +687    if (!matchRole(model, type)) throw InvalidModelType.from(model, EnumSet.of(type));
    +688  }
    +689
    +690  /**
    +691   * <b>Enforce</b> that a particular datamodel schema {@code descriptor} matches the provided {@link DatapointType}
    +692   * annotation.
    +693   *
    +694   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +695   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +696   * a set of types for you.</p>
    +697   *
    +698   * @param descriptor Descriptor to validate against the provided set of types.
    +699   * @param type Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +700   * @throws InvalidModelType If the specified model's type is not included in {@code types}.
    +701   */
    +702  public static void enforceRole(@Nonnull Descriptor descriptor, @Nonnull DatapointType type) throws InvalidModelType {
    +703    if (!matchRole(descriptor, type)) throw InvalidModelType.from(descriptor, EnumSet.of(type));
    +704  }
    +705
    +706  /**
    +707   * <b>Enforce</b> that a particular datamodel type matches <b>any</b> of the provided {@link DatapointType}
    +708   * annotations.
    +709   *
    +710   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +711   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +712   * a set of types for you.</p>
    +713   *
    +714   * @param model Model to validate against the provided set of types.
    +715   * @param types Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +716   * @throws InvalidModelType If the specified model's type is not included in {@code types}.
    +717   */
    +718  public static void enforceAnyRole(@Nonnull Message model, @Nonnull DatapointType ...types) throws InvalidModelType {
    +719    if (!matchAnyRole(model, types)) throw InvalidModelType.from(model, EnumSet.copyOf(Arrays.asList(types)));
    +720  }
    +721
    +722  /**
    +723   * <b>Enforce</b> that a particular schema {@code descriptor} matches <b>any</b> of the provided {@link DatapointType}
    +724   * annotations.
    +725   *
    +726   * <p>{@link DatapointType} annotations describe the general use case for a given model definition. Is it a database
    +727   * model? A wire model? {@link DatapointType} will tell you, and this model will sweetly enforce membership amongst
    +728   * a set of types for you.</p>
    +729   *
    +730   * @param descriptor Schema descriptor to validate against the provided set of types.
    +731   * @param types Types to validate the model against. If <b>any</b> of the provided types match, the check passes.
    +732   * @throws InvalidModelType If the specified model's type is not included in {@code types}.
    +733   */
    +734  public static void enforceAnyRole(@Nonnull Descriptor descriptor,
    +735                                    @Nonnull DatapointType ...types) throws InvalidModelType {
    +736    if (!matchAnyRole(descriptor, types)) throw InvalidModelType.from(descriptor, EnumSet.copyOf(Arrays.asList(types)));
    +737  }
    +738
    +739  // -- Metadata: Field Resolution -- //
    +740
    +741  /**
    +742   * Resolve an arbitrary field pointer from the provided model {@code instance}, specified by the given {@code path} to
    +743   * the property. If the property cannot be found, {@link Optional#empty()} is returned.
    +744   *
    +745   * <p>This method is <b>safe</b>, in that, unlike other util methods for model metadata, it will not throw if the
    +746   * provided {@code path} is invalid.</p>
    +747   *
    +748   * @param instance Model instance on which to resolve the specified field.
    +749   * @param path Dotted deep-path to the desired field.
    +750   * @return Resolved field pointer for the requested field, or {@link Optional#empty()}.
    +751   */
    +752  public static @Nonnull Optional<FieldPointer> resolveField(@Nonnull Message instance, @Nonnull String path) {
    +753    return resolveField(instance.getDescriptorForType(), path);
    +754  }
    +755
    +756  /**
    +757   * Resolve an arbitrary field pointer from the provided model type {@code escriptor}, specified by the given
    +758   * {@code path} to the property. If the property cannot be found, {@link Optional#empty()} is returned.
    +759   *
    +760   * <p>This method is <b>safe</b>, in that, unlike other util methods for model metadata, it will not throw if the
    +761   * provided {@code path} is invalid.</p>
    +762   *
    +763   * @param descriptor Model type descriptor on which to resolve the specified field.
    +764   * @param path Dotted deep-path to the desired field.
    +765   * @return Resolved field pointer for the requested field, or {@link Optional#empty()}.
    +766   */
    +767  public static @Nonnull Optional<FieldPointer> resolveField(@Nonnull Descriptor descriptor, @Nonnull String path) {
    +768    return resolveArbitraryField(descriptor, descriptor, path, path);
    +769  }
    +770
    +771  // -- Metadata: Model Annotations -- //
    +772
    +773  /**
    +774   * Retrieve a model-level annotation, from {@code instance}, structured by {@code ext}. If no instance of the
    +775   * requested model annotation can be found, {@link Optional#empty()} is returned. Search recursively is supported as
    +776   * well, which descends the search to sub-messages to search for the desired annotation.
    +777   *
    +778   * @param instance Message instance to scan for the specified annotation.
    +779   * @param ext Extension to fetch from the subject model, or any sub-model (if {@code recursive} is {@code true}).
    +780   * @param recursive Whether to search recursively for the desired extension.
    +781   * @param <E> Generic type of extension we are looking for.
    +782   * @return Optional, either {@link Optional#empty()}, or wrapping the found extension data instance.
    +783   */
    +784  public static @Nonnull <E> Optional<E> modelAnnotation(@Nonnull Message instance,
    +785                                                         @Nonnull GeneratedExtension<MessageOptions, E> ext,
    +786                                                         @Nonnull Boolean recursive) {
    +787    return modelAnnotation(instance.getDescriptorForType(), ext, recursive);
    +788  }
    +789
    +790  /**
    +791   * Retrieve a model-level annotation, from the provided model schema {@code descriptor}, structured by {@code ext}. If
    +792   * no instance of the requested model annotation can be found, {@link Optional#empty()} is returned. Search
    +793   * recursively is supported as well, which descends the search to sub-messages to search for the desired annotation.
    +794   *
    +795   * @param descriptor Schema descriptor for a model type.
    +796   * @param ext Extension to fetch from the subject model, or any sub-model (if {@code recursive} is {@code true}).
    +797   * @param recursive Whether to search recursively for the desired extension.
    +798   * @param <E> Generic type of extension we are looking for.
    +799   * @return Optional, either {@link Optional#empty()}, or wrapping the found extension data instance.
    +800   */
    +801  public static @Nonnull <E> Optional<E> modelAnnotation(@Nonnull Descriptor descriptor,
    +802                                                         @Nonnull GeneratedExtension<MessageOptions, E> ext,
    +803                                                         @Nonnull Boolean recursive) {
    +804    Objects.requireNonNull(descriptor, "Cannot resolve type for `null` descriptor.");
    +805    if (descriptor.getOptions().hasExtension(ext))
    +806      return Optional.of(descriptor.getOptions().getExtension(ext));
    +807    if (recursive) {
    +808      // loop through fields. gather any sub-messages, and check procedurally if any of them match. if we find one that
    +809      // does, we return immediately.
    +810      for (FieldDescriptor field : descriptor.getFields()) {
    +811        if (field.getType() == FieldDescriptor.Type.MESSAGE) {
    +812          //noinspection ConstantConditions
    +813          var subresult = modelAnnotation(field.getMessageType(), ext, recursive);
    +814          if (subresult.isPresent())
    +815            return subresult;
    +816        }
    +817      }
    +818    }
    +819    return Optional.empty();
    +820  }
    +821
    +822  // -- Metadata: Field Annotations -- //
    +823
    +824  /**
    +825   * Resolve a {@link FieldPointer} within the scope of {@code instance}, that holds values for the specified metadata
    +826   * annotation {@code ext}. By default, this method searches recursively.
    +827   *
    +828   * @see #annotatedField(Descriptor, GeneratedExtension) variant if a descriptor is on-hand
    +829   * @see #annotatedField(Descriptor, GeneratedExtension, Boolean, Optional) full-spec variant.
    +830   * @param instance Model instance to search for the specified annotated field on.
    +831   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +832   * @param <E> Extension generic type.
    +833   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +834   */
    +835  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Message instance,
    +836                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext) {
    +837    return annotatedField(instance, ext, true);
    +838  }
    +839
    +840  /**
    +841   * Resolve a {@link FieldPointer} within the scope of {@code instance}, that holds values for the specified metadata
    +842   * annotation {@code ext}.
    +843   *
    +844   * <p>This method variant also allows specifying a <b>recursive</b> flag, which, if specified, causes the search to
    +845   * proceed to sub-models (recursively) until a matching field is found. If <b>recursive</b> is passed as {@code false}
    +846   * then the search will only occur at the top-level of {@code instance}.</p>
    +847   *
    +848   * @see #annotatedField(Message, GeneratedExtension, Boolean, Optional) Variant that supports a filter
    +849   * @see #annotatedField(Descriptor, GeneratedExtension, Boolean, Optional) full-spec variant.
    +850   * @param instance Model instance to search for the specified annotated field on.
    +851   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +852   * @param recursive Whether to conduct this search recursively, or just at the top-level.
    +853   * @param <E> Extension generic type.
    +854   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +855   */
    +856  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Message instance,
    +857                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext,
    +858                                                                   @Nonnull Boolean recursive) {
    +859    return annotatedField(instance, ext, recursive, Optional.empty());
    +860  }
    +861
    +862  /**
    +863   * Resolve a {@link FieldPointer} within the scope of {@code instance}, that holds values for the specified metadata
    +864   * annotation {@code ext}.
    +865   *
    +866   * <p>This method variant also allows specifying a <b>filter</b>, which will be run for each property encountered with
    +867   * the annotation present. If the filter returns {@code true}, the field will be selected, otherwise, the search
    +868   * continues until all properties are exhausted (depending on {@code recursive}).</p>
    +869   *
    +870   * @param instance Model instance to search for the specified annotated field on.
    +871   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +872   * @param recursive Whether to conduct this search recursively, or just at the top-level.
    +873   * @param <E> Extension generic type.
    +874   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +875   */
    +876  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Message instance,
    +877                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext,
    +878                                                                   @Nonnull Boolean recursive,
    +879                                                                   @Nonnull Optional<Function<E, Boolean>> filter) {
    +880    return annotatedField(instance.getDescriptorForType(), ext, recursive, filter);
    +881  }
    +882
    +883  /**
    +884   * Resolve a {@link FieldPointer} within the scope of the provided model {@code descriptor}, that holds values for the
    +885   * specified metadata annotation {@code ext}. By default, this search occurs recursively, examining all nested sub-
    +886   * models on the provided instance.
    +887   *
    +888   * @param descriptor Model object descriptor to search for the specified annotated field on.
    +889   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +890   * @param <E> Extension generic type.
    +891   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +892   */
    +893  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Descriptor descriptor,
    +894                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext) {
    +895    return annotatedField(descriptor, ext, Optional.empty());
    +896  }
    +897
    +898  /**
    +899   * Resolve a {@link FieldPointer} within the scope of the provided model {@code descriptor}, that holds values for the
    +900   * specified metadata annotation {@code ext}. By default, this search occurs recursively, examining all nested sub-
    +901   * models on the provided instance.
    +902   *
    +903   * <p>This method variant also allows specifying a <b>filter</b>, which will be run for each property encountered with
    +904   * the annotation present. If the filter returns {@code true}, the field will be selected, otherwise, the search
    +905   * continues until all properties are exhausted (depending on {@code recursive}).</p>
    +906   *
    +907   * @param descriptor Model object descriptor to search for the specified annotated field on.
    +908   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +909   * @param <E> Extension generic type.
    +910   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +911   */
    +912  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Descriptor descriptor,
    +913                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext,
    +914                                                                   @Nonnull Optional<Function<E, Boolean>> filter) {
    +915    return annotatedField(descriptor, ext, true, filter);
    +916  }
    +917
    +918  /**
    +919   * Resolve a {@link FieldPointer} within the scope of the provided model {@code descriptor}, that holds values for the
    +920   * specified metadata annotation {@code ext}. Using the {@code recursive} parameter, the invoking developer may opt to
    +921   * search for the annotated field recursively.
    +922   *
    +923   * <p>This method variant also allows specifying a <b>filter</b>, which will be run for each property encountered with
    +924   * the annotation present. If the filter returns {@code true}, the field will be selected, otherwise, the search
    +925   * continues until all properties are exhausted (depending on {@code recursive}).</p>
    +926   *
    +927   * @param descriptor Model object descriptor to search for the specified annotated field on.
    +928   * @param ext Extension (annotation) which should be affixed to the field we are searching for.
    +929   * @param <E> Extension generic type.
    +930   * @return Optional-wrapped field pointer, or {@link Optional#empty()}.
    +931   */
    +932  public static @Nonnull <E> Optional<FieldPointer> annotatedField(@Nonnull Descriptor descriptor,
    +933                                                                   @Nonnull GeneratedExtension<FieldOptions, E> ext,
    +934                                                                   @Nonnull Boolean recursive,
    +935                                                                   @Nonnull Optional<Function<E, Boolean>> filter) {
    +936    return resolveAnnotatedField(descriptor, ext, recursive, filter, "");
    +937  }
    +938
    +939  /**
    +940   * Retrieve a field-level annotation, from the provided field schema {@code descriptor}, structured by {@code ext}. If
    +941   * no instance of the requested field annotation can be found, {@link Optional#empty()} is returned.
    +942   *
    +943   * @param descriptor Schema descriptor for a field on a model type.
    +944   * @param ext Extension to fetch from the subject field.
    +945   * @param <E> Generic type of extension we are looking for.
    +946   * @return Optional, either {@link Optional#empty()}, or wrapping the found extension data instance.
    +947   */
    +948  public static @Nonnull <E> Optional<E> fieldAnnotation(@Nonnull FieldDescriptor descriptor,
    +949                                                         @Nonnull GeneratedExtension<FieldOptions, E> ext) {
    +950    Objects.requireNonNull(descriptor, "Cannot resolve type for `null` field descriptor.");
    +951    if (descriptor.getOptions().hasExtension(ext))
    +952      return Optional.of(descriptor.getOptions().getExtension(ext));
    +953    return Optional.empty();
    +954  }
    +955
    +956  // -- Metadata: ID Fields -- //
    +957
    +958  /**
    +959   * Resolve a pointer to the provided model {@code instance}'s ID field, whether or not it has a value. If there is no
    +960   * ID-annotated field at all, {@link Optional#empty()} is returned. Alternatively, if the model is not compatible with
    +961   * ID fields, an exception is raised (see below).
    +962   *
    +963   * @param instance Model instance for which an ID field is being resolved.
    +964   * @return Optional, either {@link Optional#empty()} or containing a {@link FieldPointer} to the resolved ID field.
    +965   * @throws InvalidModelType If the specified model does not support IDs. Only objects of type {@code OBJECT} can be
    +966   *         used with this interface.
    +967   */
    +968  public static @Nonnull Optional<FieldPointer> idField(@Nonnull Message instance) throws InvalidModelType {
    +969    return idField(instance.getDescriptorForType());
    +970  }
    +971
    +972  /**
    +973   * Resolve a pointer to the provided schema type {@code descriptor}'s ID field, whether or not it has a value. If
    +974   * there is no ID-annotated field at all, {@link Optional#empty()} is returned. Alternatively, if the model is not
    +975   * compatible with ID fields, an exception is raised (see below).
    +976   *
    +977   * @param descriptor Model instance for which an ID field is being resolved.
    +978   * @return Optional, either {@link Optional#empty()} or containing a {@link FieldPointer} to the resolved ID field.
    +979   * @throws InvalidModelType If the specified model does not support IDs. Only objects of type {@code OBJECT} can be
    +980   *         used with this interface.
    +981   */
    +982  public static @Nonnull Optional<FieldPointer> idField(@Nonnull Descriptor descriptor) throws InvalidModelType {
    +983    enforceAnyRole(Objects.requireNonNull(descriptor), DatapointType.OBJECT, DatapointType.OBJECT_KEY);
    +984    var topLevelId = Objects.requireNonNull(annotatedField(
    +985      descriptor,
    +986      Datamodel.field,
    +987      false,
    +988      Optional.of((field) -> field.getType() == FieldType.ID)));
    +989
    +990    if (topLevelId.isPresent()) {
    +991      return topLevelId;
    +992    } else {
    +993      // okay. no top level ID. what about keys, which must be top-level?
    +994      var keyBase = keyField(descriptor);
    +995      if (keyBase.isPresent()) {
    +996        // we found a key, so scan the key for an ID, which is required on keys.
    +997        return Objects.requireNonNull(resolveAnnotatedField(
    +998          keyBase.get().field.getMessageType(),
    +999          Datamodel.field,
    +1000          false,
    +1001          Optional.of((field) -> field.getType() == FieldType.ID),
    +1002          keyBase.get().getField().getName()));
    +1003      }
    +1004    }
    +1005    // there's no top-level ID, and no top-level key, or the key had no ID. we're done here.
    +1006    return Optional.empty();
    +1007  }
    +1008
    +1009  // -- Metadata: Key Fields -- //
    +1010
    +1011  /**
    +1012   * Resolve a pointer to the provided schema type {@code descriptor}'s {@code KEY} field, whether or not it has a value
    +1013   * assigned. If there is no key-annotated field at all, {@link Optional#empty()} is returned. Alternatively, if the
    +1014   * model is not compatible with key fields, an exception is raised (see below).
    +1015   *
    +1016   * @param instance Model instance for which a key field is being resolved.
    +1017   * @return Optional, either {@link Optional#empty()} or containing a {@link FieldPointer} to the resolved key field.
    +1018   * @throws InvalidModelType If the specified model does not support keys. Only objects of type {@code OBJECT} can be
    +1019   *         used with this interface.
    +1020   */
    +1021  public static @Nonnull Optional<FieldPointer> keyField(@Nonnull Message instance) throws InvalidModelType {
    +1022    return keyField(instance.getDescriptorForType());
    +1023  }
    +1024
    +1025  /**
    +1026   * Resolve a pointer to the provided schema type {@code descriptor}'s {@code KEY} field, whether or not it has a value
    +1027   * assigned. If there is no key-annotated field at all, {@link Optional#empty()} is returned. Alternatively, if the
    +1028   * model is not compatible with key fields, an exception is raised (see below).
    +1029   *
    +1030   * @param descriptor Model type descriptor for which a key field is being resolved.
    +1031   * @return Optional, either {@link Optional#empty()} or containing a {@link FieldPointer} to the resolved key field.
    +1032   * @throws InvalidModelType If the specified model does not support keys. Only objects of type {@code OBJECT} can be
    +1033   *         used with this interface.
    +1034   */
    +1035  public static @Nonnull Optional<FieldPointer> keyField(@Nonnull Descriptor descriptor) throws InvalidModelType {
    +1036    enforceAnyRole(Objects.requireNonNull(descriptor), DatapointType.OBJECT);
    +1037    return Objects.requireNonNull(annotatedField(
    +1038      Objects.requireNonNull(descriptor),
    +1039      Datamodel.field,
    +1040      false,
    +1041      Optional.of((field) -> field.getType() == FieldType.KEY)));
    +1042  }
    +1043
    +1044  // -- Metadata: Value Pluck -- //
    +1045
    +1046  /**
    +1047   * Pluck a field value, addressed by a {@link FieldPointer}, from the provided {@code instance}. If the referenced
    +1048   * field is a message, a message instance will be handed back only if there is an initialized value. Leaf fields
    +1049   * return their raw value, if set. In all cases, if there is no initialized value, {@link Optional#empty()} is
    +1050   * returned.
    +1051   *
    +1052   * @param instance Model instance from which to pluck the property.
    +1053   * @param fieldPointer Pointer to the field we wish to fetch.
    +1054   * @param <V> Generic type of data returned by this operation.
    +1055   * @return Optional wrapping the resolved value, or {@link Optional#empty()} if no value could be resolved.
    +1056   * @throws IllegalStateException If the referenced property is not found, despite witnessing matching types.
    +1057   * @throws IllegalArgumentException If the specified field does not have a matching base type with {@code instance}.
    +1058   */
    +1059  public static @Nonnull <V> FieldContainer<V> pluck(@Nonnull Message instance, @Nonnull FieldPointer fieldPointer) {
    +1060    return pluck(instance, fieldPointer.path);
    +1061  }
    +1062
    +1063  /**
    +1064   * Return a single field value container, plucked from the specified deep {@code path}, in dot form, using the regular
    +1065   * protobuf-definition names for each field. If a referenced field is a message, a message instance will be returned
    +1066   * only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no
    +1067   * initialized value, {@link Optional#empty()} is supplied in place.
    +1068   *
    +1069   * @param instance Model instance to pluck the specified property from.
    +1070   * @param path Deep path for the property value we wish to pluck.
    +1071   * @param <V> Expected type for the property. If types do not match, a {@link ClassCastException} will be raised.
    +1072   * @return Field container, either empty, or containing the plucked value.
    +1073   * @throws IllegalArgumentException If the provided path is syntactically invalid, or the field does not exist.
    +1074   */
    +1075  public static @Nonnull <V> FieldContainer<V> pluck(@Nonnull Message instance, @Nonnull String path) {
    +1076    return pluckFieldRecursive(instance, instance, path, path);
    +1077  }
    +1078
    +1079  /**
    +1080   * Return an iterable containing plucked value containers for each field mentioned in {@code mask}, that is present on
    +1081   * {@code instance} with an initialized value. If a referenced field is a message, a message instance will be included
    +1082   * only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no
    +1083   * initialized value, {@link Optional#empty()} is supplied in place.
    +1084   *
    +1085   * <p>If a field cannot be found, {@link Optional#empty()} is supplied in its place, so that the output order matches
    +1086   * path iteration order on the supplied {@code mask}. This method is therefore safe with regard to path access.</p>
    +1087   *
    +1088   * @param instance Model instance to pluck the specified properties from.
    +1089   * @param mask Mask of properties to pluck from the model instance.
    +1090   * @return Stream which emits each field container, with a generic {@code Object} for each value.
    +1091   */
    +1092  public static @Nonnull SortedSet<FieldContainer<Object>> pluckAll(@Nonnull Message instance, @Nonnull FieldMask mask) {
    +1093    return pluckAll(instance, mask, true);
    +1094  }
    +1095
    +1096  /**
    +1097   * Return an iterable containing plucked value containers for each field mentioned in {@code mask}, that is present on
    +1098   * {@code instance} with an initialized value. If a referenced field is a message, a message instance will be included
    +1099   * only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no
    +1100   * initialized value, {@link Optional#empty()} is supplied in place.
    +1101   *
    +1102   * <p>If a field cannot be found, {@link Optional#empty()} is supplied in its place, so that the output order matches
    +1103   * path iteration order on the supplied {@code mask}. This method is therefore safe with regard to path access. If
    +1104   * {@code normalize} is activated (the default for {@link #pluckAll(Message, FieldMask)}), the field mask will be
    +1105   * sorted and de-duplicated before processing.</p>
    +1106   *
    +1107   * <p>Sort order of the return value is based on the full path of properties selected - i.e. field containers are
    +1108   * returned in lexicographic sort order matching their underlying property paths.</p>
    +1109   *
    +1110   * @param instance Model instance to pluck the specified properties from.
    +1111   * @param mask Mask of properties to pluck from the model instance.
    +1112   * @param normalize Whether to normalize the field mask before plucking fields.
    +1113   * @return Stream which emits each field container, with a generic {@code Object} for each value.
    +1114   */
    +1115  public static @Nonnull SortedSet<FieldContainer<Object>> pluckAll(@Nonnull Message instance,
    +1116                                                                    @Nonnull FieldMask mask,
    +1117                                                                    @Nonnull Boolean normalize) {
    +1118    return ImmutableSortedSet.copyOfSorted(pluckStream(instance, mask, normalize)
    +1119      .collect(Collectors.toCollection(ConcurrentSkipListSet::new)));
    +1120  }
    +1121
    +1122  /**
    +1123   * Return a stream which emits plucked value containers for each field mentioned in {@code mask}, that is present on
    +1124   * {@code instance} with an initialized value. If a referenced field is a message, a message instance will be emitted
    +1125   * only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no
    +1126   * initialized value, {@link Optional#empty()} is supplied in place.
    +1127   *
    +1128   * <p>If a field cannot be found, {@link Optional#empty()} is supplied in its place, so that the output order matches
    +1129   * path iteration order on the supplied {@code mask}. This method is therefore safe with regard to path access.</p>
    +1130   *
    +1131   * <p><b>Performance note:</b> the {@link Stream} returned by this method is explicitly parallel-capable, because
    +1132   * reading descriptor schema is safely concurrent.</p>
    +1133   *
    +1134   * @param instance Model instance to pluck the specified properties from.
    +1135   * @param mask Mask of properties to pluck from the model instance.
    +1136   * @return Stream which emits each field container, with a generic {@code Object} for each value.
    +1137   */
    +1138  public static @Nonnull Stream<FieldContainer<Object>> pluckStream(@Nonnull Message instance,
    +1139                                                                    @Nonnull FieldMask mask) {
    +1140    return pluckStream(instance, mask, true);
    +1141  }
    +1142
    +1143  /**
    +1144   * Return a stream which emits plucked value containers for each field mentioned in {@code mask}, that is present on
    +1145   * {@code instance} with an initialized value. If a referenced field is a message, a message instance will be emitted
    +1146   * only if there is an initialized value. Leaf fields return their raw value, if set. In all cases, if there is no
    +1147   * initialized value, {@link Optional#empty()} is supplied in place.
    +1148   *
    +1149   * <p>If a field cannot be found, {@link Optional#empty()} is supplied in its place, so that the output order matches
    +1150   * path iteration order on the supplied {@code mask}. This method is therefore safe with regard to path access. If
    +1151   * {@code normalize} is activated (the default for {@link #pluckStream(Message, FieldMask)}), the field mask will be
    +1152   * sorted and de-duplicated before processing.</p>
    +1153   *
    +1154   * <p><b>Performance note:</b> the {@link Stream} returned by this method is explicitly parallel-capable, because
    +1155   * reading descriptor schema is safely concurrent.</p>
    +1156   *
    +1157   * @param instance Model instance to pluck the specified properties from.
    +1158   * @param mask Mask of properties to pluck from the model instance.
    +1159   * @param normalize Whether to normalize the field mask before plucking fields.
    +1160   * @return Stream which emits each field container, with a generic {@code Object} for each value.
    +1161   */
    +1162  public static @Nonnull Stream<FieldContainer<Object>> pluckStream(@Nonnull Message instance,
    +1163                                                                    @Nonnull FieldMask mask,
    +1164                                                                    @Nonnull Boolean normalize) {
    +1165    return (new TreeSet<>((normalize ? FieldMaskUtil.normalize(mask) : mask).getPathsList()))
    +1166      .parallelStream()
    +1167      .map((fieldPath) -> pluck(instance, fieldPath));
    +1168  }
    +1169
    +1170  // -- Metadata: ID/Key Value Pluck -- //
    +1171
    +1172  /**
    +1173   * Resolve the provided model instance's assigned ID, by walking the property structure for the entity, and returning
    +1174   * either the first {@code id}-annotated field's value at the top-level, or the first {@code id}-annotated field value
    +1175   * on the first {@code key}-annotated message field at the top level of the provided message.
    +1176   *
    +1177   * <p>If no ID field <i>value</i> can be resolved, {@link Optional#empty()} is returned. On the other hand, if the
    +1178   * model is not a business object or does not have an ID annotation at all, an exception is raised (see below).</p>
    +1179   *
    +1180   * @param <ID> Type for the ID value we are resolving.
    +1181   * @param instance Model instance for which an ID value is desired.
    +1182   * @return Optional wrapping the value of the model instance's ID, or an empty optional if no value could be resolved.
    +1183   * @throws InvalidModelType If the supplied model is not a business object and/or does not have an ID field at all.
    +1184   */
    +1185  public static @Nonnull <ID> Optional<ID> id(@Nonnull Message instance) {
    +1186    var descriptor = instance.getDescriptorForType();
    +1187    enforceAnyRole(descriptor, DatapointType.OBJECT, DatapointType.OBJECT_KEY);
    +1188    Optional<FieldPointer> idField = idField(descriptor);
    +1189    if (idField.isEmpty())
    +1190      throw new MissingAnnotatedField(descriptor, FieldType.ID);
    +1191    return ModelMetadata.<ID>pluck(instance, idField.get()).getValue();
    +1192  }
    +1193
    +1194  /**
    +1195   * Resolve the provided model instance's assigned {@code KEY} instance, by walking the property structure for the
    +1196   * entity, and returning the first {@code key}-annotated field's value at the top-level of the provided message.
    +1197   *
    +1198   * <p>If no key field <i>value</i> can be resolved, {@link Optional#empty()} is returned. On the other hand, if the
    +1199   * model is not a business object or does not support key annotations at all, an exception is raised (see below).</p>
    +1200   *
    +1201   * @param <Key> Type for the key we are resolving.
    +1202   * @param instance Model instance for which an key value is desired.
    +1203   * @return Optional wrapping the value of the model instance's key, or an empty optional if none could be resolved.
    +1204   * @throws InvalidModelType If the supplied model is not a business object and/or does not have an key field at all.
    +1205   */
    +1206  public static @Nonnull <Key> Optional<Key> key(@Nonnull Message instance) {
    +1207    Descriptor descriptor = instance.getDescriptorForType();
    +1208    enforceRole(descriptor, DatapointType.OBJECT);
    +1209    Optional<FieldPointer> keyField = annotatedField(
    +1210      descriptor,
    +1211      Datamodel.field,
    +1212      false,
    +1213      Optional.of((field) -> field.getType() == FieldType.KEY));
    +1214
    +1215    if (keyField.isEmpty())
    +1216      throw new MissingAnnotatedField(descriptor, FieldType.KEY);
    +1217    //noinspection unchecked
    +1218    return (Optional<Key>)pluck(instance, keyField.get()).getValue();
    +1219  }
    +1220
    +1221  // -- Metadata: Value Splice -- //
    +1222
    +1223  /**
    +1224   * Splice the provided optional value (or clear any existing value) at the field {@code path} in the provided model
    +1225   * {@code instance}. Return a re-built message after the splice.
    +1226   *
    +1227   * <p>If {@link Optional#empty()} is passed for the {@code value} to set, any existing value placed in that field
    +1228   * will be cleared. This method works identically for primitive leaf fields and message fields.</p>
    +1229   *
    +1230   * @param instance Model instance to splice the value into.
    +1231   * @param path Deep path at which to splice the value.
    +1232   * @param val Value to splice into the model, or {@link Optional#empty()} to clear any existing value.
    +1233   * @param <Model> Model type which we are working with for this splice operation.
    +1234   * @param <Value> Value type which we are splicing in, if applicable.
    +1235   * @return Re-built model, after the splice operation.
    +1236   */
    +1237  public static @Nonnull <Model extends Message, Value> Model splice(@Nonnull Message instance,
    +1238                                                                     @Nonnull String path,
    +1239                                                                     @Nonnull Optional<Value> val) {
    +1240
    +1241    return splice(
    +1242      instance,
    +1243      resolveField(instance, path)
    +1244        .orElseThrow(() -> new IllegalArgumentException(String.format(
    +1245          "Failed to resolve path '%s' on model instance of type '%s' for value splice.",
    +1246          path,
    +1247          instance.getDescriptorForType().getName()))),
    +1248      val);
    +1249  }
    +1250
    +1251  /**
    +1252   * Splice the provided optional value (or clear any existing value) at the specified {@code field} pointer, in the
    +1253   * provided model {@code instance}. Return a re-built message after the splice.
    +1254   *
    +1255   * <p>If {@link Optional#empty()} is passed for the {@code value} to set, any existing value placed in that field
    +1256   * will be cleared. This method works identically for primitive leaf fields and message fields.</p>
    +1257   *
    +1258   * @param instance Model instance to splice the value into.
    +1259   * @param field Resolved and validated field pointer for the field to splice.
    +1260   * @param val Value to splice into the model, or {@link Optional#empty()} to clear any existing value.
    +1261   * @param <Model> Model type which we are working with for this splice operation.
    +1262   * @param <Value> Value type which we are splicing in, if applicable.
    +1263   * @return Re-built model, after the splice operation.
    +1264   */
    +1265  public static @Nonnull <Model extends Message, Value> Model splice(@Nonnull Message instance,
    +1266                                                                     @Nonnull FieldPointer field,
    +1267                                                                     @Nonnull Optional<Value> val) {
    +1268    //noinspection unchecked
    +1269    return (Model)spliceBuilder(instance.toBuilder(), field, val).build();
    +1270  }
    +1271
    +1272  /**
    +1273   * Splice the provided optional value (or clear any existing value) at the specified {@code field} pointer, in the
    +1274   * provided model {@code instance}. Return the provided builder after the splice operation. The return value may be
    +1275   * ignored if the developer so wishes (the provided {@code builder} is mutated in place).
    +1276   *
    +1277   * <p>If {@link Optional#empty()} is passed for the {@code value} to set, any existing value placed in that field
    +1278   * will be cleared. This method works identically for primitive leaf fields and message fields.</p>
    +1279   *
    +1280   * @param builder Model builder to splice the value into.
    +1281   * @param field Resolved and validated field pointer for the field to splice.
    +1282   * @param val Value to splice into the model, or {@link Optional#empty()} to clear any existing value.
    +1283   * @param <Builder> Model builder type which we are working with for this splice operation.
    +1284   * @param <Value> Value type which we are splicing in, if applicable.
    +1285   * @return Model {@code builder}, after the splice operation.
    +1286   */
    +1287  @CanIgnoreReturnValue
    +1288  public static @Nonnull <Builder extends Message.Builder, Value> Builder spliceBuilder(
    +1289    @Nonnull Message.Builder builder,
    +1290    @Nonnull FieldPointer field,
    +1291    @Nonnull Optional<Value> val) {
    +1292    var noPrefixPath = field.path.startsWith(".") ? field.path.substring(1) : field.path;
    +1293    return spliceArbitraryField(
    +1294      builder,
    +1295      builder,
    +1296      noPrefixPath,
    +1297      val,
    +1298      noPrefixPath
    +1299    );
    +1300  }
    +1301
    +1302  // -- Metadata: ID/Key Splice -- //
    +1303
    +1304  /**
    +1305   * Splice the provided value at {@code val}, into the ID field value for {@code instance}. If an ID-annotated property
    +1306   * cannot be located, or the model is not of a suitable/type role for use with IDs, an exception is raised (see below
    +1307   * for more info).
    +1308   *
    +1309   * <p>If an existing value exists for the model's ID, <b>it will be replaced</b>. In most object-based storage engines
    +1310   * this will end up copying the object, rather than mutating an ID. Be careful of this behavior. Passing
    +1311   * {@link Optional#empty()} will clear any existing ID on the model.</p>
    +1312   *
    +1313   * @param instance Model instance to splice the value into. Because models are immutable, this involves converting the
    +1314   *                 model to a builder, splicing in the value, and then re-building the model. As such, the model
    +1315   *                 returned will be a <i>different object instance</i>, but will otherwise be untouched.
    +1316   * @param val Value we should splice-into the ID field for the record. It is expected that the generic type of this
    +1317   *            value will line up with the ID field type, otherwise a {@link ClassCastException} will be thrown.
    +1318   * @param <Model> Type of model we are splicing an ID value into.
    +1319   * @param <Value> Type of ID value we are splicing into the model.
    +1320   * @return Model instance, rebuilt, after splicing in the provided value, at the model's ID-annotated field.
    +1321   * @throws InvalidModelType If the specified model is not suitable for use with IDs at all.
    +1322   * @throws ClassCastException If the {@code Value} generic type does not match the ID field primitive type.
    +1323   * @throws MissingAnnotatedField If the provided {@code instance} is not of the correct type, or has no ID field.
    +1324   */
    +1325  public static @Nonnull <Model extends Message, Value> Model spliceId(@Nonnull Message instance,
    +1326                                                                       @Nonnull Optional<Value> val) {
    +1327    //noinspection unchecked
    +1328    return (Model)spliceIdBuilder(instance.toBuilder(), val).build();
    +1329  }
    +1330
    +1331  /**
    +1332   * Splice the provided value at {@code val}, into the ID field value for the provided model {@code builder}. If an ID-
    +1333   * annotated property cannot be located, or the model is not of a suitable/type role for use with IDs, an exception is
    +1334   * raised (see below for more info).
    +1335   *
    +1336   * <p>If an existing value exists for the model's ID, <b>it will be replaced</b>. In most object-based storage engines
    +1337   * this will end up copying the object, rather than mutating an ID. Be careful of this behavior. Passing
    +1338   * {@link Optional#empty()} will clear any existing ID on the model.</p>
    +1339   *
    +1340   * @param builder Model instance builder to splice the value into. The builder provided is <i>mutated in place</i>, so
    +1341   *                it will be an identical object instance to the one provided, but with the ID property filled in.
    +1342   * @param val Value we should splice-into the ID field for the record. It is expected that the generic type of this
    +1343   *            value will line up with the ID field type, otherwise a {@link ClassCastException} will be thrown.
    +1344   * @param <Builder> Type of model builder we are splicing an ID value into.
    +1345   * @param <Value> Type of ID value we are splicing into the model.
    +1346   * @return Model builder, after splicing in the provided value, at the model's ID-annotated field.
    +1347   * @throws InvalidModelType If the specified model is not suitable for use with IDs at all.
    +1348   * @throws ClassCastException If the {@code Value} generic type does not match the ID field primitive type.
    +1349   * @throws MissingAnnotatedField If the provided {@code builder} is not of the correct type, or has no ID field.
    +1350   */
    +1351  public static @Nonnull <Builder extends Message.Builder, Value> Builder spliceIdBuilder(
    +1352    @Nonnull Message.Builder builder,
    +1353    @Nonnull Optional<Value> val) {
    +1354    // resolve descriptor and field
    +1355    if (val.isPresent() && val.get() instanceof Message)
    +1356      throw new IllegalArgumentException("Cannot set messages as ID values.");
    +1357    var descriptor = builder.getDescriptorForType();
    +1358    enforceAnyRole(descriptor, DatapointType.OBJECT, DatapointType.OBJECT_KEY);
    +1359    var fieldPath = idField(descriptor)
    +1360      .orElseThrow(() -> new MissingAnnotatedField(descriptor, FieldType.ID))
    +1361      .getPath();
    +1362
    +1363    return spliceArbitraryField(
    +1364      builder,
    +1365      builder,
    +1366      fieldPath,
    +1367      val,
    +1368      fieldPath);
    +1369  }
    +1370
    +1371  /**
    +1372   * Splice the provided value at {@code val}, into the key message value for {@code instance}. If a key-annotated
    +1373   * property cannot be located, or the model is not of a suitable/type role for use with keys, an exception is raised
    +1374   * (see below for more info).
    +1375   *
    +1376   * <p>If an existing value is set for the model's key, <b>it will be replaced</b>. In most object-based storage
    +1377   * engines this will end up copying the object, rather than mutating a key. Keys are usually immutable for this
    +1378   * reason, so use this method with care. Passing {@link Optional#empty()} will clear any existing key message
    +1379   * currently affixed to the model {@code instance}.</p>
    +1380   *
    +1381   * @param instance Model instance to splice the value into. Because models are immutable, this involves converting the
    +1382   *                 model to a builder, splicing in the value, and then re-building the model. As such, the model
    +1383   *                 returned will be a <i>different object instance</i>, but will otherwise be untouched.
    +1384   * @param val Value we should splice-into the ID field for the record. It is expected that the generic type of this
    +1385   *            value will line up with the ID field type, otherwise a {@link ClassCastException} will be thrown.
    +1386   * @param <Model> Type of model we are splicing an ID value into.
    +1387   * @param <Key> Type of key message we are splicing into the model.
    +1388   * @return Model instance, rebuilt, after splicing in the provided value, at the model's ID-annotated field.
    +1389   * @throws InvalidModelType If the specified model is not suitable for use with IDs at all.
    +1390   * @throws ClassCastException If the {@code Value} generic type does not match the ID field primitive type.
    +1391   * @throws MissingAnnotatedField If the provided {@code builder} is not of the correct type, or has no ID field.
    +1392   */
    +1393  public static @Nonnull <Model extends Message, Key extends Message> Model spliceKey(@Nonnull Message instance,
    +1394                                                                                      @Nonnull Optional<Key> val) {
    +1395    //noinspection unchecked
    +1396    return (Model)spliceKeyBuilder(instance.toBuilder(), val).build();
    +1397  }
    +1398
    +1399  /**
    +1400   * Splice the provided value at {@code val}, into the key message value for the supplied {@code builder}. If a
    +1401   * key-annotated property cannot be located, or the model is not of a suitable/type role for use with keys, an
    +1402   * exception is raised (see below for more info).
    +1403   *
    +1404   * <p>If an existing value is set for the model's key, <b>it will be replaced</b>. In most object-based storage
    +1405   * engines this will end up copying the object, rather than mutating a key. Keys are usually immutable for this
    +1406   * reason, so use this method with care. Passing {@link Optional#empty()} will clear any existing key message
    +1407   * currently affixed to the model {@code instance}.</p>
    +1408   *
    +1409   * @param builder Model instance builder to splice the value into. The builder provided is <i>mutated in place</i>, so
    +1410   *                it will be an identical object instance to the one provided, but with the key property filled in.
    +1411   * @param val Value we should splice-into the key field for the record. It is expected that the generic type of this
    +1412   *            value will line up with the key message type, otherwise a {@link ClassCastException} will be thrown.
    +1413   * @param <Builder> Type of model builder we are splicing a key value into.
    +1414   * @param <Key> Type of key message we are splicing into the model.
    +1415   * @return Model builder, after splicing in the provided message, at the model's key-annotated field.
    +1416   * @throws InvalidModelType If the specified model is not suitable for use with keys at all.
    +1417   * @throws ClassCastException If the {@code Value} generic type does not match the key field primitive type.
    +1418   * @throws MissingAnnotatedField If the provided {@code builder} is not of the correct type, or has no key field.
    +1419   */
    +1420  public static @Nonnull <Builder extends Message.Builder, Key extends Message> Builder spliceKeyBuilder(
    +1421    @Nonnull Message.Builder builder,
    +1422    @Nonnull Optional<Key> val) {
    +1423    // resolve descriptor and key message field
    +1424    var descriptor = builder.getDescriptorForType();
    +1425    enforceRole(descriptor, DatapointType.OBJECT);
    +1426    var fieldPath = keyField(descriptor)
    +1427      .orElseThrow(() -> new MissingAnnotatedField(descriptor, FieldType.KEY))
    +1428      .getPath();
    +1429
    +1430    return spliceArbitraryField(
    +1431      builder,
    +1432      builder,
    +1433      fieldPath,
    +1434      val,
    +1435      fieldPath);
    +1436  }
    +1437
    +1438  /**
    +1439   * Crawl all fields, recursively, on the descriptor provided. This data may also be accessed via a Java stream via the
    +1440   * method variants listed below. Variants of this method also allow predicate-based filtering or control of recursion.
    +1441   *
    +1442   * @see #allFields(Descriptor, Optional) to provide an optional filtering predicate.
    +1443   * @see #allFields(Descriptor, Optional, Boolean) to provide an optional predicate, and/or control recursion.
    +1444   *
    +1445   * @param descriptor Schema descriptor to crawl model definitions on.
    +1446   * @return Iterable of all fields, recursively, from the descriptor.
    +1447   */
    +1448  public static @Nonnull Iterable<FieldPointer> allFields(@Nonnull Descriptor descriptor) {
    +1449    return allFields(descriptor, Optional.empty(), true);
    +1450  }
    +1451
    +1452  /**
    +1453   * Crawl all fields, recursively, on the descriptor provided. For each field encountered, run `predicate` to determine
    +1454   * whether to include the field, filtering the returned iterable accordingly. This data may also be accessed via a
    +1455   * Java stream via the method variants listed below.
    +1456   *
    +1457   * @see #allFields(Descriptor, Optional, Boolean) to additionally control recursion.
    +1458   *
    +1459   * @param descriptor Schema descriptor to crawl model definitions on.
    +1460   * @param predicate Filter predicate function, if applicable.
    +1461   * @return Iterable of all fields, recursively, from the descriptor, filtered by `predicate`.
    +1462   */
    +1463  public static @Nonnull Iterable<FieldPointer> allFields(@Nonnull Descriptor descriptor,
    +1464                                                          @Nonnull Optional<Predicate<FieldPointer>> predicate) {
    +1465    return allFields(descriptor, predicate, true);
    +1466  }
    +1467
    +1468  /**
    +1469   * Crawl all fields, recursively, on the descriptor provided. For each field encountered, run `predicate` to determine
    +1470   * whether to include the field, filtering the returned iterable accordingly. This data may also be accessed via a
    +1471   * Java stream via the method variants listed below.
    +1472   *
    +1473   * @see #streamFields(Descriptor, Optional, Boolean) to access a stream of fields instead.
    +1474   *
    +1475   * @param descriptor Schema descriptor to crawl model definitions on.
    +1476   * @param predicate Filter predicate function, if applicable.
    +1477   * @return Iterable of all fields, optionally recursively, from the descriptor, filtered by `predicate`.
    +1478   */
    +1479  public static @Nonnull Iterable<FieldPointer> allFields(@Nonnull Descriptor descriptor,
    +1480                                                          @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1481                                                          @Nonnull Boolean recursive) {
    +1482    return streamFields(
    +1483      descriptor,
    +1484      predicate,
    +1485      recursive
    +1486    ).collect(Collectors.toUnmodifiableList());
    +1487  }
    +1488
    +1489  /**
    +1490   * Crawl all fields, recursively, on the descriptor provided. For each field encountered, run `predicate` to determine
    +1491   * whether to include the field, filtering the returned iterable accordingly. This data may also be accessed via a
    +1492   * Java stream via the method variants listed below.
    +1493   *
    +1494   * <p>If a `MESSAGE` field is encountered and the algorithm needs to decide whether to recurse, this variant includes
    +1495   * support for the `decider` function. `decider` is invoked to decide whether to recurse for opportunity to do so.</p>
    +1496   *
    +1497   * @see #streamFields(Descriptor, Optional, Boolean) to access a stream of fields instead.
    +1498   *
    +1499   * @param descriptor Schema descriptor to crawl model definitions on.
    +1500   * @param predicate Filter predicate function, if applicable.
    +1501   * @param decider Function which decides whether to recurse, for each opportunity to do so.
    +1502   * @return Iterable of all fields, optionally recursively, from the descriptor, filtered by `predicate`.
    +1503   */
    +1504  public static @Nonnull Iterable<FieldPointer> allFields(@Nonnull Descriptor descriptor,
    +1505                                                          @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1506                                                          @Nonnull Predicate<FieldPointer> decider) {
    +1507    return streamFields(
    +1508      descriptor,
    +1509      predicate,
    +1510      decider
    +1511    ).collect(Collectors.toUnmodifiableList());
    +1512  }
    +1513
    +1514  /**
    +1515   * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run
    +1516   * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. This
    +1517   * method variant runs each operation serially.
    +1518   *
    +1519   * <p>This method variant does not allow the invoking user to crawl recursively.</p>
    +1520   *
    +1521   * @see #streamFields(Descriptor) for the cleanest invocation of this method.
    +1522   *
    +1523   * @param descriptor Schema descriptor to crawl model definitions on.
    +1524   * @param predicate Filter predicate function, if applicable.
    +1525   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1526   */
    +1527  public static @Nonnull Stream<FieldPointer> forEachField(@Nonnull Descriptor descriptor,
    +1528                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate) {
    +1529    Objects.requireNonNull(descriptor);
    +1530    Objects.requireNonNull(predicate);
    +1531
    +1532    return streamFieldsRecursive(
    +1533            descriptor,
    +1534            descriptor,
    +1535            predicate,
    +1536            (field) -> false,
    +1537            "",
    +1538            false
    +1539    );
    +1540  }
    +1541
    +1542  /**
    +1543   * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run
    +1544   * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. This
    +1545   * method variant runs each operation serially.
    +1546   *
    +1547   * <p>This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search
    +1548   * is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate
    +1549   * is provided, the entire set of recursive effective fields is returned from the provided descriptor.</p>
    +1550   *
    +1551   * @see #streamFields(Descriptor) for the cleanest invocation of this method.
    +1552   *
    +1553   * @param descriptor Schema descriptor to crawl model definitions on.
    +1554   * @param predicate Filter predicate function, if applicable.
    +1555   * @param recursive Whether to perform recursion down to sub-messages.
    +1556   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1557   */
    +1558  public static @Nonnull Stream<FieldPointer> forEachField(@Nonnull Descriptor descriptor,
    +1559                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1560                                                           boolean recursive) {
    +1561    Objects.requireNonNull(descriptor);
    +1562    Objects.requireNonNull(predicate);
    +1563
    +1564    return streamFieldsRecursive(
    +1565            descriptor,
    +1566            descriptor,
    +1567            predicate,
    +1568            (field) -> recursive,
    +1569            "",
    +1570            false
    +1571    );
    +1572  }
    +1573
    +1574  /**
    +1575   * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run
    +1576   * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. This
    +1577   * method variant runs each operation serially.
    +1578   *
    +1579   * <p>If a `MESSAGE` field is encountered and the algorithm needs to decide whether to recurse, this variant includes
    +1580   * support for the `decider` function. `decider` is invoked to decide whether to recurse for opportunity to do so.</p>
    +1581   *
    +1582   * <p>This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search
    +1583   * is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate
    +1584   * is provided, the entire set of recursive effective fields is returned from the provided descriptor.</p>
    +1585   *
    +1586   * @see #streamFields(Descriptor) for the cleanest invocation of this method.
    +1587   *
    +1588   * @param descriptor Schema descriptor to crawl model definitions on.
    +1589   * @param predicate Filter predicate function, if applicable.
    +1590   * @param decider Function that decides whether to recurse.
    +1591   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1592   */
    +1593  public static @Nonnull Stream<FieldPointer> forEachField(@Nonnull Descriptor descriptor,
    +1594                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1595                                                           @Nonnull Predicate<FieldPointer> decider) {
    +1596    Objects.requireNonNull(descriptor);
    +1597    Objects.requireNonNull(predicate);
    +1598
    +1599    return streamFieldsRecursive(
    +1600            descriptor,
    +1601            descriptor,
    +1602            predicate,
    +1603            decider,
    +1604            "",
    +1605            false
    +1606    );
    +1607  }
    +1608
    +1609  /**
    +1610   * Crawl all fields, recursively, on the descriptor associated with the provided model instance, and return them in
    +1611   * a stream.
    +1612   *
    +1613   * <p>This method crawls recursively by default, but this behavior can be customized via the alternate method variants
    +1614   * listed below. Other variants also allow applying a predicate to filter the returned fields.</p>
    +1615   *
    +1616   * @see #streamFields(Descriptor, Optional) for the opportunity to provide a filter predicate.
    +1617   * @see #streamFields(Descriptor, Optional, Boolean) for the opportunity to control recursive crawling, and provide a
    +1618   *      filter predicate.
    +1619   *
    +1620   * @param descriptor Schema descriptor to crawl model definitions on.
    +1621   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1622   */
    +1623  public static @Nonnull <M extends Message> Stream<FieldPointer> streamFields(@Nonnull Descriptor descriptor) {
    +1624    return streamFields(descriptor, Optional.empty());
    +1625  }
    +1626
    +1627  /**
    +1628   * Crawl all fields, recursively, on the descriptor associated with the provided model instance. For each field
    +1629   * encountered, run `predicate` to determine whether to include the field, filtering the returned stream of fields
    +1630   * accordingly.
    +1631   *
    +1632   * <p>This method crawls recursively by default, but this behavior can be customized via the alternate method variants
    +1633   * listed below.</p>
    +1634   *
    +1635   * @see #streamFields(Descriptor, Optional, Boolean) for the opportunity to control recursive crawling.
    +1636   *
    +1637   * @param descriptor Schema descriptor to crawl model definitions on.
    +1638   * @param predicate Filter predicate function, if applicable.
    +1639   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1640   */
    +1641  public static @Nonnull Stream<FieldPointer> streamFields(@Nonnull Descriptor descriptor,
    +1642                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate) {
    +1643    return streamFields(descriptor, predicate, true);
    +1644  }
    +1645
    +1646  /**
    +1647   * Crawl all fields, recursively, on the descriptor associated with the provided model instance. For each field
    +1648   * encountered, run `predicate` to determine whether to include the field, filtering the returned stream of fields
    +1649   * accordingly. In this case, `predicate` is required.
    +1650   *
    +1651   * <p>This method crawls recursively by default, but this behavior can be customized via the alternate method variants
    +1652   * listed below.</p>
    +1653   *
    +1654   * @see #streamFields(Descriptor, Optional, Boolean) for the opportunity to control recursive crawling.
    +1655   *
    +1656   * @param descriptor Schema descriptor to crawl model definitions on.
    +1657   * @param predicate Filter predicate function, if applicable.
    +1658   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1659   */
    +1660  public static @Nonnull Stream<FieldPointer> streamFields(@Nonnull Descriptor descriptor,
    +1661                                                           @Nonnull Predicate<FieldPointer> predicate) {
    +1662    return streamFields(descriptor, Optional.of(predicate), true);
    +1663  }
    +1664
    +1665  /**
    +1666   * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run
    +1667   * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly.
    +1668   *
    +1669   * <p>This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search
    +1670   * is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate
    +1671   * is provided, the entire set of recursive effective fields is returned from the provided descriptor.</p>
    +1672   *
    +1673   * @see #streamFields(Descriptor) for the cleanest invocation of this method.
    +1674   *
    +1675   * @param descriptor Schema descriptor to crawl model definitions on.
    +1676   * @param predicate Filter predicate function, if applicable.
    +1677   * @param recursive Whether to descend to sub-models recursively.
    +1678   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1679   */
    +1680  public static @Nonnull Stream<FieldPointer> streamFields(@Nonnull Descriptor descriptor,
    +1681                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1682                                                           @Nonnull Boolean recursive) {
    +1683    Objects.requireNonNull(recursive, "cannot pass `null` for recursive switch");
    +1684
    +1685    return streamFields(
    +1686      descriptor,
    +1687      predicate,
    +1688      (field) -> recursive
    +1689    );
    +1690  }
    +1691
    +1692  /**
    +1693   * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run
    +1694   * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. By
    +1695   * default, all field streaming methods run in parallel.
    +1696   *
    +1697   * <p>If a `MESSAGE` field is encountered and the algorithm needs to decide whether to recurse, this variant includes
    +1698   * support for the `decider` function. `decider` is invoked to decide whether to recurse for opportunity to do so.</p>
    +1699   *
    +1700   * <p>This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search
    +1701   * is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate
    +1702   * is provided, the entire set of recursive effective fields is returned from the provided descriptor.</p>
    +1703   *
    +1704   * @see #streamFields(Descriptor) for the cleanest invocation of this method.
    +1705   *
    +1706   * @param descriptor Schema descriptor to crawl model definitions on.
    +1707   * @param predicate Filter predicate function, if applicable.
    +1708   * @param decider Function that decides whether to recurse.
    +1709   * @return Stream of field descriptors, recursively, which match the `predicate`, if provided.
    +1710   */
    +1711  public static @Nonnull Stream<FieldPointer> streamFields(@Nonnull Descriptor descriptor,
    +1712                                                           @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1713                                                           @Nonnull Predicate<FieldPointer> decider) {
    +1714    Objects.requireNonNull(descriptor);
    +1715    Objects.requireNonNull(predicate);
    +1716
    +1717    return streamFieldsRecursive(
    +1718      descriptor,
    +1719      descriptor,
    +1720      predicate,
    +1721      decider,
    +1722      "",
    +1723      true
    +1724    );
    +1725  }
    +1726
    +1727  private static @Nonnull Stream<FieldPointer> streamFieldsRecursive(
    +1728    @Nonnull Descriptor base,
    +1729    @Nonnull Descriptor descriptor,
    +1730    @Nonnull Optional<Predicate<FieldPointer>> predicate,
    +1731    @Nonnull Predicate<FieldPointer> decider,
    +1732    @Nonnull String parent,
    +1733    @Nonnull Boolean parallel) {
    +1734    return (parallel ? descriptor.getFields().parallelStream() : descriptor.getFields().stream()).flatMap((field) -> {
    +1735      var path = String.format("%s.%s", parent, field.getName());
    +1736      var pointer = new FieldPointer(
    +1737        base,
    +1738        parent,
    +1739        path,
    +1740        field
    +1741      );
    +1742
    +1743      var branch = Stream.of(pointer);
    +1744      if (field.getType() == FieldDescriptor.Type.MESSAGE
    +1745          && !field.getMessageType().getFullName().equals(field.getContainingType().getFullName())
    +1746          && decider.test(pointer)) {
    +1747        return Stream.concat(branch, streamFieldsRecursive(
    +1748          base,
    +1749          descriptor.findFieldByNumber(field.getNumber()).getMessageType(),
    +1750          predicate,
    +1751          decider,
    +1752          path,
    +1753          parallel
    +1754        ));
    +1755      }
    +1756      return branch;
    +1757
    +1758    }).filter((field) ->
    +1759      predicate.map(fieldDescriptorPredicate ->
    +1760        fieldDescriptorPredicate.test(field)).orElse(true)
    +1761
    +1762    );
    +1763  }
    +1764}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/ModelSerializer.EnumSerializeMode.html b/docs/java/src-html/gust/backend/model/ModelSerializer.EnumSerializeMode.html new file mode 100644 index 000000000..b5e6235a3 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/ModelSerializer.EnumSerializeMode.html @@ -0,0 +1,164 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.protobuf.Message;
    +016
    +017import javax.annotation.Nonnull;
    +018import java.io.IOException;
    +019
    +020
    +021/**
    +022 * Describes the surface interface of an object responsible for <i>serializing</i> business data objects (hereinafter,
    +023 * "models"). In other words, converting {@link Message} instances into some generic type <pre>Output</pre>.
    +024 *
    +025 * @param <Model> Data model which a given serializer implementation is responsible for adapting.
    +026 * @param <Output> Output type which the serializer will provide when invoked with a matching model instance.
    +027 */
    +028public interface ModelSerializer<Model extends Message, Output> {
    +029  /**
    +030   * Describes available <i>write dispositions</i>, each of which presents a strategy that governs how an individual
    +031   * write operation is handled with regard to underlying storage. Each option is explained in its own documentation.
    +032   */
    +033  enum WriteDisposition {
    +034    /** Blind writes. Just applies the write without regard to side effects. */
    +035    BLIND,
    +036
    +037    /** Create-style writes. Guarantees the object does not exist before writing. */
    +038    CREATE,
    +039
    +040    /** Update-style writes. Guarantees the object <i>does</i> exist before writing. */
    +041    UPDATE
    +042  }
    +043
    +044  /**
    +045   * Enumerates modes for encoding enums. In NUMERIC mode, enumerated entry ID numbers are used when serializing enum
    +046   * values. In NAME mode, the string name is used. Both are valid for read operations.
    +047   */
    +048  enum EnumSerializeMode {
    +049    /** Encode enum values as their numeric ID. */
    +050    NUMERIC,
    +051
    +052    /** Encode enum values as their string name. */
    +053    NAME
    +054  }
    +055
    +056  /**
    +057   * Enumerates modes for encoding timestamps. In TIMESTAMP mode, numeric timestamps with millisecond precision (since
    +058   * the Unix epoch) are provided. In ISO8601 mode, ISO8601-formatted strings are provided.
    +059   */
    +060  enum InstantSerializeMode {
    +061    /** Encode temporal instants as millisecond-precision Unix timestamps. */
    +062    TIMESTAMP,
    +063
    +064    /** Encode temporal instants as ISO8601-formatted strings. */
    +065    ISO8601
    +066  }
    +067
    +068  /** Describes errors that occur during model serialization activities. */
    +069  final class SerializationError extends RuntimeException {
    +070    /**
    +071     * Create a generic serialization error from the provided message.
    +072     *
    +073     * @param message Error message.
    +074     */
    +075    SerializationError(@Nonnull String message) {
    +076      super(message);
    +077    }
    +078  }
    +079
    +080  /**
    +081   * Serialize a model instance from the provided object type to the specified output type, throwing exceptions
    +082   * verbosely if we are unable to correctly, verifiably, and properly export the record.
    +083   *
    +084   * @param input Input record object to serialize.
    +085   * @return Serialized record data, of the specified output type.
    +086   * @throws ModelDeflateException If the model fails to export or serialize for any reason.
    +087   * @throws IOException If an IO error of some kind occurs.
    +088   */
    +089  @Nonnull Output deflate(@Nonnull Model input) throws ModelDeflateException, IOException;
    +090}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/ModelSerializer.InstantSerializeMode.html b/docs/java/src-html/gust/backend/model/ModelSerializer.InstantSerializeMode.html new file mode 100644 index 000000000..b5e6235a3 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/ModelSerializer.InstantSerializeMode.html @@ -0,0 +1,164 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.protobuf.Message;
    +016
    +017import javax.annotation.Nonnull;
    +018import java.io.IOException;
    +019
    +020
    +021/**
    +022 * Describes the surface interface of an object responsible for <i>serializing</i> business data objects (hereinafter,
    +023 * "models"). In other words, converting {@link Message} instances into some generic type <pre>Output</pre>.
    +024 *
    +025 * @param <Model> Data model which a given serializer implementation is responsible for adapting.
    +026 * @param <Output> Output type which the serializer will provide when invoked with a matching model instance.
    +027 */
    +028public interface ModelSerializer<Model extends Message, Output> {
    +029  /**
    +030   * Describes available <i>write dispositions</i>, each of which presents a strategy that governs how an individual
    +031   * write operation is handled with regard to underlying storage. Each option is explained in its own documentation.
    +032   */
    +033  enum WriteDisposition {
    +034    /** Blind writes. Just applies the write without regard to side effects. */
    +035    BLIND,
    +036
    +037    /** Create-style writes. Guarantees the object does not exist before writing. */
    +038    CREATE,
    +039
    +040    /** Update-style writes. Guarantees the object <i>does</i> exist before writing. */
    +041    UPDATE
    +042  }
    +043
    +044  /**
    +045   * Enumerates modes for encoding enums. In NUMERIC mode, enumerated entry ID numbers are used when serializing enum
    +046   * values. In NAME mode, the string name is used. Both are valid for read operations.
    +047   */
    +048  enum EnumSerializeMode {
    +049    /** Encode enum values as their numeric ID. */
    +050    NUMERIC,
    +051
    +052    /** Encode enum values as their string name. */
    +053    NAME
    +054  }
    +055
    +056  /**
    +057   * Enumerates modes for encoding timestamps. In TIMESTAMP mode, numeric timestamps with millisecond precision (since
    +058   * the Unix epoch) are provided. In ISO8601 mode, ISO8601-formatted strings are provided.
    +059   */
    +060  enum InstantSerializeMode {
    +061    /** Encode temporal instants as millisecond-precision Unix timestamps. */
    +062    TIMESTAMP,
    +063
    +064    /** Encode temporal instants as ISO8601-formatted strings. */
    +065    ISO8601
    +066  }
    +067
    +068  /** Describes errors that occur during model serialization activities. */
    +069  final class SerializationError extends RuntimeException {
    +070    /**
    +071     * Create a generic serialization error from the provided message.
    +072     *
    +073     * @param message Error message.
    +074     */
    +075    SerializationError(@Nonnull String message) {
    +076      super(message);
    +077    }
    +078  }
    +079
    +080  /**
    +081   * Serialize a model instance from the provided object type to the specified output type, throwing exceptions
    +082   * verbosely if we are unable to correctly, verifiably, and properly export the record.
    +083   *
    +084   * @param input Input record object to serialize.
    +085   * @return Serialized record data, of the specified output type.
    +086   * @throws ModelDeflateException If the model fails to export or serialize for any reason.
    +087   * @throws IOException If an IO error of some kind occurs.
    +088   */
    +089  @Nonnull Output deflate(@Nonnull Model input) throws ModelDeflateException, IOException;
    +090}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/ModelSerializer.SerializationError.html b/docs/java/src-html/gust/backend/model/ModelSerializer.SerializationError.html new file mode 100644 index 000000000..b5e6235a3 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/ModelSerializer.SerializationError.html @@ -0,0 +1,164 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.protobuf.Message;
    +016
    +017import javax.annotation.Nonnull;
    +018import java.io.IOException;
    +019
    +020
    +021/**
    +022 * Describes the surface interface of an object responsible for <i>serializing</i> business data objects (hereinafter,
    +023 * "models"). In other words, converting {@link Message} instances into some generic type <pre>Output</pre>.
    +024 *
    +025 * @param <Model> Data model which a given serializer implementation is responsible for adapting.
    +026 * @param <Output> Output type which the serializer will provide when invoked with a matching model instance.
    +027 */
    +028public interface ModelSerializer<Model extends Message, Output> {
    +029  /**
    +030   * Describes available <i>write dispositions</i>, each of which presents a strategy that governs how an individual
    +031   * write operation is handled with regard to underlying storage. Each option is explained in its own documentation.
    +032   */
    +033  enum WriteDisposition {
    +034    /** Blind writes. Just applies the write without regard to side effects. */
    +035    BLIND,
    +036
    +037    /** Create-style writes. Guarantees the object does not exist before writing. */
    +038    CREATE,
    +039
    +040    /** Update-style writes. Guarantees the object <i>does</i> exist before writing. */
    +041    UPDATE
    +042  }
    +043
    +044  /**
    +045   * Enumerates modes for encoding enums. In NUMERIC mode, enumerated entry ID numbers are used when serializing enum
    +046   * values. In NAME mode, the string name is used. Both are valid for read operations.
    +047   */
    +048  enum EnumSerializeMode {
    +049    /** Encode enum values as their numeric ID. */
    +050    NUMERIC,
    +051
    +052    /** Encode enum values as their string name. */
    +053    NAME
    +054  }
    +055
    +056  /**
    +057   * Enumerates modes for encoding timestamps. In TIMESTAMP mode, numeric timestamps with millisecond precision (since
    +058   * the Unix epoch) are provided. In ISO8601 mode, ISO8601-formatted strings are provided.
    +059   */
    +060  enum InstantSerializeMode {
    +061    /** Encode temporal instants as millisecond-precision Unix timestamps. */
    +062    TIMESTAMP,
    +063
    +064    /** Encode temporal instants as ISO8601-formatted strings. */
    +065    ISO8601
    +066  }
    +067
    +068  /** Describes errors that occur during model serialization activities. */
    +069  final class SerializationError extends RuntimeException {
    +070    /**
    +071     * Create a generic serialization error from the provided message.
    +072     *
    +073     * @param message Error message.
    +074     */
    +075    SerializationError(@Nonnull String message) {
    +076      super(message);
    +077    }
    +078  }
    +079
    +080  /**
    +081   * Serialize a model instance from the provided object type to the specified output type, throwing exceptions
    +082   * verbosely if we are unable to correctly, verifiably, and properly export the record.
    +083   *
    +084   * @param input Input record object to serialize.
    +085   * @return Serialized record data, of the specified output type.
    +086   * @throws ModelDeflateException If the model fails to export or serialize for any reason.
    +087   * @throws IOException If an IO error of some kind occurs.
    +088   */
    +089  @Nonnull Output deflate(@Nonnull Model input) throws ModelDeflateException, IOException;
    +090}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/ModelSerializer.WriteDisposition.html b/docs/java/src-html/gust/backend/model/ModelSerializer.WriteDisposition.html new file mode 100644 index 000000000..b5e6235a3 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/ModelSerializer.WriteDisposition.html @@ -0,0 +1,164 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.protobuf.Message;
    +016
    +017import javax.annotation.Nonnull;
    +018import java.io.IOException;
    +019
    +020
    +021/**
    +022 * Describes the surface interface of an object responsible for <i>serializing</i> business data objects (hereinafter,
    +023 * "models"). In other words, converting {@link Message} instances into some generic type <pre>Output</pre>.
    +024 *
    +025 * @param <Model> Data model which a given serializer implementation is responsible for adapting.
    +026 * @param <Output> Output type which the serializer will provide when invoked with a matching model instance.
    +027 */
    +028public interface ModelSerializer<Model extends Message, Output> {
    +029  /**
    +030   * Describes available <i>write dispositions</i>, each of which presents a strategy that governs how an individual
    +031   * write operation is handled with regard to underlying storage. Each option is explained in its own documentation.
    +032   */
    +033  enum WriteDisposition {
    +034    /** Blind writes. Just applies the write without regard to side effects. */
    +035    BLIND,
    +036
    +037    /** Create-style writes. Guarantees the object does not exist before writing. */
    +038    CREATE,
    +039
    +040    /** Update-style writes. Guarantees the object <i>does</i> exist before writing. */
    +041    UPDATE
    +042  }
    +043
    +044  /**
    +045   * Enumerates modes for encoding enums. In NUMERIC mode, enumerated entry ID numbers are used when serializing enum
    +046   * values. In NAME mode, the string name is used. Both are valid for read operations.
    +047   */
    +048  enum EnumSerializeMode {
    +049    /** Encode enum values as their numeric ID. */
    +050    NUMERIC,
    +051
    +052    /** Encode enum values as their string name. */
    +053    NAME
    +054  }
    +055
    +056  /**
    +057   * Enumerates modes for encoding timestamps. In TIMESTAMP mode, numeric timestamps with millisecond precision (since
    +058   * the Unix epoch) are provided. In ISO8601 mode, ISO8601-formatted strings are provided.
    +059   */
    +060  enum InstantSerializeMode {
    +061    /** Encode temporal instants as millisecond-precision Unix timestamps. */
    +062    TIMESTAMP,
    +063
    +064    /** Encode temporal instants as ISO8601-formatted strings. */
    +065    ISO8601
    +066  }
    +067
    +068  /** Describes errors that occur during model serialization activities. */
    +069  final class SerializationError extends RuntimeException {
    +070    /**
    +071     * Create a generic serialization error from the provided message.
    +072     *
    +073     * @param message Error message.
    +074     */
    +075    SerializationError(@Nonnull String message) {
    +076      super(message);
    +077    }
    +078  }
    +079
    +080  /**
    +081   * Serialize a model instance from the provided object type to the specified output type, throwing exceptions
    +082   * verbosely if we are unable to correctly, verifiably, and properly export the record.
    +083   *
    +084   * @param input Input record object to serialize.
    +085   * @return Serialized record data, of the specified output type.
    +086   * @throws ModelDeflateException If the model fails to export or serialize for any reason.
    +087   * @throws IOException If an IO error of some kind occurs.
    +088   */
    +089  @Nonnull Output deflate(@Nonnull Model input) throws ModelDeflateException, IOException;
    +090}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/ModelSerializer.html b/docs/java/src-html/gust/backend/model/ModelSerializer.html new file mode 100644 index 000000000..b5e6235a3 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/ModelSerializer.html @@ -0,0 +1,164 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.protobuf.Message;
    +016
    +017import javax.annotation.Nonnull;
    +018import java.io.IOException;
    +019
    +020
    +021/**
    +022 * Describes the surface interface of an object responsible for <i>serializing</i> business data objects (hereinafter,
    +023 * "models"). In other words, converting {@link Message} instances into some generic type <pre>Output</pre>.
    +024 *
    +025 * @param <Model> Data model which a given serializer implementation is responsible for adapting.
    +026 * @param <Output> Output type which the serializer will provide when invoked with a matching model instance.
    +027 */
    +028public interface ModelSerializer<Model extends Message, Output> {
    +029  /**
    +030   * Describes available <i>write dispositions</i>, each of which presents a strategy that governs how an individual
    +031   * write operation is handled with regard to underlying storage. Each option is explained in its own documentation.
    +032   */
    +033  enum WriteDisposition {
    +034    /** Blind writes. Just applies the write without regard to side effects. */
    +035    BLIND,
    +036
    +037    /** Create-style writes. Guarantees the object does not exist before writing. */
    +038    CREATE,
    +039
    +040    /** Update-style writes. Guarantees the object <i>does</i> exist before writing. */
    +041    UPDATE
    +042  }
    +043
    +044  /**
    +045   * Enumerates modes for encoding enums. In NUMERIC mode, enumerated entry ID numbers are used when serializing enum
    +046   * values. In NAME mode, the string name is used. Both are valid for read operations.
    +047   */
    +048  enum EnumSerializeMode {
    +049    /** Encode enum values as their numeric ID. */
    +050    NUMERIC,
    +051
    +052    /** Encode enum values as their string name. */
    +053    NAME
    +054  }
    +055
    +056  /**
    +057   * Enumerates modes for encoding timestamps. In TIMESTAMP mode, numeric timestamps with millisecond precision (since
    +058   * the Unix epoch) are provided. In ISO8601 mode, ISO8601-formatted strings are provided.
    +059   */
    +060  enum InstantSerializeMode {
    +061    /** Encode temporal instants as millisecond-precision Unix timestamps. */
    +062    TIMESTAMP,
    +063
    +064    /** Encode temporal instants as ISO8601-formatted strings. */
    +065    ISO8601
    +066  }
    +067
    +068  /** Describes errors that occur during model serialization activities. */
    +069  final class SerializationError extends RuntimeException {
    +070    /**
    +071     * Create a generic serialization error from the provided message.
    +072     *
    +073     * @param message Error message.
    +074     */
    +075    SerializationError(@Nonnull String message) {
    +076      super(message);
    +077    }
    +078  }
    +079
    +080  /**
    +081   * Serialize a model instance from the provided object type to the specified output type, throwing exceptions
    +082   * verbosely if we are unable to correctly, verifiably, and properly export the record.
    +083   *
    +084   * @param input Input record object to serialize.
    +085   * @return Serialized record data, of the specified output type.
    +086   * @throws ModelDeflateException If the model fails to export or serialize for any reason.
    +087   * @throws IOException If an IO error of some kind occurs.
    +088   */
    +089  @Nonnull Output deflate(@Nonnull Model input) throws ModelDeflateException, IOException;
    +090}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/ModelWriteConflict.html b/docs/java/src-html/gust/backend/model/ModelWriteConflict.html new file mode 100644 index 000000000..7b4608c1f --- /dev/null +++ b/docs/java/src-html/gust/backend/model/ModelWriteConflict.html @@ -0,0 +1,122 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.protobuf.Message;
    +016import javax.annotation.Nonnull;
    +017import javax.annotation.Nullable;
    +018
    +019
    +020/** Thrown when a write operation fails, because of some conflict situation. */
    +021@SuppressWarnings("WeakerAccess")
    +022public final class ModelWriteConflict extends ModelWriteFailure {
    +023  /** Expectation that was violated during the write operation. */
    +024  private final @Nonnull WriteOptions.WriteDisposition failedExpectation;
    +025
    +026  /**
    +027   * Create a model write exception with a throwable as a cause.
    +028   *
    +029   * @param key   Key for the record that failed to write.
    +030   * @param model Model that failed to write.
    +031   * @param expectation Expectation that failed to be met.
    +032   */
    +033  public ModelWriteConflict(@Nullable Object key,
    +034                            @Nonnull Message model,
    +035                            @Nonnull WriteOptions.WriteDisposition expectation) {
    +036    super(key, model, String.format(
    +037      "Cannot write to the specified model. Key %s did not meet expectation %s.",
    +038      key,
    +039      expectation.name()));
    +040    this.failedExpectation = expectation;
    +041  }
    +042
    +043  // -- Getters -- //
    +044  /** @return Expectation that was violated, when the error was encountered. */
    +045  public @Nonnull WriteOptions.WriteDisposition getFailedExpectation() {
    +046    return failedExpectation;
    +047  }
    +048}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/ModelWriteFailure.html b/docs/java/src-html/gust/backend/model/ModelWriteFailure.html new file mode 100644 index 000000000..d270692bd --- /dev/null +++ b/docs/java/src-html/gust/backend/model/ModelWriteFailure.html @@ -0,0 +1,169 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.protobuf.Message;
    +016
    +017import javax.annotation.Nonnull;
    +018import javax.annotation.Nullable;
    +019
    +020
    +021/** Thrown when a model fails to write. Should be extended by more specific error cases. */
    +022@SuppressWarnings("unused")
    +023public class ModelWriteFailure extends PersistenceException {
    +024  /** Key for the model that failed to write. */
    +025  private final @Nullable Object key;
    +026
    +027  /** Model that failed to write. */
    +028  private final @Nonnull Message model;
    +029
    +030  /**
    +031   * Create a model write exception with a throwable as a cause.
    +032   *
    +033   * @param key Key for the record that failed to write.
    +034   * @param model Model that failed to write.
    +035   */
    +036  ModelWriteFailure(@Nullable Object key, @Nonnull Message model) {
    +037    super(String.format("Failed to write model at key '%s'.", key));
    +038    this.key = key;
    +039    this.model = model;
    +040  }
    +041
    +042  /**
    +043   * Create a model write exception with a throwable as a cause.
    +044   *
    +045   * @param key Key for the record that failed to write.
    +046   * @param model Model that failed to write.
    +047   * @param cause Cause for the error.
    +048   */
    +049  ModelWriteFailure(@Nullable Object key, @Nonnull Message model, @Nonnull Throwable cause) {
    +050    super(String.format("Failed to write model at key '%s': %s.", key, cause.getMessage()), cause);
    +051    this.key = key;
    +052    this.model = model;
    +053  }
    +054
    +055  /**
    +056   * Create a model write exception with a custom error message.
    +057   *
    +058   * @param key Key for the record that failed to write.
    +059   * @param model Model that failed to write.
    +060   * @param errorMessage Custom error message.
    +061   */
    +062  ModelWriteFailure(@Nullable Object key, @Nonnull Message model, @Nonnull String errorMessage) {
    +063    super(errorMessage);
    +064    this.key = key;
    +065    this.model = model;
    +066  }
    +067
    +068  /**
    +069   * Create a model write exception with a throwable and a custom error message.
    +070   *
    +071   * @param key Key for the record that failed to write.
    +072   * @param model Model that failed to write.
    +073   * @param cause Cause for the error.
    +074   * @param errorMessage Custom error message.
    +075   */
    +076  ModelWriteFailure(@Nullable Object key,
    +077                    @Nonnull Message model,
    +078                    @Nonnull Throwable cause,
    +079                    @Nonnull String errorMessage) {
    +080    super(errorMessage, cause);
    +081    this.key = key;
    +082    this.model = model;
    +083  }
    +084
    +085  // -- Getters -- //
    +086  /** @return Key for the model that failed to write. */
    +087  public @Nullable Object getKey() {
    +088    return key;
    +089  }
    +090
    +091  /** @return Model that failed to write. */
    +092  public @Nonnull Message getModel() {
    +093    return model;
    +094  }
    +095}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/OperationOptions.html b/docs/java/src-html/gust/backend/model/OperationOptions.html new file mode 100644 index 000000000..43e54f603 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/OperationOptions.html @@ -0,0 +1,136 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.common.util.concurrent.ListeningScheduledExecutorService;
    +016
    +017import javax.annotation.Nonnull;
    +018import java.util.Optional;
    +019import java.util.concurrent.TimeUnit;
    +020
    +021
    +022/**
    +023 * Operational options that can be applied to individual calls into the {@link ModelAdapter} framework. See individual
    +024 * options interfaces for more information.
    +025*/
    +026@SuppressWarnings("UnstableApiUsage")
    +027public interface OperationOptions {
    +028  /** @return Value to apply to the operation timeout. If left unspecified, the global default is used. */
    +029  default @Nonnull Optional<Long> timeoutValue() {
    +030    return Optional.empty();
    +031  }
    +032
    +033  /** @return Unit to apply to the operation timeout. If left unspecified, the global default is used. */
    +034  default @Nonnull Optional<TimeUnit> timeoutUnit() {
    +035    return Optional.empty();
    +036  }
    +037
    +038  /** @return Executor service that should be used for calls that reference this option set. */
    +039  default @Nonnull Optional<ListeningScheduledExecutorService> executorService() {
    +040    return Optional.empty();
    +041  }
    +042
    +043  /** @return Set a precondition for the precise time (in microseconds) that a record was updated. */
    +044  default @Nonnull Optional<Long> updatedAtMicros() {
    +045    return Optional.empty();
    +046  }
    +047
    +048  /** @return Set a precondition for the precise time (in seconds) that a record was updated. */
    +049  default @Nonnull Optional<Long> updatedAtSeconds() {
    +050    return Optional.empty();
    +051  }
    +052
    +053  /** @return Number of retries, otherwise the default is used. */
    +054  default @Nonnull Optional<Integer> retries() {
    +055    return Optional.empty();
    +056  }
    +057
    +058  /** @return Whether to run in a transaction. */
    +059  default @Nonnull Optional<Boolean> transactional() {
    +060    return Optional.empty();
    +061  }
    +062}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/PersistenceDriver.Internals.html b/docs/java/src-html/gust/backend/model/PersistenceDriver.Internals.html new file mode 100644 index 000000000..1da80f826 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/PersistenceDriver.Internals.html @@ -0,0 +1,993 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.common.annotations.VisibleForTesting;
    +016import com.google.common.collect.ImmutableSet;
    +017import com.google.common.util.concurrent.ListenableFuture;
    +018import com.google.common.util.concurrent.ListeningScheduledExecutorService;
    +019import com.google.errorprone.annotations.CanIgnoreReturnValue;
    +020import com.google.protobuf.Descriptors.FieldDescriptor;
    +021import com.google.protobuf.FieldMask;
    +022import com.google.protobuf.Message;
    +023import gust.backend.runtime.Logging;
    +024import gust.backend.runtime.ReactiveFuture;
    +025import org.reactivestreams.Publisher;
    +026import org.slf4j.Logger;
    +027import tools.elide.core.DatapointType;
    +028import tools.elide.core.FieldType;
    +029
    +030import javax.annotation.Nonnull;
    +031import javax.annotation.Nullable;
    +032import javax.annotation.OverridingMethodsMustInvokeSuper;
    +033import javax.annotation.concurrent.Immutable;
    +034import javax.annotation.concurrent.ThreadSafe;
    +035import java.util.*;
    +036import java.util.concurrent.*;
    +037
    +038import static java.lang.String.format;
    +039import static gust.backend.model.ModelMetadata.*;
    +040
    +041
    +042/**
    +043 * Describes the surface of a generic persistence driver, which is capable of accepting arbitrary structured and typed
    +044 * business data (also called "data models"), and managing them with regard to persistent storage, which includes
    +045 * storing them when asked, and recalling them when subsequently asked to do so.
    +046 *
    +047 * <p>Persistence driver implementations do not always guarantee <i>durability</i> of data. For example,
    +048 * {@link CacheDriver} implementations are also {@link PersistenceDriver}s, and that entire class of implementations
    +049 * does not guarantee data will be there when you ask for it <i>at all</i> (relying on cache state is generally
    +050 * considered to be a very bad practice).</p>
    +051 *
    +052 * <p>Other implementation trees exist (notably, {@link DatabaseDriver}) which go the other way, and are expected to
    +053 * guarantee durability of data across restarts, distributed systems and networks, and failure cases, as applicable.
    +054 * Database driver implementations also support richer data storage features like querying and indexing.</p>
    +055 *
    +056 * @see CacheDriver <pre>`CacheDriver`</pre> for persistence drivers with volatile durability guarantees
    +057 * @see DatabaseDriver <pre>`DatabaseDriver`</pre> for drivers with rich features and/or strong durability guarantees.
    +058 * @param <Key> Key record type (must be annotated with model role {@code OBJECT_KEY}).
    +059 * @param <Model> Message/model type which this persistence driver is specialized for.
    +060 * @param <ReadIntermediate> Intermediate record format used by the underlying driver implementation during model
    +061 *                           de-serialization.
    +062 * @param <WriteIntermediate> Intermediate record format used by the underlying driver implementation during model
    +063 *                           serialization.
    +064 */
    +065@Immutable
    +066@ThreadSafe
    +067@SuppressWarnings({"unused", "UnstableApiUsage"})
    +068public interface PersistenceDriver<Key extends Message, Model extends Message, ReadIntermediate, WriteIntermediate> {
    +069  /** Default timeout to apply when otherwise unspecified. */
    +070  long DEFAULT_TIMEOUT = 30;
    +071
    +072  /** Time units for {@link #DEFAULT_TIMEOUT}. */
    +073  TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS;
    +074
    +075  /** Default timeout to apply when fetching from the cache. */
    +076  long DEFAULT_CACHE_TIMEOUT = 5;
    +077
    +078  /** Time units for {@link #DEFAULT_CACHE_TIMEOUT}. */
    +079  TimeUnit DEFAULT_CACHE_TIMEOUT_UNIT = TimeUnit.SECONDS;
    +080
    +081  /** Default model adapter internals. */
    +082  @SuppressWarnings("SameParameterValue")
    +083  final class Internals {
    +084    /** Log pipe for default model adapter. */
    +085    static final Logger logging = Logging.logger(PersistenceDriver.class);
    +086
    +087    private Internals() { /* Disallow instantiation. */ }
    +088
    +089    /** Runnable that might throw async exceptions. */
    +090    @FunctionalInterface
    +091    interface DriverRunnable {
    +092      /**
    +093       * Run some operation that may throw async-style exceptions.
    +094       *
    +095       * @throws TimeoutException The operation timed out.
    +096       * @throws InterruptedException The operation was interrupted during execution.
    +097       * @throws ExecutionException An execution error halted async execution.
    +098       */
    +099      void run() throws TimeoutException, InterruptedException, ExecutionException;
    +100    }
    +101
    +102    /**
    +103     * Swallow any exceptions that occur
    +104     *
    +105     * @param operation Operation to run and wrap.
    +106     */
    +107    static void swallowExceptions(@Nonnull DriverRunnable operation) {
    +108      try {
    +109        operation.run();
    +110
    +111      } catch (Exception exc) {
    +112        Throwable inner = exc.getCause() != null ? exc.getCause() : exc;
    +113        logging.warn(format(
    +114          "Encountered unidentified exception '%s'. Message: '%s'.",
    +115          exc.getClass().getSimpleName(), exc.getMessage()));
    +116
    +117      }
    +118    }
    +119
    +120    /**
    +121     * Convert async exceptions into persistence layer exceptions, according to the failure that occurred. Also print a
    +122     * descriptive log statement.
    +123     *
    +124     * @param operation Operation to execute and wrap with protection.
    +125     * @param <R> Return type for the callable operation, if applicable.
    +126     * @return Return value of the async operation.
    +127     */
    +128    @CanIgnoreReturnValue
    +129    static <R> R convertAsyncExceptions(@Nonnull Callable<R> operation) {
    +130      try {
    +131        return operation.call();
    +132      } catch (InterruptedException ixe) {
    +133        logging.warn(format("Interrupted. Message: '%s'.",
    +134          ixe.getMessage()));
    +135        throw PersistenceOperationFailed.forErr(PersistenceFailure.INTERRUPTED);
    +136
    +137      } catch (ExecutionException exe) {
    +138        Throwable inner = exe.getCause() != null ? exe.getCause() : exe;
    +139        logging.warn(format("Encountered async exception '%s'. Message: '%s'.",
    +140          inner.getClass().getSimpleName(), inner.getMessage()));
    +141        throw PersistenceOperationFailed.forErr(PersistenceFailure.INTERNAL);
    +142
    +143      } catch (TimeoutException txe) {
    +144        throw PersistenceOperationFailed.forErr(PersistenceFailure.TIMEOUT);
    +145      } catch (Exception exc) {
    +146        logging.warn(format(
    +147          "Encountered unidentified exception '%s'. Message: '%s'.",
    +148          exc.getClass().getSimpleName(), exc.getMessage()));
    +149        throw PersistenceOperationFailed.forErr(PersistenceFailure.INTERNAL,
    +150          exc.getCause() != null ? exc.getCause() : exc);
    +151
    +152      }
    +153    }
    +154
    +155    /**
    +156     * Enforce that a particular model operation have the provided value present, and equal to the expected value. If
    +157     * these expectations are violated, an exception is thrown.
    +158     *
    +159     * @param value Value in the option set for this method.
    +160     * @param expected Expected value from the option set.
    +161     * @param expectation Message to throw if the expectation is violated.
    +162     * @param <R> Return value type - same as {@code value} and {@code expected}.
    +163     * @return Expected value if it is equal to {@code value}.
    +164     */
    +165    @CanIgnoreReturnValue
    +166    static <R> R enforceOption(@Nullable R value, @Nonnull R expected, @Nonnull String expectation) {
    +167      if (value != null && value.equals(expected)) {
    +168        return value;
    +169      }
    +170      throw new IllegalArgumentException("Operation failed: " + expectation);
    +171    }
    +172  }
    +173
    +174  // -- API: Execution -- //
    +175  /**
    +176   * Resolve an executor service for use with this persistence driver. Operations will be executed against this as they
    +177   * are received.
    +178   *
    +179   * @return Scheduled executor service.
    +180   */
    +181  @Nonnull ListeningScheduledExecutorService executorService();
    +182
    +183  // -- API: Codec -- //
    +184  /**
    +185   * Acquire an instance of the codec used by this adapter. Codecs are either injected/otherwise provided during adapter
    +186   * construction, or they are specified statically if the adapter depends on a specific codec.
    +187   *
    +188   * @return Model codec currently in use by this adapter.
    +189   */
    +190  @Nonnull ModelCodec<Model, WriteIntermediate, ReadIntermediate> codec();
    +191
    +192  // -- API: Key Generation -- //
    +193  /**
    +194   * Generate a semi-random opaque token, usable as an ID for a newly-created entity via the model layer. In this case,
    +195   * the ID is returned directly, so it may be used to populate a key.
    +196   *
    +197   * @param instance Model instance to generate an ID for.
    +198   * @return Generated opaque string ID.
    +199   */
    +200  default @Nonnull String generateId(@Nonnull Message instance) {
    +201    return UUID.randomUUID().toString();
    +202  }
    +203
    +204  /**
    +205   * Generate a key for a new entity, which must be stored by this driver, but does not yet have a key. If the driver
    +206   * does not support key generation, {@link UnsupportedOperationException} is thrown.
    +207   *
    +208   * <p>Generated keys are expected to be best-effort unique. Generally, Java's built-in {@link java.util.UUID} should
    +209   * do the trick just fine. In more complex or scalable circumstances, this method can be overridden to reach out to
    +210   * the data engine to generate a key.</p>
    +211   *
    +212   * @param instance Default instance of the model type for which a key is desired.
    +213   * @return Generated key for an entity to be stored.
    +214   */
    +215  default @Nonnull Key generateKey(@Nonnull Message instance) {
    +216    // enforce role, key field presence
    +217    var descriptor = instance.getDescriptorForType();
    +218    enforceRole(descriptor, DatapointType.OBJECT);
    +219    var keyType = keyField(descriptor);
    +220    if (keyType.isEmpty()) throw new MissingAnnotatedField(descriptor, FieldType.KEY);
    +221
    +222    // convert to builder, grab field builder for key (keys must be top-level fields)
    +223    var builder = instance.newBuilderForType();
    +224    var keyBuilder = builder.getFieldBuilder(keyType.get().getField());
    +225    spliceIdBuilder(keyBuilder, Optional.of(generateId(instance)));
    +226
    +227    //noinspection unchecked
    +228    Key obj = (Key)keyBuilder.build();
    +229    if (Internals.logging.isDebugEnabled()) {
    +230      Internals.logging.debug(format("Generated key for record: '%s'.", obj.toString()));
    +231    }
    +232    return obj;
    +233  }
    +234
    +235  // -- API: Projections & Field Masking -- //
    +236  /**
    +237   * Apply the fields from {@code source} to {@code target}, considering any provided {@link FieldMask}.
    +238   *
    +239   * <p>If the invoking developer chooses to provide {@code markedPaths}, they must also supply {@code markEffect}. For
    +240   * each field encountered that matches a property path in {@code markedPaths}, {@code markEffect} is applied. This
    +241   * happens recursively for the entire model tree of {@code source} (and, consequently, {@code target}).</p>
    +242   *
    +243   * <p>After all field computations are complete, the builder is built (and casted, if necessary), before being handed
    +244   * back to invoking code.</p>
    +245   *
    +246   * @see FetchOptions.MaskMode Determines how "marked" fields are treated.
    +247   * @param target Builder to set each field value on, as appropriate.
    +248   * @param source Source instance to pull fields and field values from.
    +249   * @param markedPaths "Marked" paths - each one will be treated, as encountered, according to {@code markEffect}.
    +250   * @param markEffect Determines how to treat "marked" paths. See {@link FetchOptions.MaskMode} for more information.
    +251   * @param stackPrefix Dotted stack of properties describing the path that got us to this point (via recursion).
    +252   * @return Constructed model, after applying the provided field mask, as applicable.
    +253   */
    +254  default Message.Builder applyFieldsRecursive(@Nonnull Message.Builder target,
    +255                                               @Nonnull Message source,
    +256                                               @Nonnull Set<String> markedPaths,
    +257                                               @Nonnull FetchOptions.MaskMode markEffect,
    +258                                               @Nonnull String stackPrefix) {
    +259    // otherwise, we must examine each field with a value on the `source`, checking against `markedPaths` (if present)
    +260    // as we go. if it matches, we filter through `markEffect` before applying against `target`.
    +261    for (Map.Entry<FieldDescriptor, Object> property : source.getAllFields().entrySet()) {
    +262      FieldDescriptor field = property.getKey();
    +263      boolean skip = false;
    +264      Object value = property.getValue();
    +265      FetchOptions.MaskMode effect = FetchOptions.MaskMode.INCLUDE.equals(markEffect) ?
    +266        FetchOptions.MaskMode.EXCLUDE : FetchOptions.MaskMode.INCLUDE;
    +267
    +268      String currentPath = stackPrefix.isEmpty() ? field.getName() : stackPrefix + "." + field.getName();
    +269
    +270      boolean marked = markedPaths.contains(currentPath);
    +271      if (!FieldDescriptor.Type.MESSAGE.equals(field.getType()) && marked) {
    +272        // field is in the marked paths.
    +273        effect = markEffect;
    +274      } else if (FieldDescriptor.Type.MESSAGE.equals(field.getType())) {
    +275        effect = FetchOptions.MaskMode.INCLUDE;  // always include messages
    +276      }
    +277
    +278      switch (effect) {
    +279        case PROJECTION:
    +280        case INCLUDE:
    +281          if (Internals.logging.isDebugEnabled()) {
    +282            Internals.logging.debug(format(
    +283              "Field '%s' (%s) included because it did not violate expectation %s via field mask.",
    +284              currentPath,
    +285              field.getFullName(),
    +286              markEffect.name()));
    +287          }
    +288
    +289          // handle recursive cases first
    +290          if (FieldDescriptor.Type.MESSAGE.equals(field.getType())) {
    +291            target.setField(
    +292              field,
    +293              applyFieldsRecursive(
    +294                target.getFieldBuilder(field),
    +295                (Message)value,
    +296                markedPaths,
    +297                markEffect,
    +298                currentPath).build());
    +299
    +300          } else {
    +301            // it's a simple field value
    +302            target.setField(field, value);
    +303          }
    +304          break;
    +305
    +306        case EXCLUDE:
    +307          if (Internals.logging.isDebugEnabled()) {
    +308            Internals.logging.debug(format(
    +309              "Excluded field '%s' (%s) because it did not meet expectation %s via field mask.",
    +310              currentPath,
    +311              field.getFullName(),
    +312              markEffect.name()));
    +313          }
    +314      }
    +315    }
    +316    return target;
    +317  }
    +318
    +319  /**
    +320   * Apply mask-related options to the provided instance. This may include re-building <i>without</i> certain fields, so
    +321   * the instance returned may be different.
    +322   *
    +323   * @param instance Instance to filter based on any provided field mask.k
    +324   * @param options Options to apply to the provided instance.
    +325   * @return Model, post-filtering.
    +326   */
    +327  @VisibleForTesting
    +328  default Model applyMask(@Nonnull Model instance, @Nonnull FetchOptions options) {
    +329    // do we have a mask to apply? does it have fields?
    +330    if (instance.isInitialized()
    +331        && options.fieldMask().isPresent()
    +332        && options.fieldMask().get().getPathsCount() > 0) {
    +333      if (Internals.logging.isTraceEnabled())
    +334        Internals.logging.trace(format("Found valid field mask, applying: '%s'.", options.fieldMask().get()));
    +335
    +336      // resolve mask & mode
    +337      FieldMask mask = options.fieldMask().get();
    +338      FetchOptions.MaskMode maskMode = Objects.requireNonNull(options.fieldMaskMode(),
    +339        "Cannot provide `null` for field mask mode.");
    +340
    +341      //noinspection unchecked
    +342      return (Model)applyFieldsRecursive(
    +343        instance.newBuilderForType(),
    +344        instance,
    +345        ImmutableSet.copyOf(Objects.requireNonNull(mask.getPathsList())),
    +346        maskMode,
    +347        "" /* root path */).build();
    +348    }
    +349    if (Internals.logging.isTraceEnabled())
    +350      Internals.logging.trace("No field mask found. Skipping mask application.");
    +351    return instance;
    +352  }
    +353
    +354  // -- API: Fetch -- //
    +355  /**
    +356   * Synchronously retrieve a data model instance from underlying storage, addressed by its unique ID.
    +357   *
    +358   * <p>If the record cannot be located by the storage engine, {@code null} will be returned instead. For a safe variant
    +359   * of this method (relying on {@link Optional}), see {@link #fetchSafe(Message)}.</p>
    +360   *
    +361   * <p><b>Note:</b> Asynchronous and reactive versions of this method also exist. You should always consider using
    +362   * those if your requirements allow.</p>
    +363   *
    +364   * @see #fetchAsync(Message) For an async version of this method, which produces a {@link ListenableFuture}.
    +365   * @see #fetchSafe(Message) For a safe version of this method, which uses {@link Optional} instead of null.
    +366   * @see #fetchReactive(Message) For a reactive version of this method, which produces a {@link Publisher}.
    +367   * @param key Key at which we should look for the requested entity, and return it if found.
    +368   * @return Requested record, as a model instance, or {@code null} if one could not be found.
    +369   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested instance.
    +370   */
    +371  default @Nullable Model fetch(@Nonnull Key key) throws PersistenceException {
    +372    return fetch(key, FetchOptions.DEFAULTS);
    +373  }
    +374
    +375  /**
    +376   * Synchronously retrieve a data model instance from underlying storage, addressed by its unique ID.
    +377   *
    +378   * <p>If the record cannot be located by the storage engine, {@code null} will be returned instead. For a safe
    +379   * variant of this method (relying on {@link Optional}), see {@link #fetchSafe(Message)}}. This variant
    +380   * additionally allows specification of {@link FetchOptions}.</p>
    +381   *
    +382   * <p><b>Note:</b> Asynchronous and reactive versions of this method also exist. You should always consider using
    +383   * those if your requirements allow.</p>
    +384   *
    +385   * @see #fetchAsync(Message) For an async version of this method, which produces a {@link ListenableFuture}.
    +386   * @see #fetchSafe(Message) For a safe version of this method, which uses {@link Optional} instead of null.
    +387   * @see #fetchReactive(Message) For a reactive version of this method, which produces a {@link Publisher}.
    +388   * @param key Key at which we should look for the requested entity, and return it if found.
    +389   * @param options Options to apply to this individual retrieval operation.
    +390   * @return Requested record, as a model instance, or {@code null} if one could not be found.
    +391   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +392   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested instance.
    +393   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +394   */
    +395  default @Nullable Model fetch(@Nonnull Key key, @Nullable FetchOptions options) throws PersistenceException {
    +396    Optional<Model> msg = fetchSafe(key, options);
    +397    return msg.orElse(null);
    +398  }
    +399
    +400  /**
    +401   * Safely (and synchronously) retrieve a data model instance from storage, returning {@link Optional#empty()} if it
    +402   * cannot be located, rather than {@code null}.
    +403   *
    +404   * <p><b>Note:</b> Asynchronous and reactive versions of this method also exist. You should always consider using
    +405   * those if your requirements allow. All of the reactive/async methods support null safety with {@link Optional}.</p>
    +406   *
    +407   * @see #fetch(Message) For a simpler, but {@code null}-unsafe version of this method.
    +408   * @see #fetchAsync(Message) For an async version of this metho, which produces a {@link ListenableFuture}.
    +409   * @see #fetchReactive(Message) For a reactive version of this method, which produces a {@link Publisher}.
    +410   * @param key Key at which we should look for the requested entity, and return it if found.
    +411   * @return Requested record, as a model instance, or {@link Optional#empty()} if it cannot be found.
    +412   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +413   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested resource.
    +414   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +415   */
    +416  default @Nonnull Optional<Model> fetchSafe(@Nonnull Key key) throws PersistenceException {
    +417    return fetchSafe(key, FetchOptions.DEFAULTS);
    +418  }
    +419
    +420  /**
    +421   * Safely (and synchronously) retrieve a data model instance from storage, returning {@link Optional#empty()} if it
    +422   * cannot be located, rather than {@code null}.
    +423   *
    +424   * <p>This variant additionally allows specification of {@link FetchOptions}.</p>
    +425   *
    +426   * <p><b>Note:</b> Asynchronous and reactive versions of this method also exist. You should always consider using
    +427   * those if your requirements allow. All of the reactive/async methods support null safety with {@link Optional}.</p>
    +428   *
    +429   * @see #fetch(Message) For a simpler, but {@code null}-unsafe version of this method.
    +430   * @see #fetchAsync(Message) For an async version of this metho, which produces a {@link ListenableFuture}.
    +431   * @see #fetchReactive(Message) For a reactive version of this method, which produces a {@link Publisher}.
    +432   * @param key Key at which we should look for the requested entity, and return it if found.
    +433   * @param options Options to apply to this individual retrieval operation.
    +434   * @return Requested record, as a model instance, or {@link Optional#empty()} if it cannot be found.
    +435   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +436   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested resource.
    +437   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +438   */
    +439  @Nonnull
    +440  default Optional<Model> fetchSafe(@Nonnull Key key, @Nullable FetchOptions options) throws PersistenceException {
    +441    if (Internals.logging.isTraceEnabled())
    +442      Internals.logging.trace(format("Synchronously fetching model with key '%s'. Options follow.\n%s",
    +443        key, options));
    +444    return Internals.convertAsyncExceptions(() -> {
    +445      FetchOptions resolvedOptions = options != null ? options : FetchOptions.DEFAULTS;
    +446      return this.fetchAsync(key, options).get(
    +447        resolvedOptions.timeoutValue().orElse(DEFAULT_TIMEOUT),
    +448        resolvedOptions.timeoutUnit().orElse(DEFAULT_TIMEOUT_UNIT));
    +449    });
    +450  }
    +451
    +452  /**
    +453   * Reactively retrieve a data model instance from storage, emitting it over a {@link Publisher} wrapped in an
    +454   * {@link Optional}.
    +455   *
    +456   * <p>In other words, if the model cannot be located, exactly one {@link Optional#empty()} will be emitted over the
    +457   * channel. If the model is successfully located and retrieved, it is emitted exactly once. See other method variants,
    +458   * which allow specification of additional options.</p>
    +459   *
    +460   * <p><b>Exceptions:</b> Instead of throwing a {@link PersistenceException} as other methods do, this operation will
    +461   * <i>emit</i> the exception over the {@link Publisher} channel instead, to enable reactive exception handling.</p>
    +462   *
    +463   * @see #fetch(Message) For a simple, synchronous ({@code null}-unsafe) version of this method.
    +464   * @see #fetchAsync(Message) For an async version of this method, which produces a {@link ListenableFuture}.
    +465   * @see #fetchReactive(Message, FetchOptions) For a variant of this method that allows specification of options.
    +466   * @param key Key at which we should look for the requested entity, and emit it if found.
    +467   * @return Publisher which will receive exactly-one emitted {@link Optional#empty()}, or wrapped object.
    +468   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +469   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested resource.
    +470   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +471   */
    +472  default @Nonnull ReactiveFuture<Optional<Model>> fetchReactive(@Nonnull Key key) {
    +473    return fetchReactive(key, FetchOptions.DEFAULTS);
    +474  }
    +475
    +476  /**
    +477   * Reactively retrieve a data model instance from storage, emitting it over a {@link Publisher} wrapped in an
    +478   * {@link Optional}.
    +479   *
    +480   * <p>In other words, if the model cannot be located, exactly one {@link Optional#empty()} will be emitted over the
    +481   * channel. If the model is successfully located and retrieved, it is emitted exactly once. See other method variants,
    +482   * which allow specification of additional options. This method variant additionally allows the specification of
    +483   * {@link FetchOptions}.</p>
    +484   *
    +485   * <p><b>Exceptions:</b> Instead of throwing a {@link PersistenceException} as other methods do, this operation will
    +486   * <i>emit</i> the exception over the {@link Publisher} channel instead, to enable reactive exception handling.</p>
    +487   *
    +488   * @see #fetch(Message) For a simple, synchronous ({@code null}-unsafe) version of this method.
    +489   * @see #fetchAsync(Message) For an async version of this method, which produces a {@link ListenableFuture}.
    +490   * @param key Key at which we should look for the requested entity, and emit it if found.
    +491   * @param options Options to apply to this individual retrieval operation.
    +492   * @return Publisher which will receive exactly-one emitted {@link Optional#empty()}, or wrapped object.
    +493   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +494   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested resource.
    +495   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +496   */
    +497  default @Nonnull ReactiveFuture<Optional<Model>> fetchReactive(@Nonnull Key key, @Nullable FetchOptions options) {
    +498    return this.fetchAsync(key, options);
    +499  }
    +500
    +501  /**
    +502   * Asynchronously retrieve a data model instance from storage, which will populate the provided {@link Future} value.
    +503   *
    +504   * <p>All futures emitted via the persistence framework (and Gust writ-large) are {@link ListenableFuture}-compliant
    +505   * implementations under the hood. If the requested record cannot be located, {@link Optional#empty()} is returned as
    +506   * the future value, otherwise, the model is returned. See other method variants, which allow specification of
    +507   * additional options.</p>
    +508   *
    +509   * <p><b>Exceptions:</b> Instead of throwing a {@link PersistenceException} as other methods do, this operation will
    +510   * <i>emit</i> the exception over the {@link Future} channel instead, or raise the exception in the event
    +511   * {@link Future#get()} is called to surface it in the invoking (or dependent) code.</p>
    +512   *
    +513   * @see #fetch(Message) For a simple, synchronous ({@code null}=unsafe) version of this method.
    +514   * @see #fetchSafe(Message) For a simple, synchronous ({@code null}-safe) version of this method.
    +515   * @see #fetchReactive(Message) For a reactive version of this method, which returns a {@link Publisher}.
    +516   * @see #fetchAsync(Message, FetchOptions) For a variant of this method which supports {@link FetchOptions}.
    +517   * @param key Key at which we should look for the requested entity, and emit it if found.
    +518   * @return Future value, which resolves to the specified datamodel instance, or {@link Optional#empty()} if the record
    +519   *         could not be located by the storage engine.
    +520   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +521   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested resource.
    +522   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +523   */
    +524  default @Nonnull ReactiveFuture<Optional<Model>> fetchAsync(@Nonnull Key key) {
    +525    return fetchAsync(key, FetchOptions.DEFAULTS);
    +526  }
    +527
    +528  /**
    +529   * Asynchronously retrieve a data model instance from storage, which will populate the provided {@link Future} value.
    +530   *
    +531   * <p>All futures emitted via the persistence framework (and Gust writ-large) are {@link ListenableFuture}-compliant
    +532   * implementations under the hood. If the requested record cannot be located, {@link Optional#empty()} is returned as
    +533   * the future value, otherwise, the model is returned.</p>
    +534   *
    +535   * <p>This method additionally enables specification of custom {@link FetchOptions}, which are applied on a per-
    +536   * operation basis to override global defaults.</p>
    +537   *
    +538   * <p><b>Exceptions:</b> Instead of throwing a {@link PersistenceException} as other methods do, this operation will
    +539   * <i>emit</i> the exception over the {@link Future} channel instead, or raise the exception in the event
    +540   * {@link Future#get()} is called to surface it in the invoking (or dependent) code.</p>
    +541   *
    +542   * @see #fetch(Message) For a simple, synchronous ({@code null}=unsafe) version of this method.
    +543   * @see #fetchSafe(Message) For a simple, synchronous ({@code null}-safe) version of this method.
    +544   * @see #fetchReactive(Message) For a reactive version of this method, which returns a {@link Publisher}.
    +545   * @param key Key at which we should look for the requested entity, and emit it if found.
    +546   * @param options Options to apply to this individual retrieval operation.
    +547   * @return Future value, which resolves to the specified datamodel instance, or {@link Optional#empty()} if the record
    +548   *         could not be located by the storage engine.
    +549   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +550   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested resource.
    +551   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +552   */
    +553  @OverridingMethodsMustInvokeSuper
    +554  default @Nonnull ReactiveFuture<Optional<Model>> fetchAsync(@Nonnull Key key, @Nullable FetchOptions options) {
    +555    if (Internals.logging.isTraceEnabled())
    +556      Internals.logging.trace(format("Fetching model with key '%s' asynchronously. Options follow.\n%s",
    +557        key, options));
    +558    return this.retrieve(key, options != null ? options : FetchOptions.DEFAULTS);
    +559  }
    +560
    +561  /**
    +562   * Low-level record retrieval method. Effectively called by all other fetch variants. Asynchronously retrieve a data
    +563   * model instance from storage, which will populate the provided {@link ReactiveFuture} value.
    +564   *
    +565   * <p>All futures emitted via the persistence framework (and Gust writ-large) are {@link ListenableFuture}-compliant
    +566   * implementations under the hood. If the requested record cannot be located, {@link Optional#empty()} is returned as
    +567   * the future value, otherwise, the model is returned.</p>
    +568   *
    +569   * <p>This method additionally enables specification of custom {@link FetchOptions}, which are applied on a per-
    +570   * operation basis to override global defaults.</p>
    +571   *
    +572   * <p><b>Exceptions:</b> Instead of throwing a {@link PersistenceException} as other methods do, this operation will
    +573   * <i>emit</i> the exception over the {@link Future} channel instead, or raise the exception in the event
    +574   * {@link Future#get()} is called to surface it in the invoking (or dependent) code.</p>
    +575   *
    +576   * @see #fetch(Message) For a simple, synchronous ({@code null}=unsafe) version of this method.
    +577   * @see #fetchSafe(Message) For a simple, synchronous ({@code null}-safe) version of this method.
    +578   * @see #fetchAsync(Message) For an async variant of this method (identical, except options are optional).
    +579   * @see #fetchReactive(Message) For a reactive version of this method, which returns a {@link Publisher}.
    +580   * @param key Key at which we should look for the requested entity, and emit it if found.
    +581   * @param options Options to apply to this individual retrieval operation.
    +582   * @return Future value, which resolves to the specified datamodel instance, or {@link Optional#empty()} if the record
    +583   *         could not be located by the storage engine.
    +584   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +585   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested resource.
    +586   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +587   */
    +588  @Nonnull ReactiveFuture<Optional<Model>> retrieve(@Nonnull Key key, @Nonnull FetchOptions options);
    +589
    +590  // -- API: Persist -- //
    +591  /**
    +592   * Create the record specified by {@code model} in underlying storage, provisioning a key or ID for the record if
    +593   * needed. The persisted entity is returned or an error occurs.
    +594   *
    +595   * <p>This operation will enforce the option {@code MUST_NOT_EXIST} for the write - i.e., "creating" a record implies
    +596   * that it must not exist beforehand. Additionally, if the record is missing a unique ID or key (one or the other must
    +597   * be annotated on the record), then a semi-random value will be generated for the record.</p>
    +598   *
    +599   * <p>The returned record will be re-constituted, with the spliced-in ID or key value, as applicable, and with any
    +600   * computed or framework-related properties filled in (i.e. automatic timestamping).</p>
    +601   *
    +602   * @param model Model to create in underlying storage. Requires a {@code ID} or {@code KEY}-annotated field.
    +603   * @return Future value, which resolves to the stored model entity, affixed with an assigned ID or key.
    +604   * @throws InvalidModelType If the specified model record is not usable with storage.
    +605   * @throws PersistenceException If an unexpected failure occurs, of any kind, while creating the record.
    +606   * @throws MissingAnnotatedField If a required annotated field cannot be located (i.e. {@code ID} or {@code KEY}).
    +607   */
    +608  default @Nonnull ReactiveFuture<Model> create(@Nonnull Model model) {
    +609    //noinspection unchecked
    +610    return create((Key)key(model).orElse(null), model);
    +611  }
    +612
    +613  /**
    +614   * Create the record specified by {@code model} using the optional pre-fabricated {@code key}, in underlying storage.
    +615   * If the provided key is empty or {@code null}, the engine will provision a key or ID for the record. The persisted
    +616   * entity is returned or an error occurs.
    +617   *
    +618   * <p>This operation will enforce the option {@code MUST_NOT_EXIST} for the write - i.e., "creating" a record implies
    +619   * that it must not exist beforehand. Additionally, if the record is missing a unique ID or key (one or the other must
    +620   * be annotated on the record), then a semi-random value will be generated for the record.</p>
    +621   *
    +622   * <p>The returned record will be re-constituted, with the spliced-in ID or key value, as applicable, and with any
    +623   * computed or framework-related properties filled in (i.e. automatic timestamping).</p>
    +624   *
    +625   * @param model Model to create in underlying storage. Requires a {@code ID} or {@code KEY}-annotated field.
    +626   * @return Future value, which resolves to the stored model entity, affixed with an assigned ID or key.
    +627   * @throws InvalidModelType If the specified model record is not usable with storage.
    +628   * @throws PersistenceException If an unexpected failure occurs, of any kind, while creating the record.
    +629   * @throws MissingAnnotatedField If a required annotated field cannot be located (i.e. {@code ID} or {@code KEY}).
    +630   */
    +631  default @Nonnull ReactiveFuture<Model> create(@Nullable Key key, @Nonnull Model model) {
    +632    return create(key, model, new WriteOptions() {
    +633      @Override
    +634      public @Nonnull Optional<WriteDisposition> writeMode() {
    +635        return Optional.of(WriteDisposition.MUST_NOT_EXIST);
    +636      }
    +637    });
    +638  }
    +639
    +640  /**
    +641   * Create the record specified by {@code model} using the specified set of {@code options}, in underlying storage. If
    +642   * the provided mode's key or ID is empty or {@code null}, the engine will provision a key or ID for the record. The
    +643   * persisted entity is returned or an error occurs.
    +644   *
    +645   * <p>This operation will enforce the option {@code MUST_NOT_EXIST} for the write - i.e., "creating" a record implies
    +646   * that it must not exist beforehand. Additionally, if the record is missing a unique ID or key (one or the other must
    +647   * be annotated on the record), then a semi-random value will be generated for the record.</p>
    +648   *
    +649   * <p>The returned record will be re-constituted, with the spliced-in ID or key value, as applicable, and with any
    +650   * computed or framework-related properties filled in (i.e. automatic timestamping).</p>
    +651   *
    +652   * @param model Model to create in underlying storage. Requires a {@code ID} or {@code KEY}-annotated field.
    +653   * @return Future value, which resolves to the stored model entity, affixed with an assigned ID or key.
    +654   * @throws InvalidModelType If the specified model record is not usable with storage.
    +655   * @throws PersistenceException If an unexpected failure occurs, of any kind, while creating the record.
    +656   * @throws MissingAnnotatedField If a required annotated field cannot be located (i.e. {@code ID} or {@code KEY}).
    +657   */
    +658  default @Nonnull ReactiveFuture<Model> create(@Nonnull Model model, @Nonnull WriteOptions options) {
    +659    //noinspection unchecked
    +660    return create((Key)key(model).orElse(null), model, options);
    +661  }
    +662
    +663  /**
    +664   * Create the record specified by {@code model} using the optional pre-fabricated {@code key}, and making use of the
    +665   * specified {@code options}, in underlying storage. If the provided key is empty or {@code null}, the engine will
    +666   * provision a key or ID for the record. The persisted entity is returned or an error occurs.
    +667   *
    +668   * <p>This operation will enforce the option {@code MUST_NOT_EXIST} for the write - i.e., "creating" a record implies
    +669   * that it must not exist beforehand. Additionally, if the record is missing a unique ID or key (one or the other must
    +670   * be annotated on the record), then a semi-random value will be generated for the record.</p>
    +671   *
    +672   * <p>The returned record will be re-constituted, with the spliced-in ID or key value, as applicable, and with any
    +673   * computed or framework-related properties filled in (i.e. automatic timestamping).</p>
    +674   *
    +675   * @param model Model to create in underlying storage. Requires a {@code ID} or {@code KEY}-annotated field.
    +676   * @return Future value, which resolves to the stored model entity, affixed with an assigned ID or key.
    +677   * @throws InvalidModelType If the specified model record is not usable with storage.
    +678   * @throws PersistenceException If an unexpected failure occurs, of any kind, while creating the record.
    +679   * @throws MissingAnnotatedField If a required annotated field cannot be located (i.e. {@code ID} or {@code KEY}).
    +680   * @throws IllegalArgumentException If an incompatible {@link WriteOptions.WriteDisposition} value is specified.
    +681   */
    +682  @Nonnull
    +683  default ReactiveFuture<Model> create(@Nullable Key key, @Nonnull Model model, @Nonnull WriteOptions options) {
    +684    Internals.enforceOption(
    +685      options.writeMode()
    +686        .orElse(WriteOptions.WriteDisposition.MUST_NOT_EXIST),
    +687      WriteOptions.WriteDisposition.MUST_NOT_EXIST,
    +688      "Write options for `create` must specify `MUST_NOT_EXIST` write disposition.");
    +689    return persist(key, model, options);
    +690  }
    +691
    +692  /**
    +693   * Update the record specified by {@code model} in underlying storage, using the existing key or ID value affixed to
    +694   * the model. The entity is returned in its updated form, or an error occurs.
    +695   *
    +696   * <p>This operation will enforce the option {@code MUST_EXIST} for the write - i.e., "updating" a record implies that
    +697   * it must exist beforehand. This means, if the record is missing a unique ID or key (one or the other must be
    +698   * annotated on the record), then an error occurs (specifically, either {@link MissingAnnotatedField}) for a  missing
    +699   * schema field, or {@link IllegalStateException} for a missing required value).</p>
    +700   *
    +701   * <p>The returned record will be re-constituted, with the ID or key value unmodified, as applicable, and with any
    +702   * computed or framework-related properties updated in (i.e. automatic update timestamping).</p>
    +703   *
    +704   * @param model Model to update in underlying storage. Requires a {@code ID} or {@code KEY}-annotated field and value.
    +705   * @return Future value, which resolves to the stored model entity, after it has been updated.
    +706   * @throws InvalidModelType If the specified model record is not usable with storage.
    +707   * @throws PersistenceException If an unexpected failure occurs, of any kind, while updated the record.
    +708   * @throws MissingAnnotatedField If a required annotated field cannot be located (i.e. {@code ID} or {@code KEY}).
    +709   * @throws IllegalStateException If a required annotated field value cannot be resolved (i.e. an empty key or ID).
    +710   */
    +711  default @Nonnull ReactiveFuture<Model> update(@Nonnull Model model) {
    +712    //noinspection unchecked
    +713    return update(
    +714      (Key)key(model).orElseThrow(() -> new IllegalStateException("Failed to resolve a key value for record.")),
    +715      model);
    +716  }
    +717
    +718  /**
    +719   * Update the record specified by {@code model} in underlying storage, making use of the specified {@code options},
    +720   * using the existing key or ID value affixed to the model. The entity is returned in its updated form, or an error
    +721   * occurs.
    +722   *
    +723   * <p>This operation will enforce the option {@code MUST_EXIST} for the write - i.e., "updating" a record implies that
    +724   * it must exist beforehand. This means, if the record is missing a unique ID or key (one or the other must be
    +725   * annotated on the record), then an error occurs (specifically, either {@link MissingAnnotatedField}) for a  missing
    +726   * schema field, or {@link IllegalStateException} for a missing required value).</p>
    +727   *
    +728   * <p>The returned record will be re-constituted, with the ID or key value unmodified, as applicable, and with any
    +729   * computed or framework-related properties updated in (i.e. automatic update timestamping).</p>
    +730   *
    +731   * @param model Model to update in underlying storage. Requires a {@code ID} or {@code KEY}-annotated field and value.
    +732   * @return Future value, which resolves to the stored model entity, after it has been updated.
    +733   * @throws InvalidModelType If the specified model record is not usable with storage.
    +734   * @throws PersistenceException If an unexpected failure occurs, of any kind, while updated the record.
    +735   * @throws MissingAnnotatedField If a required annotated field cannot be located (i.e. {@code ID} or {@code KEY}).
    +736   * @throws IllegalStateException If a required annotated field value cannot be resolved (i.e. an empty key or ID).
    +737   */
    +738  default @Nonnull ReactiveFuture<Model> update(@Nonnull Model model, @Nonnull UpdateOptions options) {
    +739    //noinspection unchecked
    +740    return update(
    +741      (Key)key(model).orElseThrow(() -> new IllegalStateException("Failed to resolve a key value for record.")),
    +742      model,
    +743      options);
    +744  }
    +745
    +746  /**
    +747   * Update the record specified by {@code model}, and addressed by {@code key}, in underlying storage. The entity is
    +748   * returned in its updated form, or an error occurs.
    +749   *
    +750   * <p>This operation will enforce the option {@code MUST_EXIST} for the write - i.e., "updating" a record implies that
    +751   * it must exist beforehand. This means, if the record is missing a unique ID or key (one or the other must be
    +752   * annotated on the record), then an error occurs (specifically, either {@link MissingAnnotatedField}) for a  missing
    +753   * schema field, or {@link IllegalStateException} for a missing required value).</p>
    +754   *
    +755   * <p>The returned record will be re-constituted, with the ID or key value unmodified, as applicable, and with any
    +756   * computed or framework-related properties updated in (i.e. automatic update timestamping).</p>
    +757   *
    +758   * @param model Model to update in underlying storage. Requires a {@code ID} or {@code KEY}-annotated field and value.
    +759   * @return Future value, which resolves to the stored model entity, after it has been updated.
    +760   * @throws InvalidModelType If the specified model record is not usable with storage.
    +761   * @throws PersistenceException If an unexpected failure occurs, of any kind, while updated the record.
    +762   * @throws MissingAnnotatedField If a required annotated field cannot be located (i.e. {@code ID} or {@code KEY}).
    +763   * @throws IllegalStateException If a required annotated field value cannot be resolved (i.e. an empty key or ID).
    +764   */
    +765  default @Nonnull ReactiveFuture<Model> update(@Nonnull Key key, @Nonnull Model model) {
    +766    return update(key, model, new UpdateOptions() {
    +767      @Override
    +768      public @Nonnull Optional<WriteDisposition> writeMode() {
    +769        return Optional.of(WriteDisposition.MUST_EXIST);
    +770      }
    +771    });
    +772  }
    +773
    +774  /**
    +775   * Update the record specified by {@code model}, and addressed by {@code key}, in underlying storage. The entity is
    +776   * returned in its updated form, or an error occurs. This method variant additionally allows specification of custom
    +777   * {@code options} for this individual operation.
    +778   *
    +779   * <p>This operation will enforce the option {@code MUST_EXIST} for the write - i.e., "updating" a record implies that
    +780   * it must exist beforehand. This means, if the record is missing a unique ID or key (one or the other must be
    +781   * annotated on the record), then an error occurs (specifically, either {@link MissingAnnotatedField}) for a  missing
    +782   * schema field, or {@link IllegalStateException} for a missing required value).</p>
    +783   *
    +784   * <p>The returned record will be re-constituted, with the ID or key value unmodified, as applicable, and with any
    +785   * computed or framework-related properties updated in (i.e. automatic update timestamping).</p>
    +786   *
    +787   * @param model Model to update in underlying storage. Requires a {@code ID} or {@code KEY}-annotated field and value.
    +788   * @return Future value, which resolves to the stored model entity, after it has been updated.
    +789   * @throws InvalidModelType If the specified model record is not usable with storage.
    +790   * @throws PersistenceException If an unexpected failure occurs, of any kind, while updated the record.
    +791   * @throws MissingAnnotatedField If a required annotated field cannot be located (i.e. {@code ID} or {@code KEY}).
    +792   * @throws IllegalStateException If a required annotated field value cannot be resolved (i.e. an empty key or ID).
    +793   * @throws IllegalArgumentException If an incompatible {@link WriteOptions.WriteDisposition} value is specified.
    +794   */
    +795  @Nonnull
    +796  default ReactiveFuture<Model> update(@Nonnull Key key, @Nonnull Model model, @Nonnull UpdateOptions options) {
    +797    Internals.enforceOption(
    +798      options.writeMode().orElse(WriteOptions.WriteDisposition.MUST_EXIST),
    +799      WriteOptions.WriteDisposition.MUST_EXIST,
    +800      "Write options for `update` must specify `MUST_EXIST` write disposition.");
    +801    return persist(key, model, options);
    +802  }
    +803
    +804  /**
    +805   * Low-level record persistence method. Effectively called by all other create/put variants. Asynchronously write a
    +806   * data model instance to storage, which will populate the provided {@link ReactiveFuture} value.
    +807   *
    +808   * <p>Optionally, a key may be provided as a nominated value to the storage engine. Whether the engine accepts
    +809   * nominated keys is up to the implementation. In all cases, the engine must return the key used to store and address
    +810   * the value henceforth. If the engine <i>does</i> support nominated keys, it <i>must</i> operate in an idempotent
    +811   * manner with regard to those keys. In other words, repeated calls to create the same entity with the same key will
    +812   * not cause spurious side-effects - only one record will be created, with the remaining calls being rejected by the
    +813   * underlying engine.</p>
    +814   *
    +815   * <p>All futures emitted via the persistence framework (and Gust writ-large) are {@link ListenableFuture}-compliant
    +816   * implementations under the hood, but {@link ReactiveFuture} allows a model-layer result to be used as a
    +817   * {@link Future}, or a one-item reactive {@link Publisher}.</p>
    +818   *
    +819   * <p>This method additionally enables specification of custom {@link WriteOptions}, which are applied on a per-
    +820   * operation basis to override global defaults.</p>
    +821   *
    +822   * <p><b>Exceptions:</b> Instead of throwing a {@link PersistenceException} as other methods do, this operation will
    +823   * <i>emit</i> the exception over the {@link Future} channel instead, or raise the exception in the event
    +824   * {@link Future#get()} is called to surface it in the invoking (or dependent) code.</p>
    +825   *
    +826   * @param key Key nominated by invoking code for storing this record. If no key is provided, the underlying storage
    +827   *            engine is expected to allocate one. Where unsupported, {@link PersistenceException} will be thrown.
    +828   * @param model Model to store at the specified key, if provided.
    +829   * @param options Options to apply to this persist operation.
    +830   * @return Reactive future, which resolves to the key where the provided model is now stored. In no case should this
    +831   *         method return {@code null}. Instead, {@link PersistenceException} will be thrown.
    +832   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +833   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested resource.
    +834   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +835   */
    +836  @Nonnull ReactiveFuture<Model> persist(@Nullable Key key, @Nonnull Model model, @Nonnull WriteOptions options);
    +837
    +838  // -- API: Delete -- //
    +839  /**
    +840   * Delete and fully erase the record referenced by {@code key} from underlying storage, permanently. The resulting
    +841   * future resolves to the provided key value once the operation completes. If any issue occurs (besides encountering
    +842   * an already-deleted entity, which is not an error), an exception is raised.
    +843   *
    +844   * @param key Key referring to the record which should be deleted, permanently, from underlying storage.
    +845   * @return Future, which resolves to the provided key when the operation is complete.
    +846   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +847   * @throws PersistenceException If an unexpected failure occurs, of any kind, while deleting the requested resource.
    +848   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +849   * @throws IllegalStateException If a required annotated field value cannot be resolved (i.e. an empty key or ID).
    +850   */
    +851  default @Nonnull ReactiveFuture<Key> delete(@Nonnull Key key) {
    +852    return delete(key, DeleteOptions.DEFAULTS);
    +853  }
    +854
    +855  /**
    +856   * Delete and fully erase the supplied {@code model} from underlying storage, permanently. The resulting future
    +857   * resolves to the provided record's key value once the operation completes. If any issue occurs (besides encountering
    +858   * an already-deleted entity, which is not an error), an exception is raised.
    +859   *
    +860   * @param model Model instance to delete from underlying storage.
    +861   * @return Future, which resolves to the provided key when the operation is complete.
    +862   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +863   * @throws PersistenceException If an unexpected failure occurs, of any kind, while deleting the requested resource.
    +864   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +865   * @throws IllegalStateException If a required annotated field value cannot be resolved (i.e. an empty key or ID).
    +866   */
    +867  default @Nonnull ReactiveFuture<Key> deleteRecord(@Nonnull Model model) {
    +868    return deleteRecord(model, DeleteOptions.DEFAULTS);
    +869  }
    +870
    +871  /**
    +872   * Delete and fully erase the supplied {@code model} from underlying storage, permanently. The resulting future
    +873   * resolves to the provided record's key value once the operation completes. If any issue occurs (besides encountering
    +874   * an already-deleted entity, which is not an error), an exception is raised.
    +875   *
    +876   * @param model Model instance to delete from underlying storage.
    +877   * @param options Options to apply to this specific delete operation.
    +878   * @return Future, which resolves to the provided key when the operation is complete.
    +879   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +880   * @throws PersistenceException If an unexpected failure occurs, of any kind, while deleting the requested resource.
    +881   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +882   * @throws IllegalStateException If a required annotated field value cannot be resolved (i.e. an empty key or ID).
    +883   */
    +884  default @Nonnull ReactiveFuture<Key> deleteRecord(@Nonnull Model model, @Nonnull DeleteOptions options) {
    +885    //noinspection unchecked
    +886    return delete((Key)key(model)
    +887        .orElseThrow(() -> new IllegalStateException("Cannot delete record with empty key/ID.")),
    +888      options);
    +889  }
    +890
    +891  /**
    +892   * Low-level record delete method. Effectively called by all other delete variants. Asynchronously and permanently
    +893   * erase an existing data model instance from storage, addressed by its key unique key or ID.
    +894   *
    +895   * <p>If no key or ID field, or value, may be located, an error is raised (see below for details). This operation is
    +896   * expected to operate in an <i>idempotent</i> manner (i.e. repeated calls with identical parameters do not yield
    +897   * different side effects). Calls referring to an already-deleted entity should silently succeed.</p>
    +898   *
    +899   * <p>All futures emitted via the persistence framework (and Gust writ-large) are {@link ListenableFuture}-compliant
    +900   * implementations under the hood, but {@link ReactiveFuture} allows a model-layer result to be used as a
    +901   * {@link Future}, or a one-item reactive {@link Publisher}.</p>
    +902   *
    +903   * <p>This method additionally enables specification of custom {@link DeleteOptions}, which are applied on a per-
    +904   * operation basis to override global defaults.</p>
    +905   *
    +906   * <p><b>Exceptions:</b> Instead of throwing a {@link PersistenceException} as other methods do, this operation will
    +907   * <i>emit</i> the exception over the {@link Future} channel instead, or raise the exception in the event
    +908   * {@link Future#get()} is called to surface it in the invoking (or dependent) code.</p>
    +909   *
    +910   * @param key Unique key referring to the record in storage that should be deleted.
    +911   * @param options Options to apply to this specific delete operation.
    +912   * @return Future value, which resolves to the deleted record's key when the operation completes.
    +913   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +914   * @throws PersistenceException If an unexpected failure occurs, of any kind, while deleting the requested resource.
    +915   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +916   * @throws IllegalStateException If a required annotated field value cannot be resolved (i.e. an empty key or ID).
    +917   */
    +918  @Nonnull ReactiveFuture<Key> delete(@Nonnull Key key, @Nonnull DeleteOptions options);
    +919}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/PersistenceDriver.html b/docs/java/src-html/gust/backend/model/PersistenceDriver.html new file mode 100644 index 000000000..1da80f826 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/PersistenceDriver.html @@ -0,0 +1,993 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.common.annotations.VisibleForTesting;
    +016import com.google.common.collect.ImmutableSet;
    +017import com.google.common.util.concurrent.ListenableFuture;
    +018import com.google.common.util.concurrent.ListeningScheduledExecutorService;
    +019import com.google.errorprone.annotations.CanIgnoreReturnValue;
    +020import com.google.protobuf.Descriptors.FieldDescriptor;
    +021import com.google.protobuf.FieldMask;
    +022import com.google.protobuf.Message;
    +023import gust.backend.runtime.Logging;
    +024import gust.backend.runtime.ReactiveFuture;
    +025import org.reactivestreams.Publisher;
    +026import org.slf4j.Logger;
    +027import tools.elide.core.DatapointType;
    +028import tools.elide.core.FieldType;
    +029
    +030import javax.annotation.Nonnull;
    +031import javax.annotation.Nullable;
    +032import javax.annotation.OverridingMethodsMustInvokeSuper;
    +033import javax.annotation.concurrent.Immutable;
    +034import javax.annotation.concurrent.ThreadSafe;
    +035import java.util.*;
    +036import java.util.concurrent.*;
    +037
    +038import static java.lang.String.format;
    +039import static gust.backend.model.ModelMetadata.*;
    +040
    +041
    +042/**
    +043 * Describes the surface of a generic persistence driver, which is capable of accepting arbitrary structured and typed
    +044 * business data (also called "data models"), and managing them with regard to persistent storage, which includes
    +045 * storing them when asked, and recalling them when subsequently asked to do so.
    +046 *
    +047 * <p>Persistence driver implementations do not always guarantee <i>durability</i> of data. For example,
    +048 * {@link CacheDriver} implementations are also {@link PersistenceDriver}s, and that entire class of implementations
    +049 * does not guarantee data will be there when you ask for it <i>at all</i> (relying on cache state is generally
    +050 * considered to be a very bad practice).</p>
    +051 *
    +052 * <p>Other implementation trees exist (notably, {@link DatabaseDriver}) which go the other way, and are expected to
    +053 * guarantee durability of data across restarts, distributed systems and networks, and failure cases, as applicable.
    +054 * Database driver implementations also support richer data storage features like querying and indexing.</p>
    +055 *
    +056 * @see CacheDriver <pre>`CacheDriver`</pre> for persistence drivers with volatile durability guarantees
    +057 * @see DatabaseDriver <pre>`DatabaseDriver`</pre> for drivers with rich features and/or strong durability guarantees.
    +058 * @param <Key> Key record type (must be annotated with model role {@code OBJECT_KEY}).
    +059 * @param <Model> Message/model type which this persistence driver is specialized for.
    +060 * @param <ReadIntermediate> Intermediate record format used by the underlying driver implementation during model
    +061 *                           de-serialization.
    +062 * @param <WriteIntermediate> Intermediate record format used by the underlying driver implementation during model
    +063 *                           serialization.
    +064 */
    +065@Immutable
    +066@ThreadSafe
    +067@SuppressWarnings({"unused", "UnstableApiUsage"})
    +068public interface PersistenceDriver<Key extends Message, Model extends Message, ReadIntermediate, WriteIntermediate> {
    +069  /** Default timeout to apply when otherwise unspecified. */
    +070  long DEFAULT_TIMEOUT = 30;
    +071
    +072  /** Time units for {@link #DEFAULT_TIMEOUT}. */
    +073  TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS;
    +074
    +075  /** Default timeout to apply when fetching from the cache. */
    +076  long DEFAULT_CACHE_TIMEOUT = 5;
    +077
    +078  /** Time units for {@link #DEFAULT_CACHE_TIMEOUT}. */
    +079  TimeUnit DEFAULT_CACHE_TIMEOUT_UNIT = TimeUnit.SECONDS;
    +080
    +081  /** Default model adapter internals. */
    +082  @SuppressWarnings("SameParameterValue")
    +083  final class Internals {
    +084    /** Log pipe for default model adapter. */
    +085    static final Logger logging = Logging.logger(PersistenceDriver.class);
    +086
    +087    private Internals() { /* Disallow instantiation. */ }
    +088
    +089    /** Runnable that might throw async exceptions. */
    +090    @FunctionalInterface
    +091    interface DriverRunnable {
    +092      /**
    +093       * Run some operation that may throw async-style exceptions.
    +094       *
    +095       * @throws TimeoutException The operation timed out.
    +096       * @throws InterruptedException The operation was interrupted during execution.
    +097       * @throws ExecutionException An execution error halted async execution.
    +098       */
    +099      void run() throws TimeoutException, InterruptedException, ExecutionException;
    +100    }
    +101
    +102    /**
    +103     * Swallow any exceptions that occur
    +104     *
    +105     * @param operation Operation to run and wrap.
    +106     */
    +107    static void swallowExceptions(@Nonnull DriverRunnable operation) {
    +108      try {
    +109        operation.run();
    +110
    +111      } catch (Exception exc) {
    +112        Throwable inner = exc.getCause() != null ? exc.getCause() : exc;
    +113        logging.warn(format(
    +114          "Encountered unidentified exception '%s'. Message: '%s'.",
    +115          exc.getClass().getSimpleName(), exc.getMessage()));
    +116
    +117      }
    +118    }
    +119
    +120    /**
    +121     * Convert async exceptions into persistence layer exceptions, according to the failure that occurred. Also print a
    +122     * descriptive log statement.
    +123     *
    +124     * @param operation Operation to execute and wrap with protection.
    +125     * @param <R> Return type for the callable operation, if applicable.
    +126     * @return Return value of the async operation.
    +127     */
    +128    @CanIgnoreReturnValue
    +129    static <R> R convertAsyncExceptions(@Nonnull Callable<R> operation) {
    +130      try {
    +131        return operation.call();
    +132      } catch (InterruptedException ixe) {
    +133        logging.warn(format("Interrupted. Message: '%s'.",
    +134          ixe.getMessage()));
    +135        throw PersistenceOperationFailed.forErr(PersistenceFailure.INTERRUPTED);
    +136
    +137      } catch (ExecutionException exe) {
    +138        Throwable inner = exe.getCause() != null ? exe.getCause() : exe;
    +139        logging.warn(format("Encountered async exception '%s'. Message: '%s'.",
    +140          inner.getClass().getSimpleName(), inner.getMessage()));
    +141        throw PersistenceOperationFailed.forErr(PersistenceFailure.INTERNAL);
    +142
    +143      } catch (TimeoutException txe) {
    +144        throw PersistenceOperationFailed.forErr(PersistenceFailure.TIMEOUT);
    +145      } catch (Exception exc) {
    +146        logging.warn(format(
    +147          "Encountered unidentified exception '%s'. Message: '%s'.",
    +148          exc.getClass().getSimpleName(), exc.getMessage()));
    +149        throw PersistenceOperationFailed.forErr(PersistenceFailure.INTERNAL,
    +150          exc.getCause() != null ? exc.getCause() : exc);
    +151
    +152      }
    +153    }
    +154
    +155    /**
    +156     * Enforce that a particular model operation have the provided value present, and equal to the expected value. If
    +157     * these expectations are violated, an exception is thrown.
    +158     *
    +159     * @param value Value in the option set for this method.
    +160     * @param expected Expected value from the option set.
    +161     * @param expectation Message to throw if the expectation is violated.
    +162     * @param <R> Return value type - same as {@code value} and {@code expected}.
    +163     * @return Expected value if it is equal to {@code value}.
    +164     */
    +165    @CanIgnoreReturnValue
    +166    static <R> R enforceOption(@Nullable R value, @Nonnull R expected, @Nonnull String expectation) {
    +167      if (value != null && value.equals(expected)) {
    +168        return value;
    +169      }
    +170      throw new IllegalArgumentException("Operation failed: " + expectation);
    +171    }
    +172  }
    +173
    +174  // -- API: Execution -- //
    +175  /**
    +176   * Resolve an executor service for use with this persistence driver. Operations will be executed against this as they
    +177   * are received.
    +178   *
    +179   * @return Scheduled executor service.
    +180   */
    +181  @Nonnull ListeningScheduledExecutorService executorService();
    +182
    +183  // -- API: Codec -- //
    +184  /**
    +185   * Acquire an instance of the codec used by this adapter. Codecs are either injected/otherwise provided during adapter
    +186   * construction, or they are specified statically if the adapter depends on a specific codec.
    +187   *
    +188   * @return Model codec currently in use by this adapter.
    +189   */
    +190  @Nonnull ModelCodec<Model, WriteIntermediate, ReadIntermediate> codec();
    +191
    +192  // -- API: Key Generation -- //
    +193  /**
    +194   * Generate a semi-random opaque token, usable as an ID for a newly-created entity via the model layer. In this case,
    +195   * the ID is returned directly, so it may be used to populate a key.
    +196   *
    +197   * @param instance Model instance to generate an ID for.
    +198   * @return Generated opaque string ID.
    +199   */
    +200  default @Nonnull String generateId(@Nonnull Message instance) {
    +201    return UUID.randomUUID().toString();
    +202  }
    +203
    +204  /**
    +205   * Generate a key for a new entity, which must be stored by this driver, but does not yet have a key. If the driver
    +206   * does not support key generation, {@link UnsupportedOperationException} is thrown.
    +207   *
    +208   * <p>Generated keys are expected to be best-effort unique. Generally, Java's built-in {@link java.util.UUID} should
    +209   * do the trick just fine. In more complex or scalable circumstances, this method can be overridden to reach out to
    +210   * the data engine to generate a key.</p>
    +211   *
    +212   * @param instance Default instance of the model type for which a key is desired.
    +213   * @return Generated key for an entity to be stored.
    +214   */
    +215  default @Nonnull Key generateKey(@Nonnull Message instance) {
    +216    // enforce role, key field presence
    +217    var descriptor = instance.getDescriptorForType();
    +218    enforceRole(descriptor, DatapointType.OBJECT);
    +219    var keyType = keyField(descriptor);
    +220    if (keyType.isEmpty()) throw new MissingAnnotatedField(descriptor, FieldType.KEY);
    +221
    +222    // convert to builder, grab field builder for key (keys must be top-level fields)
    +223    var builder = instance.newBuilderForType();
    +224    var keyBuilder = builder.getFieldBuilder(keyType.get().getField());
    +225    spliceIdBuilder(keyBuilder, Optional.of(generateId(instance)));
    +226
    +227    //noinspection unchecked
    +228    Key obj = (Key)keyBuilder.build();
    +229    if (Internals.logging.isDebugEnabled()) {
    +230      Internals.logging.debug(format("Generated key for record: '%s'.", obj.toString()));
    +231    }
    +232    return obj;
    +233  }
    +234
    +235  // -- API: Projections & Field Masking -- //
    +236  /**
    +237   * Apply the fields from {@code source} to {@code target}, considering any provided {@link FieldMask}.
    +238   *
    +239   * <p>If the invoking developer chooses to provide {@code markedPaths}, they must also supply {@code markEffect}. For
    +240   * each field encountered that matches a property path in {@code markedPaths}, {@code markEffect} is applied. This
    +241   * happens recursively for the entire model tree of {@code source} (and, consequently, {@code target}).</p>
    +242   *
    +243   * <p>After all field computations are complete, the builder is built (and casted, if necessary), before being handed
    +244   * back to invoking code.</p>
    +245   *
    +246   * @see FetchOptions.MaskMode Determines how "marked" fields are treated.
    +247   * @param target Builder to set each field value on, as appropriate.
    +248   * @param source Source instance to pull fields and field values from.
    +249   * @param markedPaths "Marked" paths - each one will be treated, as encountered, according to {@code markEffect}.
    +250   * @param markEffect Determines how to treat "marked" paths. See {@link FetchOptions.MaskMode} for more information.
    +251   * @param stackPrefix Dotted stack of properties describing the path that got us to this point (via recursion).
    +252   * @return Constructed model, after applying the provided field mask, as applicable.
    +253   */
    +254  default Message.Builder applyFieldsRecursive(@Nonnull Message.Builder target,
    +255                                               @Nonnull Message source,
    +256                                               @Nonnull Set<String> markedPaths,
    +257                                               @Nonnull FetchOptions.MaskMode markEffect,
    +258                                               @Nonnull String stackPrefix) {
    +259    // otherwise, we must examine each field with a value on the `source`, checking against `markedPaths` (if present)
    +260    // as we go. if it matches, we filter through `markEffect` before applying against `target`.
    +261    for (Map.Entry<FieldDescriptor, Object> property : source.getAllFields().entrySet()) {
    +262      FieldDescriptor field = property.getKey();
    +263      boolean skip = false;
    +264      Object value = property.getValue();
    +265      FetchOptions.MaskMode effect = FetchOptions.MaskMode.INCLUDE.equals(markEffect) ?
    +266        FetchOptions.MaskMode.EXCLUDE : FetchOptions.MaskMode.INCLUDE;
    +267
    +268      String currentPath = stackPrefix.isEmpty() ? field.getName() : stackPrefix + "." + field.getName();
    +269
    +270      boolean marked = markedPaths.contains(currentPath);
    +271      if (!FieldDescriptor.Type.MESSAGE.equals(field.getType()) && marked) {
    +272        // field is in the marked paths.
    +273        effect = markEffect;
    +274      } else if (FieldDescriptor.Type.MESSAGE.equals(field.getType())) {
    +275        effect = FetchOptions.MaskMode.INCLUDE;  // always include messages
    +276      }
    +277
    +278      switch (effect) {
    +279        case PROJECTION:
    +280        case INCLUDE:
    +281          if (Internals.logging.isDebugEnabled()) {
    +282            Internals.logging.debug(format(
    +283              "Field '%s' (%s) included because it did not violate expectation %s via field mask.",
    +284              currentPath,
    +285              field.getFullName(),
    +286              markEffect.name()));
    +287          }
    +288
    +289          // handle recursive cases first
    +290          if (FieldDescriptor.Type.MESSAGE.equals(field.getType())) {
    +291            target.setField(
    +292              field,
    +293              applyFieldsRecursive(
    +294                target.getFieldBuilder(field),
    +295                (Message)value,
    +296                markedPaths,
    +297                markEffect,
    +298                currentPath).build());
    +299
    +300          } else {
    +301            // it's a simple field value
    +302            target.setField(field, value);
    +303          }
    +304          break;
    +305
    +306        case EXCLUDE:
    +307          if (Internals.logging.isDebugEnabled()) {
    +308            Internals.logging.debug(format(
    +309              "Excluded field '%s' (%s) because it did not meet expectation %s via field mask.",
    +310              currentPath,
    +311              field.getFullName(),
    +312              markEffect.name()));
    +313          }
    +314      }
    +315    }
    +316    return target;
    +317  }
    +318
    +319  /**
    +320   * Apply mask-related options to the provided instance. This may include re-building <i>without</i> certain fields, so
    +321   * the instance returned may be different.
    +322   *
    +323   * @param instance Instance to filter based on any provided field mask.k
    +324   * @param options Options to apply to the provided instance.
    +325   * @return Model, post-filtering.
    +326   */
    +327  @VisibleForTesting
    +328  default Model applyMask(@Nonnull Model instance, @Nonnull FetchOptions options) {
    +329    // do we have a mask to apply? does it have fields?
    +330    if (instance.isInitialized()
    +331        && options.fieldMask().isPresent()
    +332        && options.fieldMask().get().getPathsCount() > 0) {
    +333      if (Internals.logging.isTraceEnabled())
    +334        Internals.logging.trace(format("Found valid field mask, applying: '%s'.", options.fieldMask().get()));
    +335
    +336      // resolve mask & mode
    +337      FieldMask mask = options.fieldMask().get();
    +338      FetchOptions.MaskMode maskMode = Objects.requireNonNull(options.fieldMaskMode(),
    +339        "Cannot provide `null` for field mask mode.");
    +340
    +341      //noinspection unchecked
    +342      return (Model)applyFieldsRecursive(
    +343        instance.newBuilderForType(),
    +344        instance,
    +345        ImmutableSet.copyOf(Objects.requireNonNull(mask.getPathsList())),
    +346        maskMode,
    +347        "" /* root path */).build();
    +348    }
    +349    if (Internals.logging.isTraceEnabled())
    +350      Internals.logging.trace("No field mask found. Skipping mask application.");
    +351    return instance;
    +352  }
    +353
    +354  // -- API: Fetch -- //
    +355  /**
    +356   * Synchronously retrieve a data model instance from underlying storage, addressed by its unique ID.
    +357   *
    +358   * <p>If the record cannot be located by the storage engine, {@code null} will be returned instead. For a safe variant
    +359   * of this method (relying on {@link Optional}), see {@link #fetchSafe(Message)}.</p>
    +360   *
    +361   * <p><b>Note:</b> Asynchronous and reactive versions of this method also exist. You should always consider using
    +362   * those if your requirements allow.</p>
    +363   *
    +364   * @see #fetchAsync(Message) For an async version of this method, which produces a {@link ListenableFuture}.
    +365   * @see #fetchSafe(Message) For a safe version of this method, which uses {@link Optional} instead of null.
    +366   * @see #fetchReactive(Message) For a reactive version of this method, which produces a {@link Publisher}.
    +367   * @param key Key at which we should look for the requested entity, and return it if found.
    +368   * @return Requested record, as a model instance, or {@code null} if one could not be found.
    +369   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested instance.
    +370   */
    +371  default @Nullable Model fetch(@Nonnull Key key) throws PersistenceException {
    +372    return fetch(key, FetchOptions.DEFAULTS);
    +373  }
    +374
    +375  /**
    +376   * Synchronously retrieve a data model instance from underlying storage, addressed by its unique ID.
    +377   *
    +378   * <p>If the record cannot be located by the storage engine, {@code null} will be returned instead. For a safe
    +379   * variant of this method (relying on {@link Optional}), see {@link #fetchSafe(Message)}}. This variant
    +380   * additionally allows specification of {@link FetchOptions}.</p>
    +381   *
    +382   * <p><b>Note:</b> Asynchronous and reactive versions of this method also exist. You should always consider using
    +383   * those if your requirements allow.</p>
    +384   *
    +385   * @see #fetchAsync(Message) For an async version of this method, which produces a {@link ListenableFuture}.
    +386   * @see #fetchSafe(Message) For a safe version of this method, which uses {@link Optional} instead of null.
    +387   * @see #fetchReactive(Message) For a reactive version of this method, which produces a {@link Publisher}.
    +388   * @param key Key at which we should look for the requested entity, and return it if found.
    +389   * @param options Options to apply to this individual retrieval operation.
    +390   * @return Requested record, as a model instance, or {@code null} if one could not be found.
    +391   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +392   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested instance.
    +393   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +394   */
    +395  default @Nullable Model fetch(@Nonnull Key key, @Nullable FetchOptions options) throws PersistenceException {
    +396    Optional<Model> msg = fetchSafe(key, options);
    +397    return msg.orElse(null);
    +398  }
    +399
    +400  /**
    +401   * Safely (and synchronously) retrieve a data model instance from storage, returning {@link Optional#empty()} if it
    +402   * cannot be located, rather than {@code null}.
    +403   *
    +404   * <p><b>Note:</b> Asynchronous and reactive versions of this method also exist. You should always consider using
    +405   * those if your requirements allow. All of the reactive/async methods support null safety with {@link Optional}.</p>
    +406   *
    +407   * @see #fetch(Message) For a simpler, but {@code null}-unsafe version of this method.
    +408   * @see #fetchAsync(Message) For an async version of this metho, which produces a {@link ListenableFuture}.
    +409   * @see #fetchReactive(Message) For a reactive version of this method, which produces a {@link Publisher}.
    +410   * @param key Key at which we should look for the requested entity, and return it if found.
    +411   * @return Requested record, as a model instance, or {@link Optional#empty()} if it cannot be found.
    +412   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +413   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested resource.
    +414   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +415   */
    +416  default @Nonnull Optional<Model> fetchSafe(@Nonnull Key key) throws PersistenceException {
    +417    return fetchSafe(key, FetchOptions.DEFAULTS);
    +418  }
    +419
    +420  /**
    +421   * Safely (and synchronously) retrieve a data model instance from storage, returning {@link Optional#empty()} if it
    +422   * cannot be located, rather than {@code null}.
    +423   *
    +424   * <p>This variant additionally allows specification of {@link FetchOptions}.</p>
    +425   *
    +426   * <p><b>Note:</b> Asynchronous and reactive versions of this method also exist. You should always consider using
    +427   * those if your requirements allow. All of the reactive/async methods support null safety with {@link Optional}.</p>
    +428   *
    +429   * @see #fetch(Message) For a simpler, but {@code null}-unsafe version of this method.
    +430   * @see #fetchAsync(Message) For an async version of this metho, which produces a {@link ListenableFuture}.
    +431   * @see #fetchReactive(Message) For a reactive version of this method, which produces a {@link Publisher}.
    +432   * @param key Key at which we should look for the requested entity, and return it if found.
    +433   * @param options Options to apply to this individual retrieval operation.
    +434   * @return Requested record, as a model instance, or {@link Optional#empty()} if it cannot be found.
    +435   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +436   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested resource.
    +437   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +438   */
    +439  @Nonnull
    +440  default Optional<Model> fetchSafe(@Nonnull Key key, @Nullable FetchOptions options) throws PersistenceException {
    +441    if (Internals.logging.isTraceEnabled())
    +442      Internals.logging.trace(format("Synchronously fetching model with key '%s'. Options follow.\n%s",
    +443        key, options));
    +444    return Internals.convertAsyncExceptions(() -> {
    +445      FetchOptions resolvedOptions = options != null ? options : FetchOptions.DEFAULTS;
    +446      return this.fetchAsync(key, options).get(
    +447        resolvedOptions.timeoutValue().orElse(DEFAULT_TIMEOUT),
    +448        resolvedOptions.timeoutUnit().orElse(DEFAULT_TIMEOUT_UNIT));
    +449    });
    +450  }
    +451
    +452  /**
    +453   * Reactively retrieve a data model instance from storage, emitting it over a {@link Publisher} wrapped in an
    +454   * {@link Optional}.
    +455   *
    +456   * <p>In other words, if the model cannot be located, exactly one {@link Optional#empty()} will be emitted over the
    +457   * channel. If the model is successfully located and retrieved, it is emitted exactly once. See other method variants,
    +458   * which allow specification of additional options.</p>
    +459   *
    +460   * <p><b>Exceptions:</b> Instead of throwing a {@link PersistenceException} as other methods do, this operation will
    +461   * <i>emit</i> the exception over the {@link Publisher} channel instead, to enable reactive exception handling.</p>
    +462   *
    +463   * @see #fetch(Message) For a simple, synchronous ({@code null}-unsafe) version of this method.
    +464   * @see #fetchAsync(Message) For an async version of this method, which produces a {@link ListenableFuture}.
    +465   * @see #fetchReactive(Message, FetchOptions) For a variant of this method that allows specification of options.
    +466   * @param key Key at which we should look for the requested entity, and emit it if found.
    +467   * @return Publisher which will receive exactly-one emitted {@link Optional#empty()}, or wrapped object.
    +468   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +469   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested resource.
    +470   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +471   */
    +472  default @Nonnull ReactiveFuture<Optional<Model>> fetchReactive(@Nonnull Key key) {
    +473    return fetchReactive(key, FetchOptions.DEFAULTS);
    +474  }
    +475
    +476  /**
    +477   * Reactively retrieve a data model instance from storage, emitting it over a {@link Publisher} wrapped in an
    +478   * {@link Optional}.
    +479   *
    +480   * <p>In other words, if the model cannot be located, exactly one {@link Optional#empty()} will be emitted over the
    +481   * channel. If the model is successfully located and retrieved, it is emitted exactly once. See other method variants,
    +482   * which allow specification of additional options. This method variant additionally allows the specification of
    +483   * {@link FetchOptions}.</p>
    +484   *
    +485   * <p><b>Exceptions:</b> Instead of throwing a {@link PersistenceException} as other methods do, this operation will
    +486   * <i>emit</i> the exception over the {@link Publisher} channel instead, to enable reactive exception handling.</p>
    +487   *
    +488   * @see #fetch(Message) For a simple, synchronous ({@code null}-unsafe) version of this method.
    +489   * @see #fetchAsync(Message) For an async version of this method, which produces a {@link ListenableFuture}.
    +490   * @param key Key at which we should look for the requested entity, and emit it if found.
    +491   * @param options Options to apply to this individual retrieval operation.
    +492   * @return Publisher which will receive exactly-one emitted {@link Optional#empty()}, or wrapped object.
    +493   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +494   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested resource.
    +495   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +496   */
    +497  default @Nonnull ReactiveFuture<Optional<Model>> fetchReactive(@Nonnull Key key, @Nullable FetchOptions options) {
    +498    return this.fetchAsync(key, options);
    +499  }
    +500
    +501  /**
    +502   * Asynchronously retrieve a data model instance from storage, which will populate the provided {@link Future} value.
    +503   *
    +504   * <p>All futures emitted via the persistence framework (and Gust writ-large) are {@link ListenableFuture}-compliant
    +505   * implementations under the hood. If the requested record cannot be located, {@link Optional#empty()} is returned as
    +506   * the future value, otherwise, the model is returned. See other method variants, which allow specification of
    +507   * additional options.</p>
    +508   *
    +509   * <p><b>Exceptions:</b> Instead of throwing a {@link PersistenceException} as other methods do, this operation will
    +510   * <i>emit</i> the exception over the {@link Future} channel instead, or raise the exception in the event
    +511   * {@link Future#get()} is called to surface it in the invoking (or dependent) code.</p>
    +512   *
    +513   * @see #fetch(Message) For a simple, synchronous ({@code null}=unsafe) version of this method.
    +514   * @see #fetchSafe(Message) For a simple, synchronous ({@code null}-safe) version of this method.
    +515   * @see #fetchReactive(Message) For a reactive version of this method, which returns a {@link Publisher}.
    +516   * @see #fetchAsync(Message, FetchOptions) For a variant of this method which supports {@link FetchOptions}.
    +517   * @param key Key at which we should look for the requested entity, and emit it if found.
    +518   * @return Future value, which resolves to the specified datamodel instance, or {@link Optional#empty()} if the record
    +519   *         could not be located by the storage engine.
    +520   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +521   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested resource.
    +522   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +523   */
    +524  default @Nonnull ReactiveFuture<Optional<Model>> fetchAsync(@Nonnull Key key) {
    +525    return fetchAsync(key, FetchOptions.DEFAULTS);
    +526  }
    +527
    +528  /**
    +529   * Asynchronously retrieve a data model instance from storage, which will populate the provided {@link Future} value.
    +530   *
    +531   * <p>All futures emitted via the persistence framework (and Gust writ-large) are {@link ListenableFuture}-compliant
    +532   * implementations under the hood. If the requested record cannot be located, {@link Optional#empty()} is returned as
    +533   * the future value, otherwise, the model is returned.</p>
    +534   *
    +535   * <p>This method additionally enables specification of custom {@link FetchOptions}, which are applied on a per-
    +536   * operation basis to override global defaults.</p>
    +537   *
    +538   * <p><b>Exceptions:</b> Instead of throwing a {@link PersistenceException} as other methods do, this operation will
    +539   * <i>emit</i> the exception over the {@link Future} channel instead, or raise the exception in the event
    +540   * {@link Future#get()} is called to surface it in the invoking (or dependent) code.</p>
    +541   *
    +542   * @see #fetch(Message) For a simple, synchronous ({@code null}=unsafe) version of this method.
    +543   * @see #fetchSafe(Message) For a simple, synchronous ({@code null}-safe) version of this method.
    +544   * @see #fetchReactive(Message) For a reactive version of this method, which returns a {@link Publisher}.
    +545   * @param key Key at which we should look for the requested entity, and emit it if found.
    +546   * @param options Options to apply to this individual retrieval operation.
    +547   * @return Future value, which resolves to the specified datamodel instance, or {@link Optional#empty()} if the record
    +548   *         could not be located by the storage engine.
    +549   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +550   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested resource.
    +551   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +552   */
    +553  @OverridingMethodsMustInvokeSuper
    +554  default @Nonnull ReactiveFuture<Optional<Model>> fetchAsync(@Nonnull Key key, @Nullable FetchOptions options) {
    +555    if (Internals.logging.isTraceEnabled())
    +556      Internals.logging.trace(format("Fetching model with key '%s' asynchronously. Options follow.\n%s",
    +557        key, options));
    +558    return this.retrieve(key, options != null ? options : FetchOptions.DEFAULTS);
    +559  }
    +560
    +561  /**
    +562   * Low-level record retrieval method. Effectively called by all other fetch variants. Asynchronously retrieve a data
    +563   * model instance from storage, which will populate the provided {@link ReactiveFuture} value.
    +564   *
    +565   * <p>All futures emitted via the persistence framework (and Gust writ-large) are {@link ListenableFuture}-compliant
    +566   * implementations under the hood. If the requested record cannot be located, {@link Optional#empty()} is returned as
    +567   * the future value, otherwise, the model is returned.</p>
    +568   *
    +569   * <p>This method additionally enables specification of custom {@link FetchOptions}, which are applied on a per-
    +570   * operation basis to override global defaults.</p>
    +571   *
    +572   * <p><b>Exceptions:</b> Instead of throwing a {@link PersistenceException} as other methods do, this operation will
    +573   * <i>emit</i> the exception over the {@link Future} channel instead, or raise the exception in the event
    +574   * {@link Future#get()} is called to surface it in the invoking (or dependent) code.</p>
    +575   *
    +576   * @see #fetch(Message) For a simple, synchronous ({@code null}=unsafe) version of this method.
    +577   * @see #fetchSafe(Message) For a simple, synchronous ({@code null}-safe) version of this method.
    +578   * @see #fetchAsync(Message) For an async variant of this method (identical, except options are optional).
    +579   * @see #fetchReactive(Message) For a reactive version of this method, which returns a {@link Publisher}.
    +580   * @param key Key at which we should look for the requested entity, and emit it if found.
    +581   * @param options Options to apply to this individual retrieval operation.
    +582   * @return Future value, which resolves to the specified datamodel instance, or {@link Optional#empty()} if the record
    +583   *         could not be located by the storage engine.
    +584   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +585   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested resource.
    +586   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +587   */
    +588  @Nonnull ReactiveFuture<Optional<Model>> retrieve(@Nonnull Key key, @Nonnull FetchOptions options);
    +589
    +590  // -- API: Persist -- //
    +591  /**
    +592   * Create the record specified by {@code model} in underlying storage, provisioning a key or ID for the record if
    +593   * needed. The persisted entity is returned or an error occurs.
    +594   *
    +595   * <p>This operation will enforce the option {@code MUST_NOT_EXIST} for the write - i.e., "creating" a record implies
    +596   * that it must not exist beforehand. Additionally, if the record is missing a unique ID or key (one or the other must
    +597   * be annotated on the record), then a semi-random value will be generated for the record.</p>
    +598   *
    +599   * <p>The returned record will be re-constituted, with the spliced-in ID or key value, as applicable, and with any
    +600   * computed or framework-related properties filled in (i.e. automatic timestamping).</p>
    +601   *
    +602   * @param model Model to create in underlying storage. Requires a {@code ID} or {@code KEY}-annotated field.
    +603   * @return Future value, which resolves to the stored model entity, affixed with an assigned ID or key.
    +604   * @throws InvalidModelType If the specified model record is not usable with storage.
    +605   * @throws PersistenceException If an unexpected failure occurs, of any kind, while creating the record.
    +606   * @throws MissingAnnotatedField If a required annotated field cannot be located (i.e. {@code ID} or {@code KEY}).
    +607   */
    +608  default @Nonnull ReactiveFuture<Model> create(@Nonnull Model model) {
    +609    //noinspection unchecked
    +610    return create((Key)key(model).orElse(null), model);
    +611  }
    +612
    +613  /**
    +614   * Create the record specified by {@code model} using the optional pre-fabricated {@code key}, in underlying storage.
    +615   * If the provided key is empty or {@code null}, the engine will provision a key or ID for the record. The persisted
    +616   * entity is returned or an error occurs.
    +617   *
    +618   * <p>This operation will enforce the option {@code MUST_NOT_EXIST} for the write - i.e., "creating" a record implies
    +619   * that it must not exist beforehand. Additionally, if the record is missing a unique ID or key (one or the other must
    +620   * be annotated on the record), then a semi-random value will be generated for the record.</p>
    +621   *
    +622   * <p>The returned record will be re-constituted, with the spliced-in ID or key value, as applicable, and with any
    +623   * computed or framework-related properties filled in (i.e. automatic timestamping).</p>
    +624   *
    +625   * @param model Model to create in underlying storage. Requires a {@code ID} or {@code KEY}-annotated field.
    +626   * @return Future value, which resolves to the stored model entity, affixed with an assigned ID or key.
    +627   * @throws InvalidModelType If the specified model record is not usable with storage.
    +628   * @throws PersistenceException If an unexpected failure occurs, of any kind, while creating the record.
    +629   * @throws MissingAnnotatedField If a required annotated field cannot be located (i.e. {@code ID} or {@code KEY}).
    +630   */
    +631  default @Nonnull ReactiveFuture<Model> create(@Nullable Key key, @Nonnull Model model) {
    +632    return create(key, model, new WriteOptions() {
    +633      @Override
    +634      public @Nonnull Optional<WriteDisposition> writeMode() {
    +635        return Optional.of(WriteDisposition.MUST_NOT_EXIST);
    +636      }
    +637    });
    +638  }
    +639
    +640  /**
    +641   * Create the record specified by {@code model} using the specified set of {@code options}, in underlying storage. If
    +642   * the provided mode's key or ID is empty or {@code null}, the engine will provision a key or ID for the record. The
    +643   * persisted entity is returned or an error occurs.
    +644   *
    +645   * <p>This operation will enforce the option {@code MUST_NOT_EXIST} for the write - i.e., "creating" a record implies
    +646   * that it must not exist beforehand. Additionally, if the record is missing a unique ID or key (one or the other must
    +647   * be annotated on the record), then a semi-random value will be generated for the record.</p>
    +648   *
    +649   * <p>The returned record will be re-constituted, with the spliced-in ID or key value, as applicable, and with any
    +650   * computed or framework-related properties filled in (i.e. automatic timestamping).</p>
    +651   *
    +652   * @param model Model to create in underlying storage. Requires a {@code ID} or {@code KEY}-annotated field.
    +653   * @return Future value, which resolves to the stored model entity, affixed with an assigned ID or key.
    +654   * @throws InvalidModelType If the specified model record is not usable with storage.
    +655   * @throws PersistenceException If an unexpected failure occurs, of any kind, while creating the record.
    +656   * @throws MissingAnnotatedField If a required annotated field cannot be located (i.e. {@code ID} or {@code KEY}).
    +657   */
    +658  default @Nonnull ReactiveFuture<Model> create(@Nonnull Model model, @Nonnull WriteOptions options) {
    +659    //noinspection unchecked
    +660    return create((Key)key(model).orElse(null), model, options);
    +661  }
    +662
    +663  /**
    +664   * Create the record specified by {@code model} using the optional pre-fabricated {@code key}, and making use of the
    +665   * specified {@code options}, in underlying storage. If the provided key is empty or {@code null}, the engine will
    +666   * provision a key or ID for the record. The persisted entity is returned or an error occurs.
    +667   *
    +668   * <p>This operation will enforce the option {@code MUST_NOT_EXIST} for the write - i.e., "creating" a record implies
    +669   * that it must not exist beforehand. Additionally, if the record is missing a unique ID or key (one or the other must
    +670   * be annotated on the record), then a semi-random value will be generated for the record.</p>
    +671   *
    +672   * <p>The returned record will be re-constituted, with the spliced-in ID or key value, as applicable, and with any
    +673   * computed or framework-related properties filled in (i.e. automatic timestamping).</p>
    +674   *
    +675   * @param model Model to create in underlying storage. Requires a {@code ID} or {@code KEY}-annotated field.
    +676   * @return Future value, which resolves to the stored model entity, affixed with an assigned ID or key.
    +677   * @throws InvalidModelType If the specified model record is not usable with storage.
    +678   * @throws PersistenceException If an unexpected failure occurs, of any kind, while creating the record.
    +679   * @throws MissingAnnotatedField If a required annotated field cannot be located (i.e. {@code ID} or {@code KEY}).
    +680   * @throws IllegalArgumentException If an incompatible {@link WriteOptions.WriteDisposition} value is specified.
    +681   */
    +682  @Nonnull
    +683  default ReactiveFuture<Model> create(@Nullable Key key, @Nonnull Model model, @Nonnull WriteOptions options) {
    +684    Internals.enforceOption(
    +685      options.writeMode()
    +686        .orElse(WriteOptions.WriteDisposition.MUST_NOT_EXIST),
    +687      WriteOptions.WriteDisposition.MUST_NOT_EXIST,
    +688      "Write options for `create` must specify `MUST_NOT_EXIST` write disposition.");
    +689    return persist(key, model, options);
    +690  }
    +691
    +692  /**
    +693   * Update the record specified by {@code model} in underlying storage, using the existing key or ID value affixed to
    +694   * the model. The entity is returned in its updated form, or an error occurs.
    +695   *
    +696   * <p>This operation will enforce the option {@code MUST_EXIST} for the write - i.e., "updating" a record implies that
    +697   * it must exist beforehand. This means, if the record is missing a unique ID or key (one or the other must be
    +698   * annotated on the record), then an error occurs (specifically, either {@link MissingAnnotatedField}) for a  missing
    +699   * schema field, or {@link IllegalStateException} for a missing required value).</p>
    +700   *
    +701   * <p>The returned record will be re-constituted, with the ID or key value unmodified, as applicable, and with any
    +702   * computed or framework-related properties updated in (i.e. automatic update timestamping).</p>
    +703   *
    +704   * @param model Model to update in underlying storage. Requires a {@code ID} or {@code KEY}-annotated field and value.
    +705   * @return Future value, which resolves to the stored model entity, after it has been updated.
    +706   * @throws InvalidModelType If the specified model record is not usable with storage.
    +707   * @throws PersistenceException If an unexpected failure occurs, of any kind, while updated the record.
    +708   * @throws MissingAnnotatedField If a required annotated field cannot be located (i.e. {@code ID} or {@code KEY}).
    +709   * @throws IllegalStateException If a required annotated field value cannot be resolved (i.e. an empty key or ID).
    +710   */
    +711  default @Nonnull ReactiveFuture<Model> update(@Nonnull Model model) {
    +712    //noinspection unchecked
    +713    return update(
    +714      (Key)key(model).orElseThrow(() -> new IllegalStateException("Failed to resolve a key value for record.")),
    +715      model);
    +716  }
    +717
    +718  /**
    +719   * Update the record specified by {@code model} in underlying storage, making use of the specified {@code options},
    +720   * using the existing key or ID value affixed to the model. The entity is returned in its updated form, or an error
    +721   * occurs.
    +722   *
    +723   * <p>This operation will enforce the option {@code MUST_EXIST} for the write - i.e., "updating" a record implies that
    +724   * it must exist beforehand. This means, if the record is missing a unique ID or key (one or the other must be
    +725   * annotated on the record), then an error occurs (specifically, either {@link MissingAnnotatedField}) for a  missing
    +726   * schema field, or {@link IllegalStateException} for a missing required value).</p>
    +727   *
    +728   * <p>The returned record will be re-constituted, with the ID or key value unmodified, as applicable, and with any
    +729   * computed or framework-related properties updated in (i.e. automatic update timestamping).</p>
    +730   *
    +731   * @param model Model to update in underlying storage. Requires a {@code ID} or {@code KEY}-annotated field and value.
    +732   * @return Future value, which resolves to the stored model entity, after it has been updated.
    +733   * @throws InvalidModelType If the specified model record is not usable with storage.
    +734   * @throws PersistenceException If an unexpected failure occurs, of any kind, while updated the record.
    +735   * @throws MissingAnnotatedField If a required annotated field cannot be located (i.e. {@code ID} or {@code KEY}).
    +736   * @throws IllegalStateException If a required annotated field value cannot be resolved (i.e. an empty key or ID).
    +737   */
    +738  default @Nonnull ReactiveFuture<Model> update(@Nonnull Model model, @Nonnull UpdateOptions options) {
    +739    //noinspection unchecked
    +740    return update(
    +741      (Key)key(model).orElseThrow(() -> new IllegalStateException("Failed to resolve a key value for record.")),
    +742      model,
    +743      options);
    +744  }
    +745
    +746  /**
    +747   * Update the record specified by {@code model}, and addressed by {@code key}, in underlying storage. The entity is
    +748   * returned in its updated form, or an error occurs.
    +749   *
    +750   * <p>This operation will enforce the option {@code MUST_EXIST} for the write - i.e., "updating" a record implies that
    +751   * it must exist beforehand. This means, if the record is missing a unique ID or key (one or the other must be
    +752   * annotated on the record), then an error occurs (specifically, either {@link MissingAnnotatedField}) for a  missing
    +753   * schema field, or {@link IllegalStateException} for a missing required value).</p>
    +754   *
    +755   * <p>The returned record will be re-constituted, with the ID or key value unmodified, as applicable, and with any
    +756   * computed or framework-related properties updated in (i.e. automatic update timestamping).</p>
    +757   *
    +758   * @param model Model to update in underlying storage. Requires a {@code ID} or {@code KEY}-annotated field and value.
    +759   * @return Future value, which resolves to the stored model entity, after it has been updated.
    +760   * @throws InvalidModelType If the specified model record is not usable with storage.
    +761   * @throws PersistenceException If an unexpected failure occurs, of any kind, while updated the record.
    +762   * @throws MissingAnnotatedField If a required annotated field cannot be located (i.e. {@code ID} or {@code KEY}).
    +763   * @throws IllegalStateException If a required annotated field value cannot be resolved (i.e. an empty key or ID).
    +764   */
    +765  default @Nonnull ReactiveFuture<Model> update(@Nonnull Key key, @Nonnull Model model) {
    +766    return update(key, model, new UpdateOptions() {
    +767      @Override
    +768      public @Nonnull Optional<WriteDisposition> writeMode() {
    +769        return Optional.of(WriteDisposition.MUST_EXIST);
    +770      }
    +771    });
    +772  }
    +773
    +774  /**
    +775   * Update the record specified by {@code model}, and addressed by {@code key}, in underlying storage. The entity is
    +776   * returned in its updated form, or an error occurs. This method variant additionally allows specification of custom
    +777   * {@code options} for this individual operation.
    +778   *
    +779   * <p>This operation will enforce the option {@code MUST_EXIST} for the write - i.e., "updating" a record implies that
    +780   * it must exist beforehand. This means, if the record is missing a unique ID or key (one or the other must be
    +781   * annotated on the record), then an error occurs (specifically, either {@link MissingAnnotatedField}) for a  missing
    +782   * schema field, or {@link IllegalStateException} for a missing required value).</p>
    +783   *
    +784   * <p>The returned record will be re-constituted, with the ID or key value unmodified, as applicable, and with any
    +785   * computed or framework-related properties updated in (i.e. automatic update timestamping).</p>
    +786   *
    +787   * @param model Model to update in underlying storage. Requires a {@code ID} or {@code KEY}-annotated field and value.
    +788   * @return Future value, which resolves to the stored model entity, after it has been updated.
    +789   * @throws InvalidModelType If the specified model record is not usable with storage.
    +790   * @throws PersistenceException If an unexpected failure occurs, of any kind, while updated the record.
    +791   * @throws MissingAnnotatedField If a required annotated field cannot be located (i.e. {@code ID} or {@code KEY}).
    +792   * @throws IllegalStateException If a required annotated field value cannot be resolved (i.e. an empty key or ID).
    +793   * @throws IllegalArgumentException If an incompatible {@link WriteOptions.WriteDisposition} value is specified.
    +794   */
    +795  @Nonnull
    +796  default ReactiveFuture<Model> update(@Nonnull Key key, @Nonnull Model model, @Nonnull UpdateOptions options) {
    +797    Internals.enforceOption(
    +798      options.writeMode().orElse(WriteOptions.WriteDisposition.MUST_EXIST),
    +799      WriteOptions.WriteDisposition.MUST_EXIST,
    +800      "Write options for `update` must specify `MUST_EXIST` write disposition.");
    +801    return persist(key, model, options);
    +802  }
    +803
    +804  /**
    +805   * Low-level record persistence method. Effectively called by all other create/put variants. Asynchronously write a
    +806   * data model instance to storage, which will populate the provided {@link ReactiveFuture} value.
    +807   *
    +808   * <p>Optionally, a key may be provided as a nominated value to the storage engine. Whether the engine accepts
    +809   * nominated keys is up to the implementation. In all cases, the engine must return the key used to store and address
    +810   * the value henceforth. If the engine <i>does</i> support nominated keys, it <i>must</i> operate in an idempotent
    +811   * manner with regard to those keys. In other words, repeated calls to create the same entity with the same key will
    +812   * not cause spurious side-effects - only one record will be created, with the remaining calls being rejected by the
    +813   * underlying engine.</p>
    +814   *
    +815   * <p>All futures emitted via the persistence framework (and Gust writ-large) are {@link ListenableFuture}-compliant
    +816   * implementations under the hood, but {@link ReactiveFuture} allows a model-layer result to be used as a
    +817   * {@link Future}, or a one-item reactive {@link Publisher}.</p>
    +818   *
    +819   * <p>This method additionally enables specification of custom {@link WriteOptions}, which are applied on a per-
    +820   * operation basis to override global defaults.</p>
    +821   *
    +822   * <p><b>Exceptions:</b> Instead of throwing a {@link PersistenceException} as other methods do, this operation will
    +823   * <i>emit</i> the exception over the {@link Future} channel instead, or raise the exception in the event
    +824   * {@link Future#get()} is called to surface it in the invoking (or dependent) code.</p>
    +825   *
    +826   * @param key Key nominated by invoking code for storing this record. If no key is provided, the underlying storage
    +827   *            engine is expected to allocate one. Where unsupported, {@link PersistenceException} will be thrown.
    +828   * @param model Model to store at the specified key, if provided.
    +829   * @param options Options to apply to this persist operation.
    +830   * @return Reactive future, which resolves to the key where the provided model is now stored. In no case should this
    +831   *         method return {@code null}. Instead, {@link PersistenceException} will be thrown.
    +832   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +833   * @throws PersistenceException If an unexpected failure occurs, of any kind, while fetching the requested resource.
    +834   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +835   */
    +836  @Nonnull ReactiveFuture<Model> persist(@Nullable Key key, @Nonnull Model model, @Nonnull WriteOptions options);
    +837
    +838  // -- API: Delete -- //
    +839  /**
    +840   * Delete and fully erase the record referenced by {@code key} from underlying storage, permanently. The resulting
    +841   * future resolves to the provided key value once the operation completes. If any issue occurs (besides encountering
    +842   * an already-deleted entity, which is not an error), an exception is raised.
    +843   *
    +844   * @param key Key referring to the record which should be deleted, permanently, from underlying storage.
    +845   * @return Future, which resolves to the provided key when the operation is complete.
    +846   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +847   * @throws PersistenceException If an unexpected failure occurs, of any kind, while deleting the requested resource.
    +848   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +849   * @throws IllegalStateException If a required annotated field value cannot be resolved (i.e. an empty key or ID).
    +850   */
    +851  default @Nonnull ReactiveFuture<Key> delete(@Nonnull Key key) {
    +852    return delete(key, DeleteOptions.DEFAULTS);
    +853  }
    +854
    +855  /**
    +856   * Delete and fully erase the supplied {@code model} from underlying storage, permanently. The resulting future
    +857   * resolves to the provided record's key value once the operation completes. If any issue occurs (besides encountering
    +858   * an already-deleted entity, which is not an error), an exception is raised.
    +859   *
    +860   * @param model Model instance to delete from underlying storage.
    +861   * @return Future, which resolves to the provided key when the operation is complete.
    +862   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +863   * @throws PersistenceException If an unexpected failure occurs, of any kind, while deleting the requested resource.
    +864   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +865   * @throws IllegalStateException If a required annotated field value cannot be resolved (i.e. an empty key or ID).
    +866   */
    +867  default @Nonnull ReactiveFuture<Key> deleteRecord(@Nonnull Model model) {
    +868    return deleteRecord(model, DeleteOptions.DEFAULTS);
    +869  }
    +870
    +871  /**
    +872   * Delete and fully erase the supplied {@code model} from underlying storage, permanently. The resulting future
    +873   * resolves to the provided record's key value once the operation completes. If any issue occurs (besides encountering
    +874   * an already-deleted entity, which is not an error), an exception is raised.
    +875   *
    +876   * @param model Model instance to delete from underlying storage.
    +877   * @param options Options to apply to this specific delete operation.
    +878   * @return Future, which resolves to the provided key when the operation is complete.
    +879   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +880   * @throws PersistenceException If an unexpected failure occurs, of any kind, while deleting the requested resource.
    +881   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +882   * @throws IllegalStateException If a required annotated field value cannot be resolved (i.e. an empty key or ID).
    +883   */
    +884  default @Nonnull ReactiveFuture<Key> deleteRecord(@Nonnull Model model, @Nonnull DeleteOptions options) {
    +885    //noinspection unchecked
    +886    return delete((Key)key(model)
    +887        .orElseThrow(() -> new IllegalStateException("Cannot delete record with empty key/ID.")),
    +888      options);
    +889  }
    +890
    +891  /**
    +892   * Low-level record delete method. Effectively called by all other delete variants. Asynchronously and permanently
    +893   * erase an existing data model instance from storage, addressed by its key unique key or ID.
    +894   *
    +895   * <p>If no key or ID field, or value, may be located, an error is raised (see below for details). This operation is
    +896   * expected to operate in an <i>idempotent</i> manner (i.e. repeated calls with identical parameters do not yield
    +897   * different side effects). Calls referring to an already-deleted entity should silently succeed.</p>
    +898   *
    +899   * <p>All futures emitted via the persistence framework (and Gust writ-large) are {@link ListenableFuture}-compliant
    +900   * implementations under the hood, but {@link ReactiveFuture} allows a model-layer result to be used as a
    +901   * {@link Future}, or a one-item reactive {@link Publisher}.</p>
    +902   *
    +903   * <p>This method additionally enables specification of custom {@link DeleteOptions}, which are applied on a per-
    +904   * operation basis to override global defaults.</p>
    +905   *
    +906   * <p><b>Exceptions:</b> Instead of throwing a {@link PersistenceException} as other methods do, this operation will
    +907   * <i>emit</i> the exception over the {@link Future} channel instead, or raise the exception in the event
    +908   * {@link Future#get()} is called to surface it in the invoking (or dependent) code.</p>
    +909   *
    +910   * @param key Unique key referring to the record in storage that should be deleted.
    +911   * @param options Options to apply to this specific delete operation.
    +912   * @return Future value, which resolves to the deleted record's key when the operation completes.
    +913   * @throws InvalidModelType If the specified key type is not compatible with model-layer operations.
    +914   * @throws PersistenceException If an unexpected failure occurs, of any kind, while deleting the requested resource.
    +915   * @throws MissingAnnotatedField If the specified key record has no resolvable ID field.
    +916   * @throws IllegalStateException If a required annotated field value cannot be resolved (i.e. an empty key or ID).
    +917   */
    +918  @Nonnull ReactiveFuture<Key> delete(@Nonnull Key key, @Nonnull DeleteOptions options);
    +919}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/PersistenceException.html b/docs/java/src-html/gust/backend/model/PersistenceException.html new file mode 100644 index 000000000..7715a8db6 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/PersistenceException.html @@ -0,0 +1,126 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import javax.annotation.Nonnull;
    +016import javax.annotation.Nullable;
    +017
    +018
    +019/**
    +020 * Defines a class of exceptions which can be encountered when interacting with persistence tools, including internal
    +021 * (built-in) data adapters.
    +022 */
    +023@SuppressWarnings("unused")
    +024public abstract class PersistenceException extends RuntimeException {
    +025  /**
    +026   * Create a persistence exception with a string message.
    +027   *
    +028   * @param message Error message.
    +029   */
    +030  PersistenceException(@Nonnull String message) {
    +031    super(message);
    +032  }
    +033
    +034  /**
    +035   * Create a persistence exception with a throwable as a cause.
    +036   *
    +037   * @param cause Cause for the error.
    +038   */
    +039  PersistenceException(@Nonnull Throwable cause) {
    +040    super(cause);
    +041  }
    +042
    +043  /**
    +044   * Create a persistence exception with a throwable cause and an explicit error message.
    +045   *
    +046   * @param message Error message.
    +047   * @param cause Cause for the error.
    +048   */
    +049  PersistenceException(@Nonnull String message, @Nullable Throwable cause) {
    +050    super(message, cause);
    +051  }
    +052}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/PersistenceFailure.html b/docs/java/src-html/gust/backend/model/PersistenceFailure.html new file mode 100644 index 000000000..5d2faeaf7 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/PersistenceFailure.html @@ -0,0 +1,113 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015
    +016/** Enumerates common kinds of persistence failures. Goes well with {@link PersistenceOperationFailed}. */
    +017public enum PersistenceFailure {
    +018  /** The operation timed out. */
    +019  TIMEOUT,
    +020
    +021  /** The operation was cancelled. */
    +022  CANCELLED,
    +023
    +024  /** The operation was interrupted. */
    +025  INTERRUPTED,
    +026
    +027  /** An unknown internal error occurred. */
    +028  INTERNAL;
    +029
    +030  /** @return Error message for the selected case. */
    +031  String getMessage() {
    +032    switch (this) {
    +033      case TIMEOUT: return "The operation timed out.";
    +034      case CANCELLED: return "The operation was cancelled.";
    +035      case INTERRUPTED: return "The operation was interrupted.";
    +036    }
    +037    return "An unknown internal error occurred.";
    +038  }
    +039}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/PersistenceManager.html b/docs/java/src-html/gust/backend/model/PersistenceManager.html new file mode 100644 index 000000000..8a3502b42 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/PersistenceManager.html @@ -0,0 +1,96 @@ + + + +Source code + + + +
    + +
    + + diff --git a/docs/java/src-html/gust/backend/model/PersistenceOperationFailed.html b/docs/java/src-html/gust/backend/model/PersistenceOperationFailed.html new file mode 100644 index 000000000..1e01efaa7 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/PersistenceOperationFailed.html @@ -0,0 +1,141 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import javax.annotation.Nonnull;
    +016import javax.annotation.Nullable;
    +017
    +018
    +019/** Describes a generic operational failure that occurred within the persistence engine. */
    +020@SuppressWarnings("WeakerAccess")
    +021public final class PersistenceOperationFailed extends PersistenceException {
    +022  /** Enumerated failure case. */
    +023  private final @Nonnull PersistenceFailure failure;
    +024
    +025  /**
    +026   * Main private constructor.
    +027   *
    +028   * @param message Error message to apply.
    +029   * @param cause Optional cause.
    +030   */
    +031  private PersistenceOperationFailed(@Nonnull PersistenceFailure failure,
    +032                                     @Nonnull String message,
    +033                                     @Nullable Throwable cause) {
    +034    super(message, cause);
    +035    this.failure = failure;
    +036  }
    +037
    +038  /**
    +039   * Generate a persistence failure exception for a generic (known) failure case.
    +040   *
    +041   * @param failure Known failure case to spawn an exception for.
    +042   * @return Exception object.
    +043   */
    +044  static @Nonnull PersistenceOperationFailed forErr(@Nonnull PersistenceFailure failure) {
    +045    return new PersistenceOperationFailed(failure, failure.getMessage(), null);
    +046  }
    +047
    +048  /**
    +049   * Generate a persistence failure exception for a generic (known) failure case, optionally applying an inner cause
    +050   * exception to the built object.
    +051   *
    +052   * @param failure Known failure case to spawn an exception for.
    +053   * @param cause Exception object to use as the inner cause.
    +054   * @return Spawned persistence exception object.
    +055   */
    +056  static @Nonnull PersistenceOperationFailed forErr(@Nonnull PersistenceFailure failure,
    +057                                                    @Nullable Throwable cause) {
    +058    return new PersistenceOperationFailed(failure, failure.getMessage(), cause);
    +059  }
    +060
    +061  // -- Getters -- //
    +062
    +063  /** @return Failure case that occurred. */
    +064  public @Nonnull PersistenceFailure getFailure() {
    +065    return failure;
    +066  }
    +067}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/ProtoModelCodec.html b/docs/java/src-html/gust/backend/model/ProtoModelCodec.html new file mode 100644 index 000000000..c268c83fb --- /dev/null +++ b/docs/java/src-html/gust/backend/model/ProtoModelCodec.html @@ -0,0 +1,325 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import com.google.protobuf.Message;
    +016import com.google.protobuf.TypeRegistry;
    +017import com.google.protobuf.util.JsonFormat;
    +018import gust.backend.runtime.Logging;
    +019import org.slf4j.Logger;
    +020
    +021import javax.annotation.Nonnull;
    +022import javax.annotation.Nullable;
    +023import javax.annotation.concurrent.Immutable;
    +024import javax.annotation.concurrent.ThreadSafe;
    +025import java.io.IOException;
    +026import java.nio.charset.StandardCharsets;
    +027import java.util.Objects;
    +028import java.util.Optional;
    +029
    +030
    +031/**
    +032 * Defines a {@link ModelCodec} which uses Protobuf serialization to export and import protos to and from from raw
    +033 * byte-strings. These formats are built into Protobuf and are considered extremely reliable, even across languages.
    +034 *
    +035 * <p>Two formats of Protobuf serialization are supported:
    +036 * <ul>
    +037 *   <li><b>Binary:</b> Most efficient format. Best for production use. Completely illegible to humans.</li>
    +038 *   <li><b>ProtoJSON:</b> Protocol Buffers-defined JSON translation protocol.</li>
    +039 * </ul></p>
    +040 *
    +041 * @see ModelCodec Generic model codec interface.
    +042 */
    +043@Immutable
    +044@ThreadSafe
    +045public final class ProtoModelCodec<Model extends Message> implements ModelCodec<Model, EncodedModel, EncodedModel> {
    +046  /** Default wire format mode. */
    +047  private static final EncodingMode DEFAULT_FORMAT = EncodingMode.BINARY;
    +048
    +049  /** Log pipe to use. */
    +050  private static final Logger logging = Logging.logger(ProtoModelCodec.class);
    +051
    +052  /** Protobuf wire format to use. */
    +053  private final EncodingMode wireMode;
    +054
    +055  /** Builder from which to spawn models. */
    +056  private final Model instance;
    +057
    +058  /** JSON printer utility, initialized when operating with `wireMode=JSON`. */
    +059  private final @Nullable JsonFormat.Printer jsonPrinter;
    +060
    +061  /** JSON parser utility, initialized when operating with `wireMode=JSON`. */
    +062  private final @Nullable JsonFormat.Parser jsonParser;
    +063
    +064  /** Serializer object. */
    +065  private final @Nonnull ModelSerializer<Model, EncodedModel> serializer;
    +066
    +067  /** De-serializer object. */
    +068  private final @Nonnull ModelDeserializer<EncodedModel, Model> deserializer;
    +069
    +070  /**
    +071   * Private constructor. Use static factory methods.
    +072   *
    +073   * @see #forModel(Message) To spawn a proto-codec for a given model.
    +074   * @param instance Model instance (empty) to use for type information.
    +075   * @param mode Mode to apply to this codec instance.
    +076   * @param registry Optional type registry of other types to use with {@link JsonFormat}.
    +077   */
    +078  private ProtoModelCodec(@Nonnull Model instance, @Nonnull EncodingMode mode, @Nullable TypeRegistry registry) {
    +079    this.wireMode = mode;
    +080    this.instance = instance;
    +081    this.serializer = new ProtoMessageSerializer();
    +082    this.deserializer = new ProtoMessageDeserializer();
    +083
    +084    if (logging.isTraceEnabled())
    +085      logging.trace(String.format("Initializing `ProtoModelCodec` with format %s.", mode.name()));
    +086
    +087    if (mode == EncodingMode.JSON) {
    +088      TypeRegistry resolvedRegisry = registry != null ? registry : TypeRegistry.newBuilder()
    +089        .add(instance.getDescriptorForType())
    +090        .build();
    +091
    +092      this.jsonParser = JsonFormat.parser()
    +093        .usingTypeRegistry(resolvedRegisry);
    +094
    +095      this.jsonPrinter = JsonFormat.printer()
    +096        .usingTypeRegistry(resolvedRegisry)
    +097        .sortingMapKeys()
    +098        .omittingInsignificantWhitespace();
    +099
    +100    } else {
    +101      this.jsonParser = null;
    +102      this.jsonPrinter = null;
    +103    }
    +104  }
    +105
    +106  // -- Factories -- //
    +107
    +108  /**
    +109   * Acquire a Protobuf model codec for the provided model instance. The codec will operate in the default
    +110   * {@link EncodingMode} unless specified otherwise via the other method variants on this object.
    +111   *
    +112   * @param <M> Model instance type.
    +113   * @param instance Model instance to return a codec for.
    +114   * @return Model codec which serializes and de-serializes to/from Protobuf wire formats.
    +115   */
    +116  @SuppressWarnings({"WeakerAccess", "unused"})
    +117  public @Nonnull static <M extends Message> ProtoModelCodec<M> forModel(@Nonnull M instance) {
    +118    return forModel(instance, DEFAULT_FORMAT);
    +119  }
    +120
    +121  /**
    +122   * Acquire a Protobuf model codec for the provided model instance. The codec will operate in the default
    +123   * {@link EncodingMode} unless specified otherwise via the other method variants on this object.
    +124   *
    +125   * @param <M> Model instance type.
    +126   * @param instance Model instance to return a codec for.
    +127   * @param mode Wire format mode to operate in (one of {@code JSON} or {@code BINARY}).
    +128   * @return Model codec which serializes and de-serializes to/from Protobuf wire formats.
    +129   */
    +130  public @Nonnull static <M extends Message> ProtoModelCodec<M> forModel(@Nonnull M instance,
    +131                                                                         @Nonnull EncodingMode mode) {
    +132    return forModel(instance, mode, Optional.empty());
    +133  }
    +134
    +135  /**
    +136   * Acquire a Protobuf model codec for the provided model instance. The codec will operate in the default
    +137   * {@link EncodingMode} unless specified otherwise via the other method variants on this object.
    +138   *
    +139   * @param <M> Model instance type.
    +140   * @param instance Model instance to return a codec for.
    +141   * @param mode Wire format mode to operate in (one of {@code JSON} or {@code BINARY}).
    +142   * @return Model codec which serializes and de-serializes to/from Protobuf wire formats.
    +143   */
    +144  @SuppressWarnings("WeakerAccess")
    +145  public @Nonnull static <M extends Message> ProtoModelCodec<M> forModel(@Nonnull M instance,
    +146                                                                         @Nonnull EncodingMode mode,
    +147                                                                         @Nonnull Optional<TypeRegistry> registry) {
    +148    return new ProtoModelCodec<>(
    +149      instance,
    +150      mode,
    +151      registry.orElse(null));
    +152  }
    +153
    +154  /** Serializes model instances into raw bytes, according to Protobuf wire protocol semantics. */
    +155  private final class ProtoMessageSerializer implements ModelSerializer<Model, EncodedModel> {
    +156    /**
    +157     * Serialize a model instance from the provided object type to the specified output type, throwing exceptions
    +158     * verbosely if we are unable to correctly, verifiably, and properly export the record.
    +159     *
    +160     * @param input Input record object to serialize.
    +161     * @return Serialized record data, of the specified output type.
    +162     * @throws ModelDeflateException If the model fails to export or serialize for any reason.
    +163     */
    +164    @Override
    +165    public @Nonnull EncodedModel deflate(@Nonnull Message input) throws ModelDeflateException, IOException {
    +166      if (logging.isDebugEnabled())
    +167        logging.debug(String.format(
    +168          "Deflating record of type '%s' with format %s.",
    +169          input.getDescriptorForType().getFullName(),
    +170          wireMode.name()));
    +171
    +172      if (wireMode == EncodingMode.BINARY) {
    +173        return EncodedModel.wrap(
    +174          input.getDescriptorForType().getFullName(),
    +175          wireMode,
    +176          input.toByteArray());
    +177      } else {
    +178        return EncodedModel.wrap(
    +179          input.getDescriptorForType().getFullName(),
    +180          wireMode,
    +181          Objects.requireNonNull(jsonPrinter).print(input).getBytes(StandardCharsets.UTF_8));
    +182      }
    +183    }
    +184  }
    +185
    +186  /** De-serializes model instances from raw bytes, according to Protobuf wire protocol semantics. */
    +187  private final class ProtoMessageDeserializer implements ModelDeserializer<EncodedModel, Model> {
    +188    /**
    +189     * De-serialize a model instance from the provided input type, throwing exceptions verbosely if we are unable to
    +190     * correctly, verifiably, and properly load the record.
    +191     *
    +192     * @param data Input data or object from which to load the model instance.
    +193     * @return De-serialized and inflated model instance. Always a {@link Message}.
    +194     * @throws ModelInflateException If the model fails to load for any reason.
    +195     */
    +196    @Override
    +197    public @Nonnull Model inflate(@Nonnull EncodedModel data) throws ModelInflateException, IOException {
    +198      if (logging.isDebugEnabled())
    +199        logging.debug(String.format(
    +200          "Inflating record of type '%s' with format %s.",
    +201          data.getType(),
    +202          wireMode.name()));
    +203
    +204      if (wireMode == EncodingMode.BINARY) {
    +205        //noinspection unchecked
    +206        return (Model)instance.newBuilderForType().mergeFrom(data.getRawBytes().toByteArray()).build();
    +207      } else {
    +208        Message.Builder builder = instance.newBuilderForType();
    +209        Objects.requireNonNull(jsonParser).merge(
    +210          data.getRawBytes().toStringUtf8(),
    +211          builder);
    +212
    +213        //noinspection unchecked
    +214        return (Model)builder.build();  // need to install proto JSON
    +215      }
    +216    }
    +217  }
    +218
    +219  // -- API: Codec -- //
    +220  /** @inheritDoc */
    +221  @Override
    +222  public @Nonnull Model instance() {
    +223    return instance;
    +224  }
    +225
    +226  /**
    +227   * Acquire an instance of the {@link ModelSerializer} attached to this adapter. The instance is not guaranteed to be
    +228   * created fresh for this invocation.
    +229   *
    +230   * @return Serializer instance.
    +231   * @see #deserializer() For the inverse of this method.
    +232   * @see #deserialize(Object) To call into de-serialization directly.
    +233   */
    +234  @Override
    +235  public @Nonnull ModelSerializer<Model, EncodedModel> serializer() {
    +236    return this.serializer;
    +237  }
    +238
    +239  /**
    +240   * Acquire an instance of the {@link ModelDeserializer} attached to this adapter. The instance is not guaranteed to be
    +241   * created fresh for this invocation.
    +242   *
    +243   * @return Deserializer instance.
    +244   * @see #serializer() For the inverse of this method.
    +245   * @see #serialize(Message) To call into serialization directly.
    +246   */
    +247  @Override
    +248  public @Nonnull ModelDeserializer<EncodedModel, Model> deserializer() {
    +249    return this.deserializer;
    +250  }
    +251}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/SerializedModel.html b/docs/java/src-html/gust/backend/model/SerializedModel.html new file mode 100644 index 000000000..77b9757c1 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/SerializedModel.html @@ -0,0 +1,208 @@ + + + +Source code + + + +
    +
    +
    001package gust.backend.model;
    +002
    +003import com.google.firestore.v1.Value;
    +004import com.google.protobuf.Message;
    +005import javax.annotation.Nonnull;
    +006import java.util.*;
    +007
    +008
    +009/** Describes a model which has been serialized into a backing map of keys and properties. */
    +010public final class SerializedModel implements Map<String, Value> {
    +011  /** Raw serialized property data for the backing message. */
    +012  private final @Nonnull SortedMap<String, Value> data;
    +013
    +014  /** Original model (message) that we serialized into `data`. */
    +015  private final @Nonnull Optional<Message> message;
    +016
    +017  /**
    +018   * Create a serialized model object from scratch.
    +019   *
    +020   * @param data Serialized key-value pairs for the model.
    +021   * @param message Original model message instance.
    +022   */
    +023  SerializedModel(@Nonnull SortedMap<String, Value> data,
    +024                  @Nonnull Optional<Message> message) {
    +025    this.data = data;
    +026    this.message = message;
    +027  }
    +028
    +029  /**
    +030   * Create an empty serialized model, for use as a container.
    +031   *
    +032   * @return Empty serialized model.
    +033   */
    +034  public static @Nonnull SerializedModel factory() {
    +035    return factory(new TreeMap<>());
    +036  }
    +037
    +038  /**
    +039   * Create a serialized model, pre-filled with the provided backing data.
    +040   *
    +041   * @param data Data to pre-fill the serialized model with.
    +042   * @return Serialized model, pre-filled with the specified data.
    +043   */
    +044  public static @Nonnull SerializedModel factory(@Nonnull SortedMap<String, Value> data) {
    +045    return new SerializedModel(data, Optional.empty());
    +046  }
    +047
    +048  /**
    +049   * Create a serialized model, pre-filled with the provided backing data.
    +050   *
    +051   * @param data Data to pre-fill the serialized model with.
    +052   * @param proto Message instance to wrap, for which `data` is provided.
    +053   * @return Serialized model, pre-filled with the specified data.
    +054   */
    +055  public static @Nonnull SerializedModel wrap(@Nonnull SortedMap<String, Value> data,
    +056                                              @Nonnull Message proto) {
    +057    return new SerializedModel(data, Optional.of(proto));
    +058  }
    +059
    +060  // -- Getters -- //
    +061
    +062  /** @return Underlying data for this serialized model instance. */
    +063  @Nonnull public SortedMap<String, Value> getData() {
    +064    return data;
    +065  }
    +066
    +067  /** @return Message instance which spawned this serialized model. */
    +068  @Nonnull
    +069  public Optional<Message> getMessage() {
    +070    return message;
    +071  }
    +072
    +073  // -- Interface: Map -- //
    +074
    +075  /** @inheritDoc */
    +076  @Override public int size() {
    +077    return data.size();
    +078  }
    +079
    +080  /** @inheritDoc */
    +081  @Override public boolean isEmpty() {
    +082    return data.isEmpty();
    +083  }
    +084
    +085  /** @inheritDoc */
    +086  @Override public boolean containsKey(Object key) {
    +087    return data.containsKey(key);
    +088  }
    +089
    +090  /** @inheritDoc */
    +091  @Override public Value get(Object key) {
    +092    return data.get(key);
    +093  }
    +094
    +095  /** @inheritDoc */
    +096  @Override public Value put(String key, Value value) {
    +097    return data.put(key, value);
    +098  }
    +099
    +100  /** @inheritDoc */
    +101  @Override public Value remove(Object key) {
    +102    return data.remove(key);
    +103  }
    +104
    +105  /** @inheritDoc */
    +106  @Override public boolean containsValue(Object value) {
    +107    return data.containsValue(value);
    +108  }
    +109
    +110  /** @inheritDoc */
    +111  @Override public void putAll(Map<? extends String, ? extends Value> map) {
    +112    data.putAll(map);
    +113  }
    +114
    +115  /** @inheritDoc */
    +116  @Override public void clear() {
    +117    data.clear();
    +118  }
    +119
    +120  /** @inheritDoc */
    +121  @Override public Set<String> keySet() {
    +122    return data.keySet();
    +123  }
    +124
    +125  /** @inheritDoc */
    +126  @Override public Collection<Value> values() {
    +127    return data.values();
    +128  }
    +129
    +130  /** @inheritDoc */
    +131  @Override public Set<Entry<String, Value>> entrySet() {
    +132    return data.entrySet();
    +133  }
    +134}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/Transaction.html b/docs/java/src-html/gust/backend/model/Transaction.html new file mode 100644 index 000000000..b15a1ab13 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/Transaction.html @@ -0,0 +1,94 @@ + + + +Source code + + + +
    + +
    + + diff --git a/docs/java/src-html/gust/backend/model/UpdateOptions.html b/docs/java/src-html/gust/backend/model/UpdateOptions.html new file mode 100644 index 000000000..4c8ee721e --- /dev/null +++ b/docs/java/src-html/gust/backend/model/UpdateOptions.html @@ -0,0 +1,94 @@ + + + +Source code + + + +
    + +
    + + diff --git a/docs/java/src-html/gust/backend/model/WriteOptions.WriteDisposition.html b/docs/java/src-html/gust/backend/model/WriteOptions.WriteDisposition.html new file mode 100644 index 000000000..715a6ee70 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/WriteOptions.WriteDisposition.html @@ -0,0 +1,114 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import javax.annotation.Nonnull;
    +016import java.util.Optional;
    +017
    +018
    +019/** Describes options involved with operations to persist model entities. */
    +020public interface WriteOptions extends OperationOptions {
    +021  /** Default set of write operation options. */
    +022  WriteOptions DEFAULTS = new WriteOptions() {};
    +023
    +024  /** Enumerates write attitudes with regard to existing record collisions. */
    +025  enum WriteDisposition {
    +026    /** We don't care. Just write it. */
    +027    BLIND,
    +028
    +029    /** The record must exist for the write to proceed (an <i>update</i> operation). */
    +030    MUST_EXIST,
    +031
    +032    /** The record must <b>not</b> exist for the write to proceed (a <i>create</i> operation). */
    +033    MUST_NOT_EXIST
    +034  }
    +035
    +036  /** @return Specifies the write mode for an operation. Overridden by some methods (for instance, {@code create}). */
    +037  default @Nonnull Optional<WriteDisposition> writeMode() {
    +038    return Optional.empty();
    +039  }
    +040}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/WriteOptions.html b/docs/java/src-html/gust/backend/model/WriteOptions.html new file mode 100644 index 000000000..715a6ee70 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/WriteOptions.html @@ -0,0 +1,114 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import javax.annotation.Nonnull;
    +016import java.util.Optional;
    +017
    +018
    +019/** Describes options involved with operations to persist model entities. */
    +020public interface WriteOptions extends OperationOptions {
    +021  /** Default set of write operation options. */
    +022  WriteOptions DEFAULTS = new WriteOptions() {};
    +023
    +024  /** Enumerates write attitudes with regard to existing record collisions. */
    +025  enum WriteDisposition {
    +026    /** We don't care. Just write it. */
    +027    BLIND,
    +028
    +029    /** The record must exist for the write to proceed (an <i>update</i> operation). */
    +030    MUST_EXIST,
    +031
    +032    /** The record must <b>not</b> exist for the write to proceed (a <i>create</i> operation). */
    +033    MUST_NOT_EXIST
    +034  }
    +035
    +036  /** @return Specifies the write mode for an operation. Overridden by some methods (for instance, {@code create}). */
    +037  default @Nonnull Optional<WriteDisposition> writeMode() {
    +038    return Optional.empty();
    +039  }
    +040}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/model/WriteProxy.html b/docs/java/src-html/gust/backend/model/WriteProxy.html new file mode 100644 index 000000000..fde44fc36 --- /dev/null +++ b/docs/java/src-html/gust/backend/model/WriteProxy.html @@ -0,0 +1,156 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.model;
    +014
    +015import javax.annotation.Nonnull;
    +016import javax.annotation.Nullable;
    +017
    +018
    +019/** Provides an interface for virtualized object writes during transactions or hierarchical serialization. */
    +020public interface WriteProxy<Reference> {
    +021  /**
    +022   * Prepare a database reference, based on the provided `path`.
    +023   *
    +024   * <p>"Paths" in the model layer refer to hierarchically-stored entities. Typically, a path contains "collections" -
    +025   * i.e. buckets full of similarly shaped data, and "documents" within those collections - i.e., individual bags of
    +026   * schema-less key value associations.</p>
    +027   *
    +028   * @param path Path to prep a database reference for.
    +029   * @return Prepped database reference corresponding to the provided `path`.
    +030   */
    +031  default @Nonnull Reference ref(@Nonnull String path) {
    +032    return this.ref(path, null);
    +033  }
    +034
    +035  /**
    +036   * Prepare a database reference, based on the provided `path`, and prepend the provided transaction-wide `prefix`.
    +037   *
    +038   * <p>"Paths" in the model layer refer to hierarchically-stored entities. Typically, a path contains "collections" -
    +039   * i.e. buckets full of similarly shaped data, and "documents" within those collections - i.e., individual bags of
    +040   * schema-less key value associations.</p>
    +041   *
    +042   * @param path Path to prep a database reference for.
    +043   * @param prefix Transaction-wide prefix for this reference.
    +044   * @return Prepped database reference corresponding to the provided `path`.
    +045   */
    +046  @Nonnull Reference ref(@Nonnull String path, @Nullable String prefix);
    +047
    +048  /**
    +049   * Save a collapsed `message` in underlying storage, referenced by `reference`.
    +050   *
    +051   * This method specifies the interface that corresponds with the {@link ModelSerializer.WriteDisposition#BLIND} mode,
    +052   * wherein a write occurs without regard to underlying state.
    +053   *
    +054   * @param reference Reference / key for the serialized message.
    +055   * @param message Serialized message (i.e. serialized hierarchical data payload for the message).
    +056   */
    +057  void put(@Nonnull Reference reference, @Nonnull SerializedModel message);
    +058
    +059  /**
    +060   * Create a collapsed `message` in underlying storage, referenced by `reference`. If the record is determined to
    +061   * already exist in underlying storage, the operation fails.
    +062   *
    +063   * This method specifies the interface that corresponds with the {@link ModelSerializer.WriteDisposition#CREATE} mode,
    +064   * wherein a write occurs if-and-only-if the entity does not already exist.
    +065   *
    +066   * @param reference Reference / key for the serialized message.
    +067   * @param message Serialized message (i.e. serialized hierarchical data payload for the message).
    +068   */
    +069  void create(@Nonnull Reference reference, @Nonnull SerializedModel message);
    +070
    +071  /**
    +072   * Update a collapsed `message` in underlying storage, referenced by `reference`. If the record is determined to be
    +073   * non-existent in underlying storage, the operation fails.
    +074   *
    +075   * This method specifies the interface that corresponds with the {@link ModelSerializer.WriteDisposition#UPDATE} mode,
    +076   * wherein a write occurs if-and-only-if the underlying entity exists during transaction execution.
    +077   *
    +078   * @param reference Reference / key for the serialized message.
    +079   * @param message Serialized message (i.e. serialized hierarchical data payload for the message).
    +080   */
    +081  void update(@Nonnull Reference reference, @Nonnull SerializedModel message);
    +082}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/runtime/AssetManager.ManagedAsset.html b/docs/java/src-html/gust/backend/runtime/AssetManager.ManagedAsset.html new file mode 100644 index 000000000..a51f16dfe --- /dev/null +++ b/docs/java/src-html/gust/backend/runtime/AssetManager.ManagedAsset.html @@ -0,0 +1,670 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.runtime;
    +014
    +015import com.google.common.collect.ImmutableSortedSet;
    +016import com.google.common.collect.Multimap;
    +017import com.google.common.collect.MultimapBuilder;
    +018import com.google.protobuf.InvalidProtocolBufferException;
    +019import com.google.protobuf.Message;
    +020import com.google.protobuf.Timestamp;
    +021import gust.util.Hex;
    +022import gust.util.Pair;
    +023import io.micronaut.context.annotation.Context;
    +024import io.micronaut.context.annotation.Infrastructure;
    +025import org.slf4j.Logger;
    +026import tools.elide.assets.AssetBundle;
    +027import tools.elide.assets.AssetBundle.StyleBundle.StyleAsset;
    +028import tools.elide.assets.AssetBundle.ScriptBundle.ScriptAsset;
    +029import tools.elide.core.data.CompressedData;
    +030import tools.elide.core.data.CompressionMode;
    +031
    +032import javax.annotation.Nonnull;
    +033import javax.annotation.concurrent.Immutable;
    +034import javax.annotation.concurrent.ThreadSafe;
    +035import java.io.*;
    +036import java.net.URL;
    +037import java.nio.charset.StandardCharsets;
    +038import java.security.MessageDigest;
    +039import java.security.NoSuchAlgorithmException;
    +040import java.util.*;
    +041import java.util.concurrent.ConcurrentSkipListSet;
    +042import java.util.function.Function;
    +043import java.util.stream.Collectors;
    +044
    +045import static java.lang.String.format;
    +046
    +047
    +048/**
    +049 * Manager class, which mediates interactions with the binary asset bundle. When managed assets are active, the content
    +050 * and manifest are located in a binary proto file at the root of the JAR.
    +051 *
    +052 * <p>This object acts as a singleton, and is responsible for the actual mechanics of initially reading the asset bundle
    +053 * and interpreting its contents. Once we have established indexes and completed other prep work, the manager moves into
    +054 * a read-only mode, where its primary job shifts to satisfying dynamic asset requests - either for referential metadata
    +055 * which is used to embed an asset in the DOM, or content data, which is used to serve the asset itself.</p>
    +056 *
    +057 * <p>Multiple "variants" of an asset are stored in the bundle (if so configured). This includes one variant reflecting
    +058 * the regular, un-modified content for the asset, and an additional variant for each caching strategy supported by the
    +059 * framework ({@code GZIP} and {@code BROTLI} at the time of this writing).</p>
    +060 */
    +061@Context
    +062@ThreadSafe
    +063@Infrastructure
    +064@SuppressWarnings("UnstableApiUsage")
    +065public final class AssetManager {
    +066  /** Private logging pipe. */
    +067  private static final @Nonnull Logger logging = Logging.logger(AssetManager.class);
    +068
    +069  /** Path to the asset manifest resource. */
    +070  private static final @Nonnull String manifestPath = "/assets.pb";
    +071
    +072  /** Length of generated ETag values. */
    +073  private static final int ETAG_LENGTH = 8;
    +074
    +075  /** Algorithm to use for ETag value generation. */
    +076  private static final @Nonnull String ETAG_DIGEST_ALGORITHM = "SHA-256";
    +077
    +078  /** Shared/static asset bundle object, which is immutable. */
    +079  private static volatile AssetBundle loadedBundle;
    +080
    +081  /** Specifies a map of asset modules to metadata. */
    +082  private static final @Nonnull SortedMap<String, ModuleMetadata<? extends Message>> assetMap = new TreeMap<>();
    +083
    +084  /** Maps content blocks to their module names. */
    +085  private static final @Nonnull Multimap<String, String> modulesToTokens = MultimapBuilder
    +086    .hashKeys()
    +087    .treeSetValues()
    +088    .build();
    +089
    +090  /** Specifies a map of tokens to their content info. */
    +091  private static final @Nonnull SortedMap<String, ContentInfo> tokenMap = new TreeMap<>();
    +092
    +093  /** Holds on to info related to a raw asset file. */
    +094  @Immutable
    +095  static final class ContentInfo {
    +096    /** Unique token for this asset. */
    +097    final @Nonnull String token;
    +098
    +099    /** Unique token for this asset. */
    +100    final @Nonnull String module;
    +101
    +102    /** Original filename for this asset. */
    +103    final @Nonnull String filename;
    +104
    +105    /** Uncompressed data size. */
    +106    final @Nonnull Long size;
    +107
    +108    /** Etag, calculated from the token and filename. */
    +109    final @Nonnull String etag;
    +110
    +111    /** Smallest compression option. */
    +112    final @Nonnull CompressionMode optimalCompression;
    +113
    +114    /** Size of the optimally-compressed variant. */
    +115    final @Nonnull Long compressedSize;
    +116
    +117    /** Count of variants held by this content info block. */
    +118    final @Nonnull Integer variantCount;
    +119
    +120    /** Options that exist for pre-compressed variants of this content. */
    +121    final @Nonnull EnumSet<CompressionMode> compressionOptions;
    +122
    +123    /** Pointer to the content record backing this object. */
    +124    final @Nonnull AssetBundle.AssetContent content;
    +125
    +126    /** Raw constructor for content info metadata. */
    +127    private ContentInfo(@Nonnull String token,
    +128                        @Nonnull String module,
    +129                        @Nonnull String filename,
    +130                        @Nonnull Long size,
    +131                        @Nonnull String etag,
    +132                        @Nonnull CompressionMode optimalCompression,
    +133                        @Nonnull Long compressedSize,
    +134                        @Nonnull Integer variantCount,
    +135                        @Nonnull EnumSet<CompressionMode> compressionOptions,
    +136                        @Nonnull AssetBundle.AssetContent content) {
    +137      this.token = token;
    +138      this.module = module;
    +139      this.filename = filename;
    +140      this.size = size;
    +141      this.etag = etag;
    +142      this.optimalCompression = optimalCompression;
    +143      this.compressedSize = compressedSize;
    +144      this.variantCount = variantCount;
    +145      this.compressionOptions = compressionOptions;
    +146      this.content = content;
    +147    }
    +148
    +149    /**
    +150     * Inflate a {@link ContentInfo} record from an {@link AssetBundle.AssetContent} definition. This method variant
    +151     * additionally allows specification of a custom `ETag` digest algorithm.
    +152     *
    +153     * @param content Asset content protocol object.
    +154     * @param algorithm Algorithm to use for etags.
    +155     * @return Checked content info object.
    +156     */
    +157    static @Nonnull ContentInfo fromProto(@Nonnull AssetBundle.AssetContent content, @Nonnull String algorithm) {
    +158      try {
    +159        MessageDigest digester = MessageDigest.getInstance(algorithm);
    +160        digester.update(content.getModule().getBytes(StandardCharsets.UTF_8));
    +161        digester.update(content.getFilename().getBytes(StandardCharsets.UTF_8));
    +162        digester.update(content.getToken().getBytes(StandardCharsets.UTF_8));
    +163        digester.update(String.valueOf(content.getVariantCount()).getBytes(StandardCharsets.UTF_8));
    +164        byte[] etagDigest = digester.digest();
    +165
    +166        // find uncompressed size
    +167        Long uncompressedAssetSize = content.getVariantList().stream()
    +168          .filter((variant) -> variant.getCompression().equals(CompressionMode.IDENTITY))
    +169          .findFirst()
    +170          .orElseGet(CompressedData::getDefaultInstance)
    +171          .getSize();
    +172
    +173        // resolve optimal compression
    +174        Pair<Long, CompressionMode> optimalCompression = content.getVariantList().stream()
    +175          .map((data) -> Pair.of(data.getSize(), data.getCompression()))
    +176          .min(Comparator.comparing(Pair::getKey))
    +177          .orElse(Pair.of(0L, CompressionMode.IDENTITY));
    +178
    +179        // resolve set of supported compression options for this asset
    +180        EnumSet<CompressionMode> compressionOptions = EnumSet.copyOf(content.getVariantList().parallelStream()
    +181          .map(CompressedData::getCompression)
    +182          .collect(Collectors.toList()));
    +183
    +184        return new ContentInfo(
    +185          content.getToken(),
    +186          content.getModule(),
    +187          content.getFilename(),
    +188          uncompressedAssetSize,
    +189          Hex.bytesToHex(etagDigest, ETAG_LENGTH),
    +190          optimalCompression.getValue(),
    +191          optimalCompression.getKey(),
    +192          content.getVariantCount(),
    +193          compressionOptions,
    +194          content);
    +195
    +196      } catch (NoSuchAlgorithmException exc) {
    +197        throw new RuntimeException(exc);
    +198      }
    +199    }
    +200
    +201    /**
    +202     * Inflate a {@link ContentInfo} record from an {@link AssetBundle.AssetContent} definition.
    +203     *
    +204     * @param content Asset content protocol object.
    +205     * @return Checked content info object.
    +206     */
    +207    static @Nonnull ContentInfo fromProto(AssetBundle.AssetContent content) {
    +208      return fromProto(content, ETAG_DIGEST_ALGORITHM);
    +209    }
    +210  }
    +211
    +212  /** Enumerates types of asset modules. */
    +213  public enum ModuleType {
    +214    /** The bundle contains JavaScript code. */
    +215    JS,
    +216
    +217    /** The bundle contains style declarations. */
    +218    CSS
    +219  }
    +220
    +221  /** Holds on to info related to an asset module's metadata. */
    +222  @Immutable
    +223  static final class ModuleMetadata<M extends Message> {
    +224    /** Name of this asset module. */
    +225    final @Nonnull String name;
    +226
    +227    /** Type of code/logic contained by this asset. */
    +228    final @Nonnull ModuleType type;
    +229
    +230    /** Raw asset records for this module. */
    +231    final @Nonnull List<M> assets;
    +232
    +233    /** Raw constructor for asset module metadata. */
    +234    private ModuleMetadata(@Nonnull ModuleType type,
    +235                           @Nonnull String name,
    +236                           @Nonnull List<M> assets) {
    +237      this.name = name;
    +238      this.type = type;
    +239      this.assets = assets;
    +240    }
    +241
    +242    /**
    +243     * Inflate a {@link ModuleMetadata} record from a {@link AssetBundle.StyleBundle} definition.
    +244     *
    +245     * @param content Asset content protocol object.
    +246     * @return Checked module info object.
    +247     */
    +248    static @Nonnull ModuleMetadata<StyleAsset> fromStyleProto(@Nonnull AssetBundle.StyleBundle content) {
    +249      return new ModuleMetadata<>(
    +250        ModuleType.CSS,
    +251        content.getModule(),
    +252        content.getAssetList());
    +253    }
    +254
    +255    /**
    +256     * Inflate a {@link ModuleMetadata} record from a {@link AssetBundle.ScriptBundle} definition.
    +257     *
    +258     * @param content Asset content protocol object.
    +259     * @return Checked module info object.
    +260     */
    +261    static @Nonnull ModuleMetadata<ScriptAsset> fromScriptProto(@Nonnull AssetBundle.ScriptBundle content) {
    +262      return new ModuleMetadata<>(
    +263        ModuleType.JS,
    +264        content.getModule(),
    +265        content.getAssetList());
    +266    }
    +267  }
    +268
    +269  /** Public API surface for interacting with raw asset content. */
    +270  @Immutable
    +271  @SuppressWarnings("unused")
    +272  public static final class ManagedAssetContent implements Comparable<ManagedAssetContent> {
    +273    /** Attached/encapsulated asset content and info. */
    +274    private final @Nonnull ContentInfo content;
    +275
    +276    /** Create a {@link ManagedAssetContent} object from scratch. */
    +277    ManagedAssetContent(@Nonnull ContentInfo content) {
    +278      this.content = content;
    +279    }
    +280
    +281    @Override
    +282    public boolean equals(Object other) {
    +283      if (this == other) return true;
    +284      if (other == null || getClass() != other.getClass()) return false;
    +285      ManagedAssetContent that = (ManagedAssetContent) other;
    +286      return com.google.common.base.Objects
    +287        .equal(content.token, that.content.token);
    +288    }
    +289
    +290    @Override
    +291    public int hashCode() {
    +292      return com.google.common.base.Objects.hashCode(content.token);
    +293    }
    +294
    +295    @Override
    +296    public int compareTo(@Nonnull ManagedAssetContent other) {
    +297      return this.content.token.compareTo(other.content.token);
    +298    }
    +299
    +300    /** @return Opaque token identifying this asset content. */
    +301    public @Nonnull String getToken() {
    +302      return content.token;
    +303    }
    +304
    +305    /** @return Module name for this content chunk. */
    +306    public @Nonnull String getModule() {
    +307      return content.module;
    +308    }
    +309
    +310    /** @return Pre-calculated ETag value for this asset. */
    +311    public @Nonnull String getETag() {
    +312      return content.etag;
    +313    }
    +314
    +315    /** @return Original filename for the asset. */
    +316    public @Nonnull String getFilename() {
    +317      return content.filename;
    +318    }
    +319
    +320    /** @return Last-modified-timestamp for this asset. */
    +321    public @Nonnull Timestamp getLastModified() {
    +322      return loadedBundle.getGenerated();
    +323    }
    +324
    +325    /** @return Un-compressed size of the asset. */
    +326    public @Nonnull Long getSize() {
    +327      return content.size;
    +328    }
    +329
    +330    /** @return Optimal compression mode. */
    +331    public @Nonnull CompressionMode getOptimalCompression() {
    +332      return content.optimalCompression;
    +333    }
    +334
    +335    /** @return Compressed size of the asset (optimal). */
    +336    @SuppressWarnings("WeakerAccess")
    +337    public @Nonnull Long getCompressedSize() {
    +338      return content.compressedSize;
    +339    }
    +340
    +341    /** @return Count of variants that exist for this asset. */
    +342    public @Nonnull Integer getVariantCount() {
    +343      return content.variantCount;
    +344    }
    +345
    +346    /** @return Set of supported compression modes for this asset. */
    +347    public @Nonnull EnumSet<CompressionMode> getCompressionOptions() {
    +348      return content.compressionOptions;
    +349    }
    +350
    +351    /** Retrieve the content backing this info record. */
    +352    public @Nonnull AssetBundle.AssetContent getContent() {
    +353      return content.content;
    +354    }
    +355  }
    +356
    +357  /** Public API surface for interacting with raw asset metadata. */
    +358  @Immutable
    +359  public static final class ManagedAsset<M extends Message> implements Comparable<ManagedAsset> {
    +360    /** Resolved module metadata for this asset. */
    +361    private final @Nonnull ModuleMetadata<M> module;
    +362
    +363    /** Logic references that constitute this managed asset, including dependencies, in reverse topological order. */
    +364    private final @Nonnull Collection<ManagedAssetContent> content;
    +365
    +366    /** Construct a new managed asset from scratch. */
    +367    ManagedAsset(@Nonnull ModuleMetadata<M> module,
    +368                 @Nonnull Collection<ManagedAssetContent> content) {
    +369      this.module = module;
    +370      this.content = content;
    +371    }
    +372
    +373    @Override
    +374    public boolean equals(Object o) {
    +375      if (this == o) return true;
    +376      if (o == null || getClass() != o.getClass()) return false;
    +377      ManagedAsset that = (ManagedAsset) o;
    +378      return com.google.common.base.Objects
    +379        .equal(module.name, that.module.name);
    +380    }
    +381
    +382    @Override
    +383    public int hashCode() {
    +384      return com.google.common.base.Objects
    +385        .hashCode(module.name);
    +386    }
    +387
    +388    @Override
    +389    public int compareTo(@Nonnull ManagedAsset other) {
    +390      return this.module.name.compareTo(other.module.name);
    +391    }
    +392
    +393    /** @return This module's assigned name. */
    +394    public @Nonnull String getName() {
    +395      return module.name;
    +396    }
    +397
    +398    /** @return This module's assigned type. */
    +399    public @Nonnull ModuleType getType() {
    +400      return module.type;
    +401    }
    +402
    +403    /** @return Collection of typed asset records constituting this bundle. */
    +404    public @Nonnull Collection<M> getAssets() {
    +405      return module.assets;
    +406    }
    +407
    +408    /** @return Content configurations associated with this asset bundle. */
    +409    public @Nonnull Collection<ManagedAssetContent> getContent() {
    +410      return this.content;
    +411    }
    +412  }
    +413
    +414  /** index the newly-installed asset bundle. */
    +415  private static void index() {
    +416    if (logging.isDebugEnabled())
    +417      logging.debug("Indexing raw assets by token...");
    +418    tokenMap.putAll(loadedBundle.getAssetList().stream()
    +419      .map(ContentInfo::fromProto)
    +420      .map((info) -> Pair.of(info.token, info))
    +421      .peek((pair) -> {
    +422        if (logging.isTraceEnabled())
    +423          logging.trace(format("- Indexing asset content at token '%s' from original file '%s'.",
    +424            pair.getKey(),
    +425            pair.getValue().filename));
    +426      })
    +427      .collect(Collectors.toMap(Pair::getKey, Pair::getValue)));
    +428
    +429    if (logging.isDebugEnabled())
    +430      logging.debug("Indexing CSS assets by module name...");
    +431    assetMap.putAll(loadedBundle.getStylesMap().entrySet().stream()
    +432      .map((entry) -> Pair.of(entry.getKey(), ModuleMetadata.fromStyleProto(entry.getValue())))
    +433      .peek((pair) -> {
    +434        // map each asset to its constituent module
    +435        pair.getValue().assets.forEach((asset) -> modulesToTokens.put(pair.getKey(), asset.getToken()));
    +436
    +437        if (logging.isTraceEnabled())
    +438          logging.trace(format("- Indexing style module '%s' of type %s.",
    +439            pair.getKey(),
    +440            pair.getValue().type));
    +441      })
    +442      .collect(Collectors.toMap(Pair::getKey, Pair::getValue)));
    +443
    +444    if (logging.isDebugEnabled())
    +445      logging.debug("Indexing JS assets by module name...");
    +446    assetMap.putAll(loadedBundle.getScriptsMap().entrySet().stream()
    +447      .map((entry) -> Pair.of(entry.getKey(), ModuleMetadata.fromScriptProto(entry.getValue())))
    +448      .peek((pair) -> {
    +449        // map each asset to its constituent module
    +450        pair.getValue().assets.forEach((asset) -> modulesToTokens.put(pair.getKey(), asset.getToken()));
    +451
    +452        if (logging.isTraceEnabled())
    +453          logging.trace(format("- Indexing script module '%s' of type %s.",
    +454            pair.getKey(),
    +455            pair.getValue().type));
    +456      })
    +457      .collect(Collectors.toMap(Pair::getKey, Pair::getValue)));
    +458  }
    +459
    +460  /**
    +461   * Attempt to force-load the asset manifest, in a static context, preparing our indexed read-only data regarding the
    +462   * data it contains. If we can't load the file, surface an exception so the invoking code can decide what to do.
    +463   *
    +464   * @throws IOException If some otherwise unmentioned I/O error occurs.
    +465   * @throws FileNotFoundException If the asset manifest could not be found.
    +466   * @throws InvalidProtocolBufferException If the enclosed Protocol Buffer data isn't recognizable.
    +467   */
    +468  public static void load() throws IOException, InvalidProtocolBufferException {
    +469    if (loadedBundle != null) return;
    +470    if (logging.isDebugEnabled())
    +471      logging.debug(format("Attempting to load manifest as resource (at path '%s')", manifestPath));
    +472
    +473    URL manifestURL = AssetManager.class.getResource(manifestPath);
    +474    if (manifestURL == null) {
    +475      logging.debug("No resource manifest found. Proceeding with empty manifest...");
    +476      loadedBundle = AssetBundle.getDefaultInstance();
    +477    } else {
    +478      if (logging.isDebugEnabled()) logging.debug("Loading resource manifest...");
    +479      try (InputStream assetBundle = AssetManager.class.getResourceAsStream(manifestPath)) {
    +480        try (BufferedInputStream buffer = new BufferedInputStream(assetBundle)) {
    +481          AssetBundle bundle = AssetBundle.parseDelimitedFrom(buffer);
    +482          Function<Integer, Boolean> plural = ((number) -> (number > 1 || number == 0));
    +483
    +484          if (bundle.isInitialized()) {
    +485            loadedBundle = bundle;
    +486            index();
    +487            logging.info(format("Asset bundle loaded with %s %s (%s %s, %s %s%s).",
    +488              bundle.getAssetCount(),
    +489              plural.apply(bundle.getAssetCount()) ? "assets" : "asset",
    +490              bundle.getScriptsCount(),
    +491              plural.apply(bundle.getScriptsCount()) ? "scripts" : "script",
    +492              bundle.getStylesCount(),
    +493              plural.apply(bundle.getStylesCount()) ? "stylesheets" : "stylesheet",
    +494              bundle.getRewrite() ? ", with rewriting ACTIVE" : ", with no style rewriting"));
    +495          }
    +496        }
    +497      }
    +498    }
    +499  }
    +500
    +501  /**
    +502   * Acquire a new instance of the asset manager. The instance provided by this method is not guaranteed to be fresh for
    +503   * every invocation (it may be a shared object), but all operations on the asset manager are threadsafe nonetheless.
    +504   *
    +505   * @return Asset manager instance.
    +506   */
    +507  public static AssetManager acquire() throws IOException {
    +508    AssetManager.load();
    +509    return new AssetManager();
    +510  }
    +511
    +512  /** Package-private constructor. Acquire an instance through {@link #acquire()}. */
    +513  @SuppressWarnings("WeakerAccess")
    +514  AssetManager() { /* Disallow instantiation except through DI. */ }
    +515
    +516  // -- Public API -- //
    +517
    +518  /**
    +519   * Resolve raw asset content by its opaque token. This will hand back an object containing representations of the
    +520   * asset for each enabled compression mode.
    +521   *
    +522   * <p>The object also knows how to resolve the most-optimal representation, based on the accepted compression modes
    +523   * indicated by the invoking client.</p>
    +524   *
    +525   * @param token Token uniquely identifying this asset (generated from the module name and content fingerprint).
    +526   * @return Optional, either {@link Optional#empty()} if the asset could not be found, or wrapping the result.
    +527   */
    +528  public @Nonnull Optional<ManagedAssetContent> assetDataByToken(@Nonnull String token) {
    +529    if (logging.isTraceEnabled())
    +530      logging.trace(format("Resolving asset by token '%s'.", token));
    +531    if (!tokenMap.containsKey(token)) {
    +532      logging.warn(format("Asset not found at token '%s'.", token));
    +533      return Optional.empty();
    +534    }
    +535    if (logging.isDebugEnabled())
    +536      logging.debug(format("Resolved valid asset via token '%s'.", token));
    +537    return Optional.of(new ManagedAssetContent(Objects.requireNonNull(tokenMap.get(token))));
    +538  }
    +539
    +540  /**
    +541   * Resolve asset metadata by its module name. This will hand back an object specifying the type/name of the module,
    +542   * and links to each of the content blocks that constitute it.
    +543   *
    +544   * <p>This code path is generally used for resolving metadata for an asset so it can be <i>referenced</i>. The serving
    +545   * for URL generated for the asset refers to a specific content block with an opaque token, rather than the module
    +546   * name, which refers to a bundle of assets or content.</p>
    +547   *
    +548   * @param module Module name for which we should resolve asset metadata.
    +549   * @return Optional, either {@link Optional#empty()} if the asset group could not be found, or wrapping the result.
    +550   */
    +551  @SuppressWarnings("unused")
    +552  public @Nonnull <M extends Message> Optional<ManagedAsset<M>> assetMetadataByModule(@Nonnull String module) {
    +553    if (logging.isTraceEnabled())
    +554      logging.trace(format("Resolving asset metadata at module '%s'.", module));
    +555    if (!assetMap.containsKey(module)) {
    +556      if (assetMap.isEmpty()) {
    +557        logging.warn(format("Asset metadata not found in (EMPTY) module map, at module name '%s'.", module));
    +558      } else {
    +559        logging.warn(format("Asset metadata not found at module name '%s'.", module));
    +560      }
    +561      return Optional.empty();
    +562    }
    +563
    +564    // resolve content for the module
    +565    ImmutableSortedSet.Builder<ManagedAssetContent> assetsBuilder = ImmutableSortedSet.naturalOrder();
    +566    Collection<String> contentTokens = modulesToTokens.get(module);
    +567
    +568    Collection<ManagedAssetContent> contents = Collections.emptySet();
    +569    if (!contentTokens.isEmpty()) {
    +570      // resolve content for each token
    +571      assetsBuilder.addAll((Iterable<ManagedAssetContent>)contentTokens.parallelStream()
    +572        .map((token) -> Pair.of(token, this.assetDataByToken(token)))
    +573        .peek((pair) -> {
    +574          var content = pair.getValue();
    +575          if (content.isPresent() && logging.isDebugEnabled()) {
    +576            logging.debug(format("Resolved content block at token '%s' for module '%s.'",
    +577              pair.getKey(),
    +578              module));
    +579          }
    +580        })
    +581        .filter((pair) -> pair.getValue().isPresent())
    +582        .map(Pair::getValue)
    +583        .map(Optional::get)
    +584        .collect(Collectors.toCollection(ConcurrentSkipListSet::new)));
    +585
    +586      contents = assetsBuilder.build();
    +587    }
    +588
    +589    if (logging.isDebugEnabled())
    +590      logging.debug(format("Resolved valid asset metadata for module '%s'.", module));
    +591    //noinspection unchecked
    +592    return Optional.of(new ManagedAsset<>(
    +593      Objects.requireNonNull((ModuleMetadata<M>)assetMap.get(module)),
    +594      contents));
    +595  }
    +596}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/runtime/AssetManager.ManagedAssetContent.html b/docs/java/src-html/gust/backend/runtime/AssetManager.ManagedAssetContent.html new file mode 100644 index 000000000..a51f16dfe --- /dev/null +++ b/docs/java/src-html/gust/backend/runtime/AssetManager.ManagedAssetContent.html @@ -0,0 +1,670 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.runtime;
    +014
    +015import com.google.common.collect.ImmutableSortedSet;
    +016import com.google.common.collect.Multimap;
    +017import com.google.common.collect.MultimapBuilder;
    +018import com.google.protobuf.InvalidProtocolBufferException;
    +019import com.google.protobuf.Message;
    +020import com.google.protobuf.Timestamp;
    +021import gust.util.Hex;
    +022import gust.util.Pair;
    +023import io.micronaut.context.annotation.Context;
    +024import io.micronaut.context.annotation.Infrastructure;
    +025import org.slf4j.Logger;
    +026import tools.elide.assets.AssetBundle;
    +027import tools.elide.assets.AssetBundle.StyleBundle.StyleAsset;
    +028import tools.elide.assets.AssetBundle.ScriptBundle.ScriptAsset;
    +029import tools.elide.core.data.CompressedData;
    +030import tools.elide.core.data.CompressionMode;
    +031
    +032import javax.annotation.Nonnull;
    +033import javax.annotation.concurrent.Immutable;
    +034import javax.annotation.concurrent.ThreadSafe;
    +035import java.io.*;
    +036import java.net.URL;
    +037import java.nio.charset.StandardCharsets;
    +038import java.security.MessageDigest;
    +039import java.security.NoSuchAlgorithmException;
    +040import java.util.*;
    +041import java.util.concurrent.ConcurrentSkipListSet;
    +042import java.util.function.Function;
    +043import java.util.stream.Collectors;
    +044
    +045import static java.lang.String.format;
    +046
    +047
    +048/**
    +049 * Manager class, which mediates interactions with the binary asset bundle. When managed assets are active, the content
    +050 * and manifest are located in a binary proto file at the root of the JAR.
    +051 *
    +052 * <p>This object acts as a singleton, and is responsible for the actual mechanics of initially reading the asset bundle
    +053 * and interpreting its contents. Once we have established indexes and completed other prep work, the manager moves into
    +054 * a read-only mode, where its primary job shifts to satisfying dynamic asset requests - either for referential metadata
    +055 * which is used to embed an asset in the DOM, or content data, which is used to serve the asset itself.</p>
    +056 *
    +057 * <p>Multiple "variants" of an asset are stored in the bundle (if so configured). This includes one variant reflecting
    +058 * the regular, un-modified content for the asset, and an additional variant for each caching strategy supported by the
    +059 * framework ({@code GZIP} and {@code BROTLI} at the time of this writing).</p>
    +060 */
    +061@Context
    +062@ThreadSafe
    +063@Infrastructure
    +064@SuppressWarnings("UnstableApiUsage")
    +065public final class AssetManager {
    +066  /** Private logging pipe. */
    +067  private static final @Nonnull Logger logging = Logging.logger(AssetManager.class);
    +068
    +069  /** Path to the asset manifest resource. */
    +070  private static final @Nonnull String manifestPath = "/assets.pb";
    +071
    +072  /** Length of generated ETag values. */
    +073  private static final int ETAG_LENGTH = 8;
    +074
    +075  /** Algorithm to use for ETag value generation. */
    +076  private static final @Nonnull String ETAG_DIGEST_ALGORITHM = "SHA-256";
    +077
    +078  /** Shared/static asset bundle object, which is immutable. */
    +079  private static volatile AssetBundle loadedBundle;
    +080
    +081  /** Specifies a map of asset modules to metadata. */
    +082  private static final @Nonnull SortedMap<String, ModuleMetadata<? extends Message>> assetMap = new TreeMap<>();
    +083
    +084  /** Maps content blocks to their module names. */
    +085  private static final @Nonnull Multimap<String, String> modulesToTokens = MultimapBuilder
    +086    .hashKeys()
    +087    .treeSetValues()
    +088    .build();
    +089
    +090  /** Specifies a map of tokens to their content info. */
    +091  private static final @Nonnull SortedMap<String, ContentInfo> tokenMap = new TreeMap<>();
    +092
    +093  /** Holds on to info related to a raw asset file. */
    +094  @Immutable
    +095  static final class ContentInfo {
    +096    /** Unique token for this asset. */
    +097    final @Nonnull String token;
    +098
    +099    /** Unique token for this asset. */
    +100    final @Nonnull String module;
    +101
    +102    /** Original filename for this asset. */
    +103    final @Nonnull String filename;
    +104
    +105    /** Uncompressed data size. */
    +106    final @Nonnull Long size;
    +107
    +108    /** Etag, calculated from the token and filename. */
    +109    final @Nonnull String etag;
    +110
    +111    /** Smallest compression option. */
    +112    final @Nonnull CompressionMode optimalCompression;
    +113
    +114    /** Size of the optimally-compressed variant. */
    +115    final @Nonnull Long compressedSize;
    +116
    +117    /** Count of variants held by this content info block. */
    +118    final @Nonnull Integer variantCount;
    +119
    +120    /** Options that exist for pre-compressed variants of this content. */
    +121    final @Nonnull EnumSet<CompressionMode> compressionOptions;
    +122
    +123    /** Pointer to the content record backing this object. */
    +124    final @Nonnull AssetBundle.AssetContent content;
    +125
    +126    /** Raw constructor for content info metadata. */
    +127    private ContentInfo(@Nonnull String token,
    +128                        @Nonnull String module,
    +129                        @Nonnull String filename,
    +130                        @Nonnull Long size,
    +131                        @Nonnull String etag,
    +132                        @Nonnull CompressionMode optimalCompression,
    +133                        @Nonnull Long compressedSize,
    +134                        @Nonnull Integer variantCount,
    +135                        @Nonnull EnumSet<CompressionMode> compressionOptions,
    +136                        @Nonnull AssetBundle.AssetContent content) {
    +137      this.token = token;
    +138      this.module = module;
    +139      this.filename = filename;
    +140      this.size = size;
    +141      this.etag = etag;
    +142      this.optimalCompression = optimalCompression;
    +143      this.compressedSize = compressedSize;
    +144      this.variantCount = variantCount;
    +145      this.compressionOptions = compressionOptions;
    +146      this.content = content;
    +147    }
    +148
    +149    /**
    +150     * Inflate a {@link ContentInfo} record from an {@link AssetBundle.AssetContent} definition. This method variant
    +151     * additionally allows specification of a custom `ETag` digest algorithm.
    +152     *
    +153     * @param content Asset content protocol object.
    +154     * @param algorithm Algorithm to use for etags.
    +155     * @return Checked content info object.
    +156     */
    +157    static @Nonnull ContentInfo fromProto(@Nonnull AssetBundle.AssetContent content, @Nonnull String algorithm) {
    +158      try {
    +159        MessageDigest digester = MessageDigest.getInstance(algorithm);
    +160        digester.update(content.getModule().getBytes(StandardCharsets.UTF_8));
    +161        digester.update(content.getFilename().getBytes(StandardCharsets.UTF_8));
    +162        digester.update(content.getToken().getBytes(StandardCharsets.UTF_8));
    +163        digester.update(String.valueOf(content.getVariantCount()).getBytes(StandardCharsets.UTF_8));
    +164        byte[] etagDigest = digester.digest();
    +165
    +166        // find uncompressed size
    +167        Long uncompressedAssetSize = content.getVariantList().stream()
    +168          .filter((variant) -> variant.getCompression().equals(CompressionMode.IDENTITY))
    +169          .findFirst()
    +170          .orElseGet(CompressedData::getDefaultInstance)
    +171          .getSize();
    +172
    +173        // resolve optimal compression
    +174        Pair<Long, CompressionMode> optimalCompression = content.getVariantList().stream()
    +175          .map((data) -> Pair.of(data.getSize(), data.getCompression()))
    +176          .min(Comparator.comparing(Pair::getKey))
    +177          .orElse(Pair.of(0L, CompressionMode.IDENTITY));
    +178
    +179        // resolve set of supported compression options for this asset
    +180        EnumSet<CompressionMode> compressionOptions = EnumSet.copyOf(content.getVariantList().parallelStream()
    +181          .map(CompressedData::getCompression)
    +182          .collect(Collectors.toList()));
    +183
    +184        return new ContentInfo(
    +185          content.getToken(),
    +186          content.getModule(),
    +187          content.getFilename(),
    +188          uncompressedAssetSize,
    +189          Hex.bytesToHex(etagDigest, ETAG_LENGTH),
    +190          optimalCompression.getValue(),
    +191          optimalCompression.getKey(),
    +192          content.getVariantCount(),
    +193          compressionOptions,
    +194          content);
    +195
    +196      } catch (NoSuchAlgorithmException exc) {
    +197        throw new RuntimeException(exc);
    +198      }
    +199    }
    +200
    +201    /**
    +202     * Inflate a {@link ContentInfo} record from an {@link AssetBundle.AssetContent} definition.
    +203     *
    +204     * @param content Asset content protocol object.
    +205     * @return Checked content info object.
    +206     */
    +207    static @Nonnull ContentInfo fromProto(AssetBundle.AssetContent content) {
    +208      return fromProto(content, ETAG_DIGEST_ALGORITHM);
    +209    }
    +210  }
    +211
    +212  /** Enumerates types of asset modules. */
    +213  public enum ModuleType {
    +214    /** The bundle contains JavaScript code. */
    +215    JS,
    +216
    +217    /** The bundle contains style declarations. */
    +218    CSS
    +219  }
    +220
    +221  /** Holds on to info related to an asset module's metadata. */
    +222  @Immutable
    +223  static final class ModuleMetadata<M extends Message> {
    +224    /** Name of this asset module. */
    +225    final @Nonnull String name;
    +226
    +227    /** Type of code/logic contained by this asset. */
    +228    final @Nonnull ModuleType type;
    +229
    +230    /** Raw asset records for this module. */
    +231    final @Nonnull List<M> assets;
    +232
    +233    /** Raw constructor for asset module metadata. */
    +234    private ModuleMetadata(@Nonnull ModuleType type,
    +235                           @Nonnull String name,
    +236                           @Nonnull List<M> assets) {
    +237      this.name = name;
    +238      this.type = type;
    +239      this.assets = assets;
    +240    }
    +241
    +242    /**
    +243     * Inflate a {@link ModuleMetadata} record from a {@link AssetBundle.StyleBundle} definition.
    +244     *
    +245     * @param content Asset content protocol object.
    +246     * @return Checked module info object.
    +247     */
    +248    static @Nonnull ModuleMetadata<StyleAsset> fromStyleProto(@Nonnull AssetBundle.StyleBundle content) {
    +249      return new ModuleMetadata<>(
    +250        ModuleType.CSS,
    +251        content.getModule(),
    +252        content.getAssetList());
    +253    }
    +254
    +255    /**
    +256     * Inflate a {@link ModuleMetadata} record from a {@link AssetBundle.ScriptBundle} definition.
    +257     *
    +258     * @param content Asset content protocol object.
    +259     * @return Checked module info object.
    +260     */
    +261    static @Nonnull ModuleMetadata<ScriptAsset> fromScriptProto(@Nonnull AssetBundle.ScriptBundle content) {
    +262      return new ModuleMetadata<>(
    +263        ModuleType.JS,
    +264        content.getModule(),
    +265        content.getAssetList());
    +266    }
    +267  }
    +268
    +269  /** Public API surface for interacting with raw asset content. */
    +270  @Immutable
    +271  @SuppressWarnings("unused")
    +272  public static final class ManagedAssetContent implements Comparable<ManagedAssetContent> {
    +273    /** Attached/encapsulated asset content and info. */
    +274    private final @Nonnull ContentInfo content;
    +275
    +276    /** Create a {@link ManagedAssetContent} object from scratch. */
    +277    ManagedAssetContent(@Nonnull ContentInfo content) {
    +278      this.content = content;
    +279    }
    +280
    +281    @Override
    +282    public boolean equals(Object other) {
    +283      if (this == other) return true;
    +284      if (other == null || getClass() != other.getClass()) return false;
    +285      ManagedAssetContent that = (ManagedAssetContent) other;
    +286      return com.google.common.base.Objects
    +287        .equal(content.token, that.content.token);
    +288    }
    +289
    +290    @Override
    +291    public int hashCode() {
    +292      return com.google.common.base.Objects.hashCode(content.token);
    +293    }
    +294
    +295    @Override
    +296    public int compareTo(@Nonnull ManagedAssetContent other) {
    +297      return this.content.token.compareTo(other.content.token);
    +298    }
    +299
    +300    /** @return Opaque token identifying this asset content. */
    +301    public @Nonnull String getToken() {
    +302      return content.token;
    +303    }
    +304
    +305    /** @return Module name for this content chunk. */
    +306    public @Nonnull String getModule() {
    +307      return content.module;
    +308    }
    +309
    +310    /** @return Pre-calculated ETag value for this asset. */
    +311    public @Nonnull String getETag() {
    +312      return content.etag;
    +313    }
    +314
    +315    /** @return Original filename for the asset. */
    +316    public @Nonnull String getFilename() {
    +317      return content.filename;
    +318    }
    +319
    +320    /** @return Last-modified-timestamp for this asset. */
    +321    public @Nonnull Timestamp getLastModified() {
    +322      return loadedBundle.getGenerated();
    +323    }
    +324
    +325    /** @return Un-compressed size of the asset. */
    +326    public @Nonnull Long getSize() {
    +327      return content.size;
    +328    }
    +329
    +330    /** @return Optimal compression mode. */
    +331    public @Nonnull CompressionMode getOptimalCompression() {
    +332      return content.optimalCompression;
    +333    }
    +334
    +335    /** @return Compressed size of the asset (optimal). */
    +336    @SuppressWarnings("WeakerAccess")
    +337    public @Nonnull Long getCompressedSize() {
    +338      return content.compressedSize;
    +339    }
    +340
    +341    /** @return Count of variants that exist for this asset. */
    +342    public @Nonnull Integer getVariantCount() {
    +343      return content.variantCount;
    +344    }
    +345
    +346    /** @return Set of supported compression modes for this asset. */
    +347    public @Nonnull EnumSet<CompressionMode> getCompressionOptions() {
    +348      return content.compressionOptions;
    +349    }
    +350
    +351    /** Retrieve the content backing this info record. */
    +352    public @Nonnull AssetBundle.AssetContent getContent() {
    +353      return content.content;
    +354    }
    +355  }
    +356
    +357  /** Public API surface for interacting with raw asset metadata. */
    +358  @Immutable
    +359  public static final class ManagedAsset<M extends Message> implements Comparable<ManagedAsset> {
    +360    /** Resolved module metadata for this asset. */
    +361    private final @Nonnull ModuleMetadata<M> module;
    +362
    +363    /** Logic references that constitute this managed asset, including dependencies, in reverse topological order. */
    +364    private final @Nonnull Collection<ManagedAssetContent> content;
    +365
    +366    /** Construct a new managed asset from scratch. */
    +367    ManagedAsset(@Nonnull ModuleMetadata<M> module,
    +368                 @Nonnull Collection<ManagedAssetContent> content) {
    +369      this.module = module;
    +370      this.content = content;
    +371    }
    +372
    +373    @Override
    +374    public boolean equals(Object o) {
    +375      if (this == o) return true;
    +376      if (o == null || getClass() != o.getClass()) return false;
    +377      ManagedAsset that = (ManagedAsset) o;
    +378      return com.google.common.base.Objects
    +379        .equal(module.name, that.module.name);
    +380    }
    +381
    +382    @Override
    +383    public int hashCode() {
    +384      return com.google.common.base.Objects
    +385        .hashCode(module.name);
    +386    }
    +387
    +388    @Override
    +389    public int compareTo(@Nonnull ManagedAsset other) {
    +390      return this.module.name.compareTo(other.module.name);
    +391    }
    +392
    +393    /** @return This module's assigned name. */
    +394    public @Nonnull String getName() {
    +395      return module.name;
    +396    }
    +397
    +398    /** @return This module's assigned type. */
    +399    public @Nonnull ModuleType getType() {
    +400      return module.type;
    +401    }
    +402
    +403    /** @return Collection of typed asset records constituting this bundle. */
    +404    public @Nonnull Collection<M> getAssets() {
    +405      return module.assets;
    +406    }
    +407
    +408    /** @return Content configurations associated with this asset bundle. */
    +409    public @Nonnull Collection<ManagedAssetContent> getContent() {
    +410      return this.content;
    +411    }
    +412  }
    +413
    +414  /** index the newly-installed asset bundle. */
    +415  private static void index() {
    +416    if (logging.isDebugEnabled())
    +417      logging.debug("Indexing raw assets by token...");
    +418    tokenMap.putAll(loadedBundle.getAssetList().stream()
    +419      .map(ContentInfo::fromProto)
    +420      .map((info) -> Pair.of(info.token, info))
    +421      .peek((pair) -> {
    +422        if (logging.isTraceEnabled())
    +423          logging.trace(format("- Indexing asset content at token '%s' from original file '%s'.",
    +424            pair.getKey(),
    +425            pair.getValue().filename));
    +426      })
    +427      .collect(Collectors.toMap(Pair::getKey, Pair::getValue)));
    +428
    +429    if (logging.isDebugEnabled())
    +430      logging.debug("Indexing CSS assets by module name...");
    +431    assetMap.putAll(loadedBundle.getStylesMap().entrySet().stream()
    +432      .map((entry) -> Pair.of(entry.getKey(), ModuleMetadata.fromStyleProto(entry.getValue())))
    +433      .peek((pair) -> {
    +434        // map each asset to its constituent module
    +435        pair.getValue().assets.forEach((asset) -> modulesToTokens.put(pair.getKey(), asset.getToken()));
    +436
    +437        if (logging.isTraceEnabled())
    +438          logging.trace(format("- Indexing style module '%s' of type %s.",
    +439            pair.getKey(),
    +440            pair.getValue().type));
    +441      })
    +442      .collect(Collectors.toMap(Pair::getKey, Pair::getValue)));
    +443
    +444    if (logging.isDebugEnabled())
    +445      logging.debug("Indexing JS assets by module name...");
    +446    assetMap.putAll(loadedBundle.getScriptsMap().entrySet().stream()
    +447      .map((entry) -> Pair.of(entry.getKey(), ModuleMetadata.fromScriptProto(entry.getValue())))
    +448      .peek((pair) -> {
    +449        // map each asset to its constituent module
    +450        pair.getValue().assets.forEach((asset) -> modulesToTokens.put(pair.getKey(), asset.getToken()));
    +451
    +452        if (logging.isTraceEnabled())
    +453          logging.trace(format("- Indexing script module '%s' of type %s.",
    +454            pair.getKey(),
    +455            pair.getValue().type));
    +456      })
    +457      .collect(Collectors.toMap(Pair::getKey, Pair::getValue)));
    +458  }
    +459
    +460  /**
    +461   * Attempt to force-load the asset manifest, in a static context, preparing our indexed read-only data regarding the
    +462   * data it contains. If we can't load the file, surface an exception so the invoking code can decide what to do.
    +463   *
    +464   * @throws IOException If some otherwise unmentioned I/O error occurs.
    +465   * @throws FileNotFoundException If the asset manifest could not be found.
    +466   * @throws InvalidProtocolBufferException If the enclosed Protocol Buffer data isn't recognizable.
    +467   */
    +468  public static void load() throws IOException, InvalidProtocolBufferException {
    +469    if (loadedBundle != null) return;
    +470    if (logging.isDebugEnabled())
    +471      logging.debug(format("Attempting to load manifest as resource (at path '%s')", manifestPath));
    +472
    +473    URL manifestURL = AssetManager.class.getResource(manifestPath);
    +474    if (manifestURL == null) {
    +475      logging.debug("No resource manifest found. Proceeding with empty manifest...");
    +476      loadedBundle = AssetBundle.getDefaultInstance();
    +477    } else {
    +478      if (logging.isDebugEnabled()) logging.debug("Loading resource manifest...");
    +479      try (InputStream assetBundle = AssetManager.class.getResourceAsStream(manifestPath)) {
    +480        try (BufferedInputStream buffer = new BufferedInputStream(assetBundle)) {
    +481          AssetBundle bundle = AssetBundle.parseDelimitedFrom(buffer);
    +482          Function<Integer, Boolean> plural = ((number) -> (number > 1 || number == 0));
    +483
    +484          if (bundle.isInitialized()) {
    +485            loadedBundle = bundle;
    +486            index();
    +487            logging.info(format("Asset bundle loaded with %s %s (%s %s, %s %s%s).",
    +488              bundle.getAssetCount(),
    +489              plural.apply(bundle.getAssetCount()) ? "assets" : "asset",
    +490              bundle.getScriptsCount(),
    +491              plural.apply(bundle.getScriptsCount()) ? "scripts" : "script",
    +492              bundle.getStylesCount(),
    +493              plural.apply(bundle.getStylesCount()) ? "stylesheets" : "stylesheet",
    +494              bundle.getRewrite() ? ", with rewriting ACTIVE" : ", with no style rewriting"));
    +495          }
    +496        }
    +497      }
    +498    }
    +499  }
    +500
    +501  /**
    +502   * Acquire a new instance of the asset manager. The instance provided by this method is not guaranteed to be fresh for
    +503   * every invocation (it may be a shared object), but all operations on the asset manager are threadsafe nonetheless.
    +504   *
    +505   * @return Asset manager instance.
    +506   */
    +507  public static AssetManager acquire() throws IOException {
    +508    AssetManager.load();
    +509    return new AssetManager();
    +510  }
    +511
    +512  /** Package-private constructor. Acquire an instance through {@link #acquire()}. */
    +513  @SuppressWarnings("WeakerAccess")
    +514  AssetManager() { /* Disallow instantiation except through DI. */ }
    +515
    +516  // -- Public API -- //
    +517
    +518  /**
    +519   * Resolve raw asset content by its opaque token. This will hand back an object containing representations of the
    +520   * asset for each enabled compression mode.
    +521   *
    +522   * <p>The object also knows how to resolve the most-optimal representation, based on the accepted compression modes
    +523   * indicated by the invoking client.</p>
    +524   *
    +525   * @param token Token uniquely identifying this asset (generated from the module name and content fingerprint).
    +526   * @return Optional, either {@link Optional#empty()} if the asset could not be found, or wrapping the result.
    +527   */
    +528  public @Nonnull Optional<ManagedAssetContent> assetDataByToken(@Nonnull String token) {
    +529    if (logging.isTraceEnabled())
    +530      logging.trace(format("Resolving asset by token '%s'.", token));
    +531    if (!tokenMap.containsKey(token)) {
    +532      logging.warn(format("Asset not found at token '%s'.", token));
    +533      return Optional.empty();
    +534    }
    +535    if (logging.isDebugEnabled())
    +536      logging.debug(format("Resolved valid asset via token '%s'.", token));
    +537    return Optional.of(new ManagedAssetContent(Objects.requireNonNull(tokenMap.get(token))));
    +538  }
    +539
    +540  /**
    +541   * Resolve asset metadata by its module name. This will hand back an object specifying the type/name of the module,
    +542   * and links to each of the content blocks that constitute it.
    +543   *
    +544   * <p>This code path is generally used for resolving metadata for an asset so it can be <i>referenced</i>. The serving
    +545   * for URL generated for the asset refers to a specific content block with an opaque token, rather than the module
    +546   * name, which refers to a bundle of assets or content.</p>
    +547   *
    +548   * @param module Module name for which we should resolve asset metadata.
    +549   * @return Optional, either {@link Optional#empty()} if the asset group could not be found, or wrapping the result.
    +550   */
    +551  @SuppressWarnings("unused")
    +552  public @Nonnull <M extends Message> Optional<ManagedAsset<M>> assetMetadataByModule(@Nonnull String module) {
    +553    if (logging.isTraceEnabled())
    +554      logging.trace(format("Resolving asset metadata at module '%s'.", module));
    +555    if (!assetMap.containsKey(module)) {
    +556      if (assetMap.isEmpty()) {
    +557        logging.warn(format("Asset metadata not found in (EMPTY) module map, at module name '%s'.", module));
    +558      } else {
    +559        logging.warn(format("Asset metadata not found at module name '%s'.", module));
    +560      }
    +561      return Optional.empty();
    +562    }
    +563
    +564    // resolve content for the module
    +565    ImmutableSortedSet.Builder<ManagedAssetContent> assetsBuilder = ImmutableSortedSet.naturalOrder();
    +566    Collection<String> contentTokens = modulesToTokens.get(module);
    +567
    +568    Collection<ManagedAssetContent> contents = Collections.emptySet();
    +569    if (!contentTokens.isEmpty()) {
    +570      // resolve content for each token
    +571      assetsBuilder.addAll((Iterable<ManagedAssetContent>)contentTokens.parallelStream()
    +572        .map((token) -> Pair.of(token, this.assetDataByToken(token)))
    +573        .peek((pair) -> {
    +574          var content = pair.getValue();
    +575          if (content.isPresent() && logging.isDebugEnabled()) {
    +576            logging.debug(format("Resolved content block at token '%s' for module '%s.'",
    +577              pair.getKey(),
    +578              module));
    +579          }
    +580        })
    +581        .filter((pair) -> pair.getValue().isPresent())
    +582        .map(Pair::getValue)
    +583        .map(Optional::get)
    +584        .collect(Collectors.toCollection(ConcurrentSkipListSet::new)));
    +585
    +586      contents = assetsBuilder.build();
    +587    }
    +588
    +589    if (logging.isDebugEnabled())
    +590      logging.debug(format("Resolved valid asset metadata for module '%s'.", module));
    +591    //noinspection unchecked
    +592    return Optional.of(new ManagedAsset<>(
    +593      Objects.requireNonNull((ModuleMetadata<M>)assetMap.get(module)),
    +594      contents));
    +595  }
    +596}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/runtime/AssetManager.ModuleType.html b/docs/java/src-html/gust/backend/runtime/AssetManager.ModuleType.html new file mode 100644 index 000000000..a51f16dfe --- /dev/null +++ b/docs/java/src-html/gust/backend/runtime/AssetManager.ModuleType.html @@ -0,0 +1,670 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.runtime;
    +014
    +015import com.google.common.collect.ImmutableSortedSet;
    +016import com.google.common.collect.Multimap;
    +017import com.google.common.collect.MultimapBuilder;
    +018import com.google.protobuf.InvalidProtocolBufferException;
    +019import com.google.protobuf.Message;
    +020import com.google.protobuf.Timestamp;
    +021import gust.util.Hex;
    +022import gust.util.Pair;
    +023import io.micronaut.context.annotation.Context;
    +024import io.micronaut.context.annotation.Infrastructure;
    +025import org.slf4j.Logger;
    +026import tools.elide.assets.AssetBundle;
    +027import tools.elide.assets.AssetBundle.StyleBundle.StyleAsset;
    +028import tools.elide.assets.AssetBundle.ScriptBundle.ScriptAsset;
    +029import tools.elide.core.data.CompressedData;
    +030import tools.elide.core.data.CompressionMode;
    +031
    +032import javax.annotation.Nonnull;
    +033import javax.annotation.concurrent.Immutable;
    +034import javax.annotation.concurrent.ThreadSafe;
    +035import java.io.*;
    +036import java.net.URL;
    +037import java.nio.charset.StandardCharsets;
    +038import java.security.MessageDigest;
    +039import java.security.NoSuchAlgorithmException;
    +040import java.util.*;
    +041import java.util.concurrent.ConcurrentSkipListSet;
    +042import java.util.function.Function;
    +043import java.util.stream.Collectors;
    +044
    +045import static java.lang.String.format;
    +046
    +047
    +048/**
    +049 * Manager class, which mediates interactions with the binary asset bundle. When managed assets are active, the content
    +050 * and manifest are located in a binary proto file at the root of the JAR.
    +051 *
    +052 * <p>This object acts as a singleton, and is responsible for the actual mechanics of initially reading the asset bundle
    +053 * and interpreting its contents. Once we have established indexes and completed other prep work, the manager moves into
    +054 * a read-only mode, where its primary job shifts to satisfying dynamic asset requests - either for referential metadata
    +055 * which is used to embed an asset in the DOM, or content data, which is used to serve the asset itself.</p>
    +056 *
    +057 * <p>Multiple "variants" of an asset are stored in the bundle (if so configured). This includes one variant reflecting
    +058 * the regular, un-modified content for the asset, and an additional variant for each caching strategy supported by the
    +059 * framework ({@code GZIP} and {@code BROTLI} at the time of this writing).</p>
    +060 */
    +061@Context
    +062@ThreadSafe
    +063@Infrastructure
    +064@SuppressWarnings("UnstableApiUsage")
    +065public final class AssetManager {
    +066  /** Private logging pipe. */
    +067  private static final @Nonnull Logger logging = Logging.logger(AssetManager.class);
    +068
    +069  /** Path to the asset manifest resource. */
    +070  private static final @Nonnull String manifestPath = "/assets.pb";
    +071
    +072  /** Length of generated ETag values. */
    +073  private static final int ETAG_LENGTH = 8;
    +074
    +075  /** Algorithm to use for ETag value generation. */
    +076  private static final @Nonnull String ETAG_DIGEST_ALGORITHM = "SHA-256";
    +077
    +078  /** Shared/static asset bundle object, which is immutable. */
    +079  private static volatile AssetBundle loadedBundle;
    +080
    +081  /** Specifies a map of asset modules to metadata. */
    +082  private static final @Nonnull SortedMap<String, ModuleMetadata<? extends Message>> assetMap = new TreeMap<>();
    +083
    +084  /** Maps content blocks to their module names. */
    +085  private static final @Nonnull Multimap<String, String> modulesToTokens = MultimapBuilder
    +086    .hashKeys()
    +087    .treeSetValues()
    +088    .build();
    +089
    +090  /** Specifies a map of tokens to their content info. */
    +091  private static final @Nonnull SortedMap<String, ContentInfo> tokenMap = new TreeMap<>();
    +092
    +093  /** Holds on to info related to a raw asset file. */
    +094  @Immutable
    +095  static final class ContentInfo {
    +096    /** Unique token for this asset. */
    +097    final @Nonnull String token;
    +098
    +099    /** Unique token for this asset. */
    +100    final @Nonnull String module;
    +101
    +102    /** Original filename for this asset. */
    +103    final @Nonnull String filename;
    +104
    +105    /** Uncompressed data size. */
    +106    final @Nonnull Long size;
    +107
    +108    /** Etag, calculated from the token and filename. */
    +109    final @Nonnull String etag;
    +110
    +111    /** Smallest compression option. */
    +112    final @Nonnull CompressionMode optimalCompression;
    +113
    +114    /** Size of the optimally-compressed variant. */
    +115    final @Nonnull Long compressedSize;
    +116
    +117    /** Count of variants held by this content info block. */
    +118    final @Nonnull Integer variantCount;
    +119
    +120    /** Options that exist for pre-compressed variants of this content. */
    +121    final @Nonnull EnumSet<CompressionMode> compressionOptions;
    +122
    +123    /** Pointer to the content record backing this object. */
    +124    final @Nonnull AssetBundle.AssetContent content;
    +125
    +126    /** Raw constructor for content info metadata. */
    +127    private ContentInfo(@Nonnull String token,
    +128                        @Nonnull String module,
    +129                        @Nonnull String filename,
    +130                        @Nonnull Long size,
    +131                        @Nonnull String etag,
    +132                        @Nonnull CompressionMode optimalCompression,
    +133                        @Nonnull Long compressedSize,
    +134                        @Nonnull Integer variantCount,
    +135                        @Nonnull EnumSet<CompressionMode> compressionOptions,
    +136                        @Nonnull AssetBundle.AssetContent content) {
    +137      this.token = token;
    +138      this.module = module;
    +139      this.filename = filename;
    +140      this.size = size;
    +141      this.etag = etag;
    +142      this.optimalCompression = optimalCompression;
    +143      this.compressedSize = compressedSize;
    +144      this.variantCount = variantCount;
    +145      this.compressionOptions = compressionOptions;
    +146      this.content = content;
    +147    }
    +148
    +149    /**
    +150     * Inflate a {@link ContentInfo} record from an {@link AssetBundle.AssetContent} definition. This method variant
    +151     * additionally allows specification of a custom `ETag` digest algorithm.
    +152     *
    +153     * @param content Asset content protocol object.
    +154     * @param algorithm Algorithm to use for etags.
    +155     * @return Checked content info object.
    +156     */
    +157    static @Nonnull ContentInfo fromProto(@Nonnull AssetBundle.AssetContent content, @Nonnull String algorithm) {
    +158      try {
    +159        MessageDigest digester = MessageDigest.getInstance(algorithm);
    +160        digester.update(content.getModule().getBytes(StandardCharsets.UTF_8));
    +161        digester.update(content.getFilename().getBytes(StandardCharsets.UTF_8));
    +162        digester.update(content.getToken().getBytes(StandardCharsets.UTF_8));
    +163        digester.update(String.valueOf(content.getVariantCount()).getBytes(StandardCharsets.UTF_8));
    +164        byte[] etagDigest = digester.digest();
    +165
    +166        // find uncompressed size
    +167        Long uncompressedAssetSize = content.getVariantList().stream()
    +168          .filter((variant) -> variant.getCompression().equals(CompressionMode.IDENTITY))
    +169          .findFirst()
    +170          .orElseGet(CompressedData::getDefaultInstance)
    +171          .getSize();
    +172
    +173        // resolve optimal compression
    +174        Pair<Long, CompressionMode> optimalCompression = content.getVariantList().stream()
    +175          .map((data) -> Pair.of(data.getSize(), data.getCompression()))
    +176          .min(Comparator.comparing(Pair::getKey))
    +177          .orElse(Pair.of(0L, CompressionMode.IDENTITY));
    +178
    +179        // resolve set of supported compression options for this asset
    +180        EnumSet<CompressionMode> compressionOptions = EnumSet.copyOf(content.getVariantList().parallelStream()
    +181          .map(CompressedData::getCompression)
    +182          .collect(Collectors.toList()));
    +183
    +184        return new ContentInfo(
    +185          content.getToken(),
    +186          content.getModule(),
    +187          content.getFilename(),
    +188          uncompressedAssetSize,
    +189          Hex.bytesToHex(etagDigest, ETAG_LENGTH),
    +190          optimalCompression.getValue(),
    +191          optimalCompression.getKey(),
    +192          content.getVariantCount(),
    +193          compressionOptions,
    +194          content);
    +195
    +196      } catch (NoSuchAlgorithmException exc) {
    +197        throw new RuntimeException(exc);
    +198      }
    +199    }
    +200
    +201    /**
    +202     * Inflate a {@link ContentInfo} record from an {@link AssetBundle.AssetContent} definition.
    +203     *
    +204     * @param content Asset content protocol object.
    +205     * @return Checked content info object.
    +206     */
    +207    static @Nonnull ContentInfo fromProto(AssetBundle.AssetContent content) {
    +208      return fromProto(content, ETAG_DIGEST_ALGORITHM);
    +209    }
    +210  }
    +211
    +212  /** Enumerates types of asset modules. */
    +213  public enum ModuleType {
    +214    /** The bundle contains JavaScript code. */
    +215    JS,
    +216
    +217    /** The bundle contains style declarations. */
    +218    CSS
    +219  }
    +220
    +221  /** Holds on to info related to an asset module's metadata. */
    +222  @Immutable
    +223  static final class ModuleMetadata<M extends Message> {
    +224    /** Name of this asset module. */
    +225    final @Nonnull String name;
    +226
    +227    /** Type of code/logic contained by this asset. */
    +228    final @Nonnull ModuleType type;
    +229
    +230    /** Raw asset records for this module. */
    +231    final @Nonnull List<M> assets;
    +232
    +233    /** Raw constructor for asset module metadata. */
    +234    private ModuleMetadata(@Nonnull ModuleType type,
    +235                           @Nonnull String name,
    +236                           @Nonnull List<M> assets) {
    +237      this.name = name;
    +238      this.type = type;
    +239      this.assets = assets;
    +240    }
    +241
    +242    /**
    +243     * Inflate a {@link ModuleMetadata} record from a {@link AssetBundle.StyleBundle} definition.
    +244     *
    +245     * @param content Asset content protocol object.
    +246     * @return Checked module info object.
    +247     */
    +248    static @Nonnull ModuleMetadata<StyleAsset> fromStyleProto(@Nonnull AssetBundle.StyleBundle content) {
    +249      return new ModuleMetadata<>(
    +250        ModuleType.CSS,
    +251        content.getModule(),
    +252        content.getAssetList());
    +253    }
    +254
    +255    /**
    +256     * Inflate a {@link ModuleMetadata} record from a {@link AssetBundle.ScriptBundle} definition.
    +257     *
    +258     * @param content Asset content protocol object.
    +259     * @return Checked module info object.
    +260     */
    +261    static @Nonnull ModuleMetadata<ScriptAsset> fromScriptProto(@Nonnull AssetBundle.ScriptBundle content) {
    +262      return new ModuleMetadata<>(
    +263        ModuleType.JS,
    +264        content.getModule(),
    +265        content.getAssetList());
    +266    }
    +267  }
    +268
    +269  /** Public API surface for interacting with raw asset content. */
    +270  @Immutable
    +271  @SuppressWarnings("unused")
    +272  public static final class ManagedAssetContent implements Comparable<ManagedAssetContent> {
    +273    /** Attached/encapsulated asset content and info. */
    +274    private final @Nonnull ContentInfo content;
    +275
    +276    /** Create a {@link ManagedAssetContent} object from scratch. */
    +277    ManagedAssetContent(@Nonnull ContentInfo content) {
    +278      this.content = content;
    +279    }
    +280
    +281    @Override
    +282    public boolean equals(Object other) {
    +283      if (this == other) return true;
    +284      if (other == null || getClass() != other.getClass()) return false;
    +285      ManagedAssetContent that = (ManagedAssetContent) other;
    +286      return com.google.common.base.Objects
    +287        .equal(content.token, that.content.token);
    +288    }
    +289
    +290    @Override
    +291    public int hashCode() {
    +292      return com.google.common.base.Objects.hashCode(content.token);
    +293    }
    +294
    +295    @Override
    +296    public int compareTo(@Nonnull ManagedAssetContent other) {
    +297      return this.content.token.compareTo(other.content.token);
    +298    }
    +299
    +300    /** @return Opaque token identifying this asset content. */
    +301    public @Nonnull String getToken() {
    +302      return content.token;
    +303    }
    +304
    +305    /** @return Module name for this content chunk. */
    +306    public @Nonnull String getModule() {
    +307      return content.module;
    +308    }
    +309
    +310    /** @return Pre-calculated ETag value for this asset. */
    +311    public @Nonnull String getETag() {
    +312      return content.etag;
    +313    }
    +314
    +315    /** @return Original filename for the asset. */
    +316    public @Nonnull String getFilename() {
    +317      return content.filename;
    +318    }
    +319
    +320    /** @return Last-modified-timestamp for this asset. */
    +321    public @Nonnull Timestamp getLastModified() {
    +322      return loadedBundle.getGenerated();
    +323    }
    +324
    +325    /** @return Un-compressed size of the asset. */
    +326    public @Nonnull Long getSize() {
    +327      return content.size;
    +328    }
    +329
    +330    /** @return Optimal compression mode. */
    +331    public @Nonnull CompressionMode getOptimalCompression() {
    +332      return content.optimalCompression;
    +333    }
    +334
    +335    /** @return Compressed size of the asset (optimal). */
    +336    @SuppressWarnings("WeakerAccess")
    +337    public @Nonnull Long getCompressedSize() {
    +338      return content.compressedSize;
    +339    }
    +340
    +341    /** @return Count of variants that exist for this asset. */
    +342    public @Nonnull Integer getVariantCount() {
    +343      return content.variantCount;
    +344    }
    +345
    +346    /** @return Set of supported compression modes for this asset. */
    +347    public @Nonnull EnumSet<CompressionMode> getCompressionOptions() {
    +348      return content.compressionOptions;
    +349    }
    +350
    +351    /** Retrieve the content backing this info record. */
    +352    public @Nonnull AssetBundle.AssetContent getContent() {
    +353      return content.content;
    +354    }
    +355  }
    +356
    +357  /** Public API surface for interacting with raw asset metadata. */
    +358  @Immutable
    +359  public static final class ManagedAsset<M extends Message> implements Comparable<ManagedAsset> {
    +360    /** Resolved module metadata for this asset. */
    +361    private final @Nonnull ModuleMetadata<M> module;
    +362
    +363    /** Logic references that constitute this managed asset, including dependencies, in reverse topological order. */
    +364    private final @Nonnull Collection<ManagedAssetContent> content;
    +365
    +366    /** Construct a new managed asset from scratch. */
    +367    ManagedAsset(@Nonnull ModuleMetadata<M> module,
    +368                 @Nonnull Collection<ManagedAssetContent> content) {
    +369      this.module = module;
    +370      this.content = content;
    +371    }
    +372
    +373    @Override
    +374    public boolean equals(Object o) {
    +375      if (this == o) return true;
    +376      if (o == null || getClass() != o.getClass()) return false;
    +377      ManagedAsset that = (ManagedAsset) o;
    +378      return com.google.common.base.Objects
    +379        .equal(module.name, that.module.name);
    +380    }
    +381
    +382    @Override
    +383    public int hashCode() {
    +384      return com.google.common.base.Objects
    +385        .hashCode(module.name);
    +386    }
    +387
    +388    @Override
    +389    public int compareTo(@Nonnull ManagedAsset other) {
    +390      return this.module.name.compareTo(other.module.name);
    +391    }
    +392
    +393    /** @return This module's assigned name. */
    +394    public @Nonnull String getName() {
    +395      return module.name;
    +396    }
    +397
    +398    /** @return This module's assigned type. */
    +399    public @Nonnull ModuleType getType() {
    +400      return module.type;
    +401    }
    +402
    +403    /** @return Collection of typed asset records constituting this bundle. */
    +404    public @Nonnull Collection<M> getAssets() {
    +405      return module.assets;
    +406    }
    +407
    +408    /** @return Content configurations associated with this asset bundle. */
    +409    public @Nonnull Collection<ManagedAssetContent> getContent() {
    +410      return this.content;
    +411    }
    +412  }
    +413
    +414  /** index the newly-installed asset bundle. */
    +415  private static void index() {
    +416    if (logging.isDebugEnabled())
    +417      logging.debug("Indexing raw assets by token...");
    +418    tokenMap.putAll(loadedBundle.getAssetList().stream()
    +419      .map(ContentInfo::fromProto)
    +420      .map((info) -> Pair.of(info.token, info))
    +421      .peek((pair) -> {
    +422        if (logging.isTraceEnabled())
    +423          logging.trace(format("- Indexing asset content at token '%s' from original file '%s'.",
    +424            pair.getKey(),
    +425            pair.getValue().filename));
    +426      })
    +427      .collect(Collectors.toMap(Pair::getKey, Pair::getValue)));
    +428
    +429    if (logging.isDebugEnabled())
    +430      logging.debug("Indexing CSS assets by module name...");
    +431    assetMap.putAll(loadedBundle.getStylesMap().entrySet().stream()
    +432      .map((entry) -> Pair.of(entry.getKey(), ModuleMetadata.fromStyleProto(entry.getValue())))
    +433      .peek((pair) -> {
    +434        // map each asset to its constituent module
    +435        pair.getValue().assets.forEach((asset) -> modulesToTokens.put(pair.getKey(), asset.getToken()));
    +436
    +437        if (logging.isTraceEnabled())
    +438          logging.trace(format("- Indexing style module '%s' of type %s.",
    +439            pair.getKey(),
    +440            pair.getValue().type));
    +441      })
    +442      .collect(Collectors.toMap(Pair::getKey, Pair::getValue)));
    +443
    +444    if (logging.isDebugEnabled())
    +445      logging.debug("Indexing JS assets by module name...");
    +446    assetMap.putAll(loadedBundle.getScriptsMap().entrySet().stream()
    +447      .map((entry) -> Pair.of(entry.getKey(), ModuleMetadata.fromScriptProto(entry.getValue())))
    +448      .peek((pair) -> {
    +449        // map each asset to its constituent module
    +450        pair.getValue().assets.forEach((asset) -> modulesToTokens.put(pair.getKey(), asset.getToken()));
    +451
    +452        if (logging.isTraceEnabled())
    +453          logging.trace(format("- Indexing script module '%s' of type %s.",
    +454            pair.getKey(),
    +455            pair.getValue().type));
    +456      })
    +457      .collect(Collectors.toMap(Pair::getKey, Pair::getValue)));
    +458  }
    +459
    +460  /**
    +461   * Attempt to force-load the asset manifest, in a static context, preparing our indexed read-only data regarding the
    +462   * data it contains. If we can't load the file, surface an exception so the invoking code can decide what to do.
    +463   *
    +464   * @throws IOException If some otherwise unmentioned I/O error occurs.
    +465   * @throws FileNotFoundException If the asset manifest could not be found.
    +466   * @throws InvalidProtocolBufferException If the enclosed Protocol Buffer data isn't recognizable.
    +467   */
    +468  public static void load() throws IOException, InvalidProtocolBufferException {
    +469    if (loadedBundle != null) return;
    +470    if (logging.isDebugEnabled())
    +471      logging.debug(format("Attempting to load manifest as resource (at path '%s')", manifestPath));
    +472
    +473    URL manifestURL = AssetManager.class.getResource(manifestPath);
    +474    if (manifestURL == null) {
    +475      logging.debug("No resource manifest found. Proceeding with empty manifest...");
    +476      loadedBundle = AssetBundle.getDefaultInstance();
    +477    } else {
    +478      if (logging.isDebugEnabled()) logging.debug("Loading resource manifest...");
    +479      try (InputStream assetBundle = AssetManager.class.getResourceAsStream(manifestPath)) {
    +480        try (BufferedInputStream buffer = new BufferedInputStream(assetBundle)) {
    +481          AssetBundle bundle = AssetBundle.parseDelimitedFrom(buffer);
    +482          Function<Integer, Boolean> plural = ((number) -> (number > 1 || number == 0));
    +483
    +484          if (bundle.isInitialized()) {
    +485            loadedBundle = bundle;
    +486            index();
    +487            logging.info(format("Asset bundle loaded with %s %s (%s %s, %s %s%s).",
    +488              bundle.getAssetCount(),
    +489              plural.apply(bundle.getAssetCount()) ? "assets" : "asset",
    +490              bundle.getScriptsCount(),
    +491              plural.apply(bundle.getScriptsCount()) ? "scripts" : "script",
    +492              bundle.getStylesCount(),
    +493              plural.apply(bundle.getStylesCount()) ? "stylesheets" : "stylesheet",
    +494              bundle.getRewrite() ? ", with rewriting ACTIVE" : ", with no style rewriting"));
    +495          }
    +496        }
    +497      }
    +498    }
    +499  }
    +500
    +501  /**
    +502   * Acquire a new instance of the asset manager. The instance provided by this method is not guaranteed to be fresh for
    +503   * every invocation (it may be a shared object), but all operations on the asset manager are threadsafe nonetheless.
    +504   *
    +505   * @return Asset manager instance.
    +506   */
    +507  public static AssetManager acquire() throws IOException {
    +508    AssetManager.load();
    +509    return new AssetManager();
    +510  }
    +511
    +512  /** Package-private constructor. Acquire an instance through {@link #acquire()}. */
    +513  @SuppressWarnings("WeakerAccess")
    +514  AssetManager() { /* Disallow instantiation except through DI. */ }
    +515
    +516  // -- Public API -- //
    +517
    +518  /**
    +519   * Resolve raw asset content by its opaque token. This will hand back an object containing representations of the
    +520   * asset for each enabled compression mode.
    +521   *
    +522   * <p>The object also knows how to resolve the most-optimal representation, based on the accepted compression modes
    +523   * indicated by the invoking client.</p>
    +524   *
    +525   * @param token Token uniquely identifying this asset (generated from the module name and content fingerprint).
    +526   * @return Optional, either {@link Optional#empty()} if the asset could not be found, or wrapping the result.
    +527   */
    +528  public @Nonnull Optional<ManagedAssetContent> assetDataByToken(@Nonnull String token) {
    +529    if (logging.isTraceEnabled())
    +530      logging.trace(format("Resolving asset by token '%s'.", token));
    +531    if (!tokenMap.containsKey(token)) {
    +532      logging.warn(format("Asset not found at token '%s'.", token));
    +533      return Optional.empty();
    +534    }
    +535    if (logging.isDebugEnabled())
    +536      logging.debug(format("Resolved valid asset via token '%s'.", token));
    +537    return Optional.of(new ManagedAssetContent(Objects.requireNonNull(tokenMap.get(token))));
    +538  }
    +539
    +540  /**
    +541   * Resolve asset metadata by its module name. This will hand back an object specifying the type/name of the module,
    +542   * and links to each of the content blocks that constitute it.
    +543   *
    +544   * <p>This code path is generally used for resolving metadata for an asset so it can be <i>referenced</i>. The serving
    +545   * for URL generated for the asset refers to a specific content block with an opaque token, rather than the module
    +546   * name, which refers to a bundle of assets or content.</p>
    +547   *
    +548   * @param module Module name for which we should resolve asset metadata.
    +549   * @return Optional, either {@link Optional#empty()} if the asset group could not be found, or wrapping the result.
    +550   */
    +551  @SuppressWarnings("unused")
    +552  public @Nonnull <M extends Message> Optional<ManagedAsset<M>> assetMetadataByModule(@Nonnull String module) {
    +553    if (logging.isTraceEnabled())
    +554      logging.trace(format("Resolving asset metadata at module '%s'.", module));
    +555    if (!assetMap.containsKey(module)) {
    +556      if (assetMap.isEmpty()) {
    +557        logging.warn(format("Asset metadata not found in (EMPTY) module map, at module name '%s'.", module));
    +558      } else {
    +559        logging.warn(format("Asset metadata not found at module name '%s'.", module));
    +560      }
    +561      return Optional.empty();
    +562    }
    +563
    +564    // resolve content for the module
    +565    ImmutableSortedSet.Builder<ManagedAssetContent> assetsBuilder = ImmutableSortedSet.naturalOrder();
    +566    Collection<String> contentTokens = modulesToTokens.get(module);
    +567
    +568    Collection<ManagedAssetContent> contents = Collections.emptySet();
    +569    if (!contentTokens.isEmpty()) {
    +570      // resolve content for each token
    +571      assetsBuilder.addAll((Iterable<ManagedAssetContent>)contentTokens.parallelStream()
    +572        .map((token) -> Pair.of(token, this.assetDataByToken(token)))
    +573        .peek((pair) -> {
    +574          var content = pair.getValue();
    +575          if (content.isPresent() && logging.isDebugEnabled()) {
    +576            logging.debug(format("Resolved content block at token '%s' for module '%s.'",
    +577              pair.getKey(),
    +578              module));
    +579          }
    +580        })
    +581        .filter((pair) -> pair.getValue().isPresent())
    +582        .map(Pair::getValue)
    +583        .map(Optional::get)
    +584        .collect(Collectors.toCollection(ConcurrentSkipListSet::new)));
    +585
    +586      contents = assetsBuilder.build();
    +587    }
    +588
    +589    if (logging.isDebugEnabled())
    +590      logging.debug(format("Resolved valid asset metadata for module '%s'.", module));
    +591    //noinspection unchecked
    +592    return Optional.of(new ManagedAsset<>(
    +593      Objects.requireNonNull((ModuleMetadata<M>)assetMap.get(module)),
    +594      contents));
    +595  }
    +596}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/runtime/AssetManager.html b/docs/java/src-html/gust/backend/runtime/AssetManager.html new file mode 100644 index 000000000..a51f16dfe --- /dev/null +++ b/docs/java/src-html/gust/backend/runtime/AssetManager.html @@ -0,0 +1,670 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.runtime;
    +014
    +015import com.google.common.collect.ImmutableSortedSet;
    +016import com.google.common.collect.Multimap;
    +017import com.google.common.collect.MultimapBuilder;
    +018import com.google.protobuf.InvalidProtocolBufferException;
    +019import com.google.protobuf.Message;
    +020import com.google.protobuf.Timestamp;
    +021import gust.util.Hex;
    +022import gust.util.Pair;
    +023import io.micronaut.context.annotation.Context;
    +024import io.micronaut.context.annotation.Infrastructure;
    +025import org.slf4j.Logger;
    +026import tools.elide.assets.AssetBundle;
    +027import tools.elide.assets.AssetBundle.StyleBundle.StyleAsset;
    +028import tools.elide.assets.AssetBundle.ScriptBundle.ScriptAsset;
    +029import tools.elide.core.data.CompressedData;
    +030import tools.elide.core.data.CompressionMode;
    +031
    +032import javax.annotation.Nonnull;
    +033import javax.annotation.concurrent.Immutable;
    +034import javax.annotation.concurrent.ThreadSafe;
    +035import java.io.*;
    +036import java.net.URL;
    +037import java.nio.charset.StandardCharsets;
    +038import java.security.MessageDigest;
    +039import java.security.NoSuchAlgorithmException;
    +040import java.util.*;
    +041import java.util.concurrent.ConcurrentSkipListSet;
    +042import java.util.function.Function;
    +043import java.util.stream.Collectors;
    +044
    +045import static java.lang.String.format;
    +046
    +047
    +048/**
    +049 * Manager class, which mediates interactions with the binary asset bundle. When managed assets are active, the content
    +050 * and manifest are located in a binary proto file at the root of the JAR.
    +051 *
    +052 * <p>This object acts as a singleton, and is responsible for the actual mechanics of initially reading the asset bundle
    +053 * and interpreting its contents. Once we have established indexes and completed other prep work, the manager moves into
    +054 * a read-only mode, where its primary job shifts to satisfying dynamic asset requests - either for referential metadata
    +055 * which is used to embed an asset in the DOM, or content data, which is used to serve the asset itself.</p>
    +056 *
    +057 * <p>Multiple "variants" of an asset are stored in the bundle (if so configured). This includes one variant reflecting
    +058 * the regular, un-modified content for the asset, and an additional variant for each caching strategy supported by the
    +059 * framework ({@code GZIP} and {@code BROTLI} at the time of this writing).</p>
    +060 */
    +061@Context
    +062@ThreadSafe
    +063@Infrastructure
    +064@SuppressWarnings("UnstableApiUsage")
    +065public final class AssetManager {
    +066  /** Private logging pipe. */
    +067  private static final @Nonnull Logger logging = Logging.logger(AssetManager.class);
    +068
    +069  /** Path to the asset manifest resource. */
    +070  private static final @Nonnull String manifestPath = "/assets.pb";
    +071
    +072  /** Length of generated ETag values. */
    +073  private static final int ETAG_LENGTH = 8;
    +074
    +075  /** Algorithm to use for ETag value generation. */
    +076  private static final @Nonnull String ETAG_DIGEST_ALGORITHM = "SHA-256";
    +077
    +078  /** Shared/static asset bundle object, which is immutable. */
    +079  private static volatile AssetBundle loadedBundle;
    +080
    +081  /** Specifies a map of asset modules to metadata. */
    +082  private static final @Nonnull SortedMap<String, ModuleMetadata<? extends Message>> assetMap = new TreeMap<>();
    +083
    +084  /** Maps content blocks to their module names. */
    +085  private static final @Nonnull Multimap<String, String> modulesToTokens = MultimapBuilder
    +086    .hashKeys()
    +087    .treeSetValues()
    +088    .build();
    +089
    +090  /** Specifies a map of tokens to their content info. */
    +091  private static final @Nonnull SortedMap<String, ContentInfo> tokenMap = new TreeMap<>();
    +092
    +093  /** Holds on to info related to a raw asset file. */
    +094  @Immutable
    +095  static final class ContentInfo {
    +096    /** Unique token for this asset. */
    +097    final @Nonnull String token;
    +098
    +099    /** Unique token for this asset. */
    +100    final @Nonnull String module;
    +101
    +102    /** Original filename for this asset. */
    +103    final @Nonnull String filename;
    +104
    +105    /** Uncompressed data size. */
    +106    final @Nonnull Long size;
    +107
    +108    /** Etag, calculated from the token and filename. */
    +109    final @Nonnull String etag;
    +110
    +111    /** Smallest compression option. */
    +112    final @Nonnull CompressionMode optimalCompression;
    +113
    +114    /** Size of the optimally-compressed variant. */
    +115    final @Nonnull Long compressedSize;
    +116
    +117    /** Count of variants held by this content info block. */
    +118    final @Nonnull Integer variantCount;
    +119
    +120    /** Options that exist for pre-compressed variants of this content. */
    +121    final @Nonnull EnumSet<CompressionMode> compressionOptions;
    +122
    +123    /** Pointer to the content record backing this object. */
    +124    final @Nonnull AssetBundle.AssetContent content;
    +125
    +126    /** Raw constructor for content info metadata. */
    +127    private ContentInfo(@Nonnull String token,
    +128                        @Nonnull String module,
    +129                        @Nonnull String filename,
    +130                        @Nonnull Long size,
    +131                        @Nonnull String etag,
    +132                        @Nonnull CompressionMode optimalCompression,
    +133                        @Nonnull Long compressedSize,
    +134                        @Nonnull Integer variantCount,
    +135                        @Nonnull EnumSet<CompressionMode> compressionOptions,
    +136                        @Nonnull AssetBundle.AssetContent content) {
    +137      this.token = token;
    +138      this.module = module;
    +139      this.filename = filename;
    +140      this.size = size;
    +141      this.etag = etag;
    +142      this.optimalCompression = optimalCompression;
    +143      this.compressedSize = compressedSize;
    +144      this.variantCount = variantCount;
    +145      this.compressionOptions = compressionOptions;
    +146      this.content = content;
    +147    }
    +148
    +149    /**
    +150     * Inflate a {@link ContentInfo} record from an {@link AssetBundle.AssetContent} definition. This method variant
    +151     * additionally allows specification of a custom `ETag` digest algorithm.
    +152     *
    +153     * @param content Asset content protocol object.
    +154     * @param algorithm Algorithm to use for etags.
    +155     * @return Checked content info object.
    +156     */
    +157    static @Nonnull ContentInfo fromProto(@Nonnull AssetBundle.AssetContent content, @Nonnull String algorithm) {
    +158      try {
    +159        MessageDigest digester = MessageDigest.getInstance(algorithm);
    +160        digester.update(content.getModule().getBytes(StandardCharsets.UTF_8));
    +161        digester.update(content.getFilename().getBytes(StandardCharsets.UTF_8));
    +162        digester.update(content.getToken().getBytes(StandardCharsets.UTF_8));
    +163        digester.update(String.valueOf(content.getVariantCount()).getBytes(StandardCharsets.UTF_8));
    +164        byte[] etagDigest = digester.digest();
    +165
    +166        // find uncompressed size
    +167        Long uncompressedAssetSize = content.getVariantList().stream()
    +168          .filter((variant) -> variant.getCompression().equals(CompressionMode.IDENTITY))
    +169          .findFirst()
    +170          .orElseGet(CompressedData::getDefaultInstance)
    +171          .getSize();
    +172
    +173        // resolve optimal compression
    +174        Pair<Long, CompressionMode> optimalCompression = content.getVariantList().stream()
    +175          .map((data) -> Pair.of(data.getSize(), data.getCompression()))
    +176          .min(Comparator.comparing(Pair::getKey))
    +177          .orElse(Pair.of(0L, CompressionMode.IDENTITY));
    +178
    +179        // resolve set of supported compression options for this asset
    +180        EnumSet<CompressionMode> compressionOptions = EnumSet.copyOf(content.getVariantList().parallelStream()
    +181          .map(CompressedData::getCompression)
    +182          .collect(Collectors.toList()));
    +183
    +184        return new ContentInfo(
    +185          content.getToken(),
    +186          content.getModule(),
    +187          content.getFilename(),
    +188          uncompressedAssetSize,
    +189          Hex.bytesToHex(etagDigest, ETAG_LENGTH),
    +190          optimalCompression.getValue(),
    +191          optimalCompression.getKey(),
    +192          content.getVariantCount(),
    +193          compressionOptions,
    +194          content);
    +195
    +196      } catch (NoSuchAlgorithmException exc) {
    +197        throw new RuntimeException(exc);
    +198      }
    +199    }
    +200
    +201    /**
    +202     * Inflate a {@link ContentInfo} record from an {@link AssetBundle.AssetContent} definition.
    +203     *
    +204     * @param content Asset content protocol object.
    +205     * @return Checked content info object.
    +206     */
    +207    static @Nonnull ContentInfo fromProto(AssetBundle.AssetContent content) {
    +208      return fromProto(content, ETAG_DIGEST_ALGORITHM);
    +209    }
    +210  }
    +211
    +212  /** Enumerates types of asset modules. */
    +213  public enum ModuleType {
    +214    /** The bundle contains JavaScript code. */
    +215    JS,
    +216
    +217    /** The bundle contains style declarations. */
    +218    CSS
    +219  }
    +220
    +221  /** Holds on to info related to an asset module's metadata. */
    +222  @Immutable
    +223  static final class ModuleMetadata<M extends Message> {
    +224    /** Name of this asset module. */
    +225    final @Nonnull String name;
    +226
    +227    /** Type of code/logic contained by this asset. */
    +228    final @Nonnull ModuleType type;
    +229
    +230    /** Raw asset records for this module. */
    +231    final @Nonnull List<M> assets;
    +232
    +233    /** Raw constructor for asset module metadata. */
    +234    private ModuleMetadata(@Nonnull ModuleType type,
    +235                           @Nonnull String name,
    +236                           @Nonnull List<M> assets) {
    +237      this.name = name;
    +238      this.type = type;
    +239      this.assets = assets;
    +240    }
    +241
    +242    /**
    +243     * Inflate a {@link ModuleMetadata} record from a {@link AssetBundle.StyleBundle} definition.
    +244     *
    +245     * @param content Asset content protocol object.
    +246     * @return Checked module info object.
    +247     */
    +248    static @Nonnull ModuleMetadata<StyleAsset> fromStyleProto(@Nonnull AssetBundle.StyleBundle content) {
    +249      return new ModuleMetadata<>(
    +250        ModuleType.CSS,
    +251        content.getModule(),
    +252        content.getAssetList());
    +253    }
    +254
    +255    /**
    +256     * Inflate a {@link ModuleMetadata} record from a {@link AssetBundle.ScriptBundle} definition.
    +257     *
    +258     * @param content Asset content protocol object.
    +259     * @return Checked module info object.
    +260     */
    +261    static @Nonnull ModuleMetadata<ScriptAsset> fromScriptProto(@Nonnull AssetBundle.ScriptBundle content) {
    +262      return new ModuleMetadata<>(
    +263        ModuleType.JS,
    +264        content.getModule(),
    +265        content.getAssetList());
    +266    }
    +267  }
    +268
    +269  /** Public API surface for interacting with raw asset content. */
    +270  @Immutable
    +271  @SuppressWarnings("unused")
    +272  public static final class ManagedAssetContent implements Comparable<ManagedAssetContent> {
    +273    /** Attached/encapsulated asset content and info. */
    +274    private final @Nonnull ContentInfo content;
    +275
    +276    /** Create a {@link ManagedAssetContent} object from scratch. */
    +277    ManagedAssetContent(@Nonnull ContentInfo content) {
    +278      this.content = content;
    +279    }
    +280
    +281    @Override
    +282    public boolean equals(Object other) {
    +283      if (this == other) return true;
    +284      if (other == null || getClass() != other.getClass()) return false;
    +285      ManagedAssetContent that = (ManagedAssetContent) other;
    +286      return com.google.common.base.Objects
    +287        .equal(content.token, that.content.token);
    +288    }
    +289
    +290    @Override
    +291    public int hashCode() {
    +292      return com.google.common.base.Objects.hashCode(content.token);
    +293    }
    +294
    +295    @Override
    +296    public int compareTo(@Nonnull ManagedAssetContent other) {
    +297      return this.content.token.compareTo(other.content.token);
    +298    }
    +299
    +300    /** @return Opaque token identifying this asset content. */
    +301    public @Nonnull String getToken() {
    +302      return content.token;
    +303    }
    +304
    +305    /** @return Module name for this content chunk. */
    +306    public @Nonnull String getModule() {
    +307      return content.module;
    +308    }
    +309
    +310    /** @return Pre-calculated ETag value for this asset. */
    +311    public @Nonnull String getETag() {
    +312      return content.etag;
    +313    }
    +314
    +315    /** @return Original filename for the asset. */
    +316    public @Nonnull String getFilename() {
    +317      return content.filename;
    +318    }
    +319
    +320    /** @return Last-modified-timestamp for this asset. */
    +321    public @Nonnull Timestamp getLastModified() {
    +322      return loadedBundle.getGenerated();
    +323    }
    +324
    +325    /** @return Un-compressed size of the asset. */
    +326    public @Nonnull Long getSize() {
    +327      return content.size;
    +328    }
    +329
    +330    /** @return Optimal compression mode. */
    +331    public @Nonnull CompressionMode getOptimalCompression() {
    +332      return content.optimalCompression;
    +333    }
    +334
    +335    /** @return Compressed size of the asset (optimal). */
    +336    @SuppressWarnings("WeakerAccess")
    +337    public @Nonnull Long getCompressedSize() {
    +338      return content.compressedSize;
    +339    }
    +340
    +341    /** @return Count of variants that exist for this asset. */
    +342    public @Nonnull Integer getVariantCount() {
    +343      return content.variantCount;
    +344    }
    +345
    +346    /** @return Set of supported compression modes for this asset. */
    +347    public @Nonnull EnumSet<CompressionMode> getCompressionOptions() {
    +348      return content.compressionOptions;
    +349    }
    +350
    +351    /** Retrieve the content backing this info record. */
    +352    public @Nonnull AssetBundle.AssetContent getContent() {
    +353      return content.content;
    +354    }
    +355  }
    +356
    +357  /** Public API surface for interacting with raw asset metadata. */
    +358  @Immutable
    +359  public static final class ManagedAsset<M extends Message> implements Comparable<ManagedAsset> {
    +360    /** Resolved module metadata for this asset. */
    +361    private final @Nonnull ModuleMetadata<M> module;
    +362
    +363    /** Logic references that constitute this managed asset, including dependencies, in reverse topological order. */
    +364    private final @Nonnull Collection<ManagedAssetContent> content;
    +365
    +366    /** Construct a new managed asset from scratch. */
    +367    ManagedAsset(@Nonnull ModuleMetadata<M> module,
    +368                 @Nonnull Collection<ManagedAssetContent> content) {
    +369      this.module = module;
    +370      this.content = content;
    +371    }
    +372
    +373    @Override
    +374    public boolean equals(Object o) {
    +375      if (this == o) return true;
    +376      if (o == null || getClass() != o.getClass()) return false;
    +377      ManagedAsset that = (ManagedAsset) o;
    +378      return com.google.common.base.Objects
    +379        .equal(module.name, that.module.name);
    +380    }
    +381
    +382    @Override
    +383    public int hashCode() {
    +384      return com.google.common.base.Objects
    +385        .hashCode(module.name);
    +386    }
    +387
    +388    @Override
    +389    public int compareTo(@Nonnull ManagedAsset other) {
    +390      return this.module.name.compareTo(other.module.name);
    +391    }
    +392
    +393    /** @return This module's assigned name. */
    +394    public @Nonnull String getName() {
    +395      return module.name;
    +396    }
    +397
    +398    /** @return This module's assigned type. */
    +399    public @Nonnull ModuleType getType() {
    +400      return module.type;
    +401    }
    +402
    +403    /** @return Collection of typed asset records constituting this bundle. */
    +404    public @Nonnull Collection<M> getAssets() {
    +405      return module.assets;
    +406    }
    +407
    +408    /** @return Content configurations associated with this asset bundle. */
    +409    public @Nonnull Collection<ManagedAssetContent> getContent() {
    +410      return this.content;
    +411    }
    +412  }
    +413
    +414  /** index the newly-installed asset bundle. */
    +415  private static void index() {
    +416    if (logging.isDebugEnabled())
    +417      logging.debug("Indexing raw assets by token...");
    +418    tokenMap.putAll(loadedBundle.getAssetList().stream()
    +419      .map(ContentInfo::fromProto)
    +420      .map((info) -> Pair.of(info.token, info))
    +421      .peek((pair) -> {
    +422        if (logging.isTraceEnabled())
    +423          logging.trace(format("- Indexing asset content at token '%s' from original file '%s'.",
    +424            pair.getKey(),
    +425            pair.getValue().filename));
    +426      })
    +427      .collect(Collectors.toMap(Pair::getKey, Pair::getValue)));
    +428
    +429    if (logging.isDebugEnabled())
    +430      logging.debug("Indexing CSS assets by module name...");
    +431    assetMap.putAll(loadedBundle.getStylesMap().entrySet().stream()
    +432      .map((entry) -> Pair.of(entry.getKey(), ModuleMetadata.fromStyleProto(entry.getValue())))
    +433      .peek((pair) -> {
    +434        // map each asset to its constituent module
    +435        pair.getValue().assets.forEach((asset) -> modulesToTokens.put(pair.getKey(), asset.getToken()));
    +436
    +437        if (logging.isTraceEnabled())
    +438          logging.trace(format("- Indexing style module '%s' of type %s.",
    +439            pair.getKey(),
    +440            pair.getValue().type));
    +441      })
    +442      .collect(Collectors.toMap(Pair::getKey, Pair::getValue)));
    +443
    +444    if (logging.isDebugEnabled())
    +445      logging.debug("Indexing JS assets by module name...");
    +446    assetMap.putAll(loadedBundle.getScriptsMap().entrySet().stream()
    +447      .map((entry) -> Pair.of(entry.getKey(), ModuleMetadata.fromScriptProto(entry.getValue())))
    +448      .peek((pair) -> {
    +449        // map each asset to its constituent module
    +450        pair.getValue().assets.forEach((asset) -> modulesToTokens.put(pair.getKey(), asset.getToken()));
    +451
    +452        if (logging.isTraceEnabled())
    +453          logging.trace(format("- Indexing script module '%s' of type %s.",
    +454            pair.getKey(),
    +455            pair.getValue().type));
    +456      })
    +457      .collect(Collectors.toMap(Pair::getKey, Pair::getValue)));
    +458  }
    +459
    +460  /**
    +461   * Attempt to force-load the asset manifest, in a static context, preparing our indexed read-only data regarding the
    +462   * data it contains. If we can't load the file, surface an exception so the invoking code can decide what to do.
    +463   *
    +464   * @throws IOException If some otherwise unmentioned I/O error occurs.
    +465   * @throws FileNotFoundException If the asset manifest could not be found.
    +466   * @throws InvalidProtocolBufferException If the enclosed Protocol Buffer data isn't recognizable.
    +467   */
    +468  public static void load() throws IOException, InvalidProtocolBufferException {
    +469    if (loadedBundle != null) return;
    +470    if (logging.isDebugEnabled())
    +471      logging.debug(format("Attempting to load manifest as resource (at path '%s')", manifestPath));
    +472
    +473    URL manifestURL = AssetManager.class.getResource(manifestPath);
    +474    if (manifestURL == null) {
    +475      logging.debug("No resource manifest found. Proceeding with empty manifest...");
    +476      loadedBundle = AssetBundle.getDefaultInstance();
    +477    } else {
    +478      if (logging.isDebugEnabled()) logging.debug("Loading resource manifest...");
    +479      try (InputStream assetBundle = AssetManager.class.getResourceAsStream(manifestPath)) {
    +480        try (BufferedInputStream buffer = new BufferedInputStream(assetBundle)) {
    +481          AssetBundle bundle = AssetBundle.parseDelimitedFrom(buffer);
    +482          Function<Integer, Boolean> plural = ((number) -> (number > 1 || number == 0));
    +483
    +484          if (bundle.isInitialized()) {
    +485            loadedBundle = bundle;
    +486            index();
    +487            logging.info(format("Asset bundle loaded with %s %s (%s %s, %s %s%s).",
    +488              bundle.getAssetCount(),
    +489              plural.apply(bundle.getAssetCount()) ? "assets" : "asset",
    +490              bundle.getScriptsCount(),
    +491              plural.apply(bundle.getScriptsCount()) ? "scripts" : "script",
    +492              bundle.getStylesCount(),
    +493              plural.apply(bundle.getStylesCount()) ? "stylesheets" : "stylesheet",
    +494              bundle.getRewrite() ? ", with rewriting ACTIVE" : ", with no style rewriting"));
    +495          }
    +496        }
    +497      }
    +498    }
    +499  }
    +500
    +501  /**
    +502   * Acquire a new instance of the asset manager. The instance provided by this method is not guaranteed to be fresh for
    +503   * every invocation (it may be a shared object), but all operations on the asset manager are threadsafe nonetheless.
    +504   *
    +505   * @return Asset manager instance.
    +506   */
    +507  public static AssetManager acquire() throws IOException {
    +508    AssetManager.load();
    +509    return new AssetManager();
    +510  }
    +511
    +512  /** Package-private constructor. Acquire an instance through {@link #acquire()}. */
    +513  @SuppressWarnings("WeakerAccess")
    +514  AssetManager() { /* Disallow instantiation except through DI. */ }
    +515
    +516  // -- Public API -- //
    +517
    +518  /**
    +519   * Resolve raw asset content by its opaque token. This will hand back an object containing representations of the
    +520   * asset for each enabled compression mode.
    +521   *
    +522   * <p>The object also knows how to resolve the most-optimal representation, based on the accepted compression modes
    +523   * indicated by the invoking client.</p>
    +524   *
    +525   * @param token Token uniquely identifying this asset (generated from the module name and content fingerprint).
    +526   * @return Optional, either {@link Optional#empty()} if the asset could not be found, or wrapping the result.
    +527   */
    +528  public @Nonnull Optional<ManagedAssetContent> assetDataByToken(@Nonnull String token) {
    +529    if (logging.isTraceEnabled())
    +530      logging.trace(format("Resolving asset by token '%s'.", token));
    +531    if (!tokenMap.containsKey(token)) {
    +532      logging.warn(format("Asset not found at token '%s'.", token));
    +533      return Optional.empty();
    +534    }
    +535    if (logging.isDebugEnabled())
    +536      logging.debug(format("Resolved valid asset via token '%s'.", token));
    +537    return Optional.of(new ManagedAssetContent(Objects.requireNonNull(tokenMap.get(token))));
    +538  }
    +539
    +540  /**
    +541   * Resolve asset metadata by its module name. This will hand back an object specifying the type/name of the module,
    +542   * and links to each of the content blocks that constitute it.
    +543   *
    +544   * <p>This code path is generally used for resolving metadata for an asset so it can be <i>referenced</i>. The serving
    +545   * for URL generated for the asset refers to a specific content block with an opaque token, rather than the module
    +546   * name, which refers to a bundle of assets or content.</p>
    +547   *
    +548   * @param module Module name for which we should resolve asset metadata.
    +549   * @return Optional, either {@link Optional#empty()} if the asset group could not be found, or wrapping the result.
    +550   */
    +551  @SuppressWarnings("unused")
    +552  public @Nonnull <M extends Message> Optional<ManagedAsset<M>> assetMetadataByModule(@Nonnull String module) {
    +553    if (logging.isTraceEnabled())
    +554      logging.trace(format("Resolving asset metadata at module '%s'.", module));
    +555    if (!assetMap.containsKey(module)) {
    +556      if (assetMap.isEmpty()) {
    +557        logging.warn(format("Asset metadata not found in (EMPTY) module map, at module name '%s'.", module));
    +558      } else {
    +559        logging.warn(format("Asset metadata not found at module name '%s'.", module));
    +560      }
    +561      return Optional.empty();
    +562    }
    +563
    +564    // resolve content for the module
    +565    ImmutableSortedSet.Builder<ManagedAssetContent> assetsBuilder = ImmutableSortedSet.naturalOrder();
    +566    Collection<String> contentTokens = modulesToTokens.get(module);
    +567
    +568    Collection<ManagedAssetContent> contents = Collections.emptySet();
    +569    if (!contentTokens.isEmpty()) {
    +570      // resolve content for each token
    +571      assetsBuilder.addAll((Iterable<ManagedAssetContent>)contentTokens.parallelStream()
    +572        .map((token) -> Pair.of(token, this.assetDataByToken(token)))
    +573        .peek((pair) -> {
    +574          var content = pair.getValue();
    +575          if (content.isPresent() && logging.isDebugEnabled()) {
    +576            logging.debug(format("Resolved content block at token '%s' for module '%s.'",
    +577              pair.getKey(),
    +578              module));
    +579          }
    +580        })
    +581        .filter((pair) -> pair.getValue().isPresent())
    +582        .map(Pair::getValue)
    +583        .map(Optional::get)
    +584        .collect(Collectors.toCollection(ConcurrentSkipListSet::new)));
    +585
    +586      contents = assetsBuilder.build();
    +587    }
    +588
    +589    if (logging.isDebugEnabled())
    +590      logging.debug(format("Resolved valid asset metadata for module '%s'.", module));
    +591    //noinspection unchecked
    +592    return Optional.of(new ManagedAsset<>(
    +593      Objects.requireNonNull((ModuleMetadata<M>)assetMap.get(module)),
    +594      contents));
    +595  }
    +596}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/runtime/Logging.html b/docs/java/src-html/gust/backend/runtime/Logging.html new file mode 100644 index 000000000..11704fad0 --- /dev/null +++ b/docs/java/src-html/gust/backend/runtime/Logging.html @@ -0,0 +1,105 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.runtime;
    +014
    +015import org.slf4j.Logger;
    +016import org.slf4j.LoggerFactory;
    +017
    +018
    +019/** Sugar bridge to SLF4J. */
    +020public final class Logging {
    +021  private Logging() { /* Disallow instantiation. */ }
    +022
    +023  /**
    +024   * Retrieve a logger for a particular Java class, named after the fully-qualified path to the class.
    +025   *
    +026   * @param cls Java class to get a logger for.
    +027   * @return Logger for the specified class.
    +028   */
    +029  public static Logger logger(Class cls) { return LoggerFactory.getLogger(cls.getName());
    +030  }
    +031}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/runtime/ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription.html b/docs/java/src-html/gust/backend/runtime/ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription.html new file mode 100644 index 000000000..a365627a5 --- /dev/null +++ b/docs/java/src-html/gust/backend/runtime/ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription.html @@ -0,0 +1,1226 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.runtime;
    +014
    +015import com.google.api.core.ApiFuture;
    +016import com.google.api.core.ApiFutureToListenableFuture;
    +017import com.google.common.util.concurrent.Futures;
    +018import com.google.common.util.concurrent.ListenableFuture;
    +019import com.google.common.util.concurrent.MoreExecutors;
    +020import com.google.common.util.concurrent.SettableFuture;
    +021import org.reactivestreams.Publisher;
    +022import org.reactivestreams.Subscriber;
    +023import org.reactivestreams.Subscription;
    +024
    +025import javax.annotation.Nonnull;
    +026import javax.annotation.Nullable;
    +027import javax.annotation.concurrent.Immutable;
    +028import javax.annotation.concurrent.ThreadSafe;
    +029import java.util.*;
    +030import java.util.concurrent.*;
    +031import java.util.concurrent.atomic.AtomicBoolean;
    +032import java.util.function.BiConsumer;
    +033import java.util.function.BiFunction;
    +034import java.util.function.Consumer;
    +035import java.util.function.Function;
    +036
    +037
    +038/**
    +039 * Adapts future/async value containers from different frameworks (namely, Reactive Java, Guava, and the JDK).
    +040 *
    +041 * <p>Create a new {@link ReactiveFuture} by using any of the {@code wrap)} factory methods. The resulting object is
    +042 * usable as a {@link Publisher}, {@link ListenableFuture}, or {@link ApiFuture}. This object simply wraps whatever
    +043 * inner object is provided, and as such instances are lightweight; there is no default functionality after immediate
    +044 * construction in most cases.</p>
    +045 *
    +046 * <p><b>Caveat:</b> when using a {@link Publisher} as a {@link ListenableFuture} (i.e. wrapping a {@link Publisher} and
    +047 * then using any of the typical future methods, like {@link ListenableFuture#addListener(Runnable, Executor)}), the
    +048 * underlying publisher may not publish more than one value. This is to prevent dropping intermediate values on the
    +049 * floor, silently, before dispatching the future's callbacks, which generally only accept one value. Other than this,
    +050 * things should work "as expected" whether you're looking at them from a Guava, JDK, or Reactive perspective.</p>
    +051 *
    +052 * @see Publisher Reactive Java type adapted by this object.
    +053 * @see ListenableFuture Guava's extension of the JDK's basic {@link Future}, which adds listener support.
    +054 * @see ApiFuture Lightweight Guava-like future meant to avoid dependencies on Java in API libraries.
    +055 * @see #wrap(Publisher) To wrap a {@link Publisher}.
    +056 * @see #wrap(ListenableFuture, Executor) To wrap a {@link ListenableFuture}.
    +057 * @see #wrap(ApiFuture, Executor) To wrap an {@link ApiFuture}.
    +058 */
    +059@Immutable
    +060@ThreadSafe
    +061@SuppressWarnings("UnstableApiUsage")
    +062public final class ReactiveFuture<R> implements Publisher<R>, ListenableFuture<R>, ApiFuture<R> {
    +063  /** Inner future, if one is set. Otherwise {@link Optional#empty()}. */
    +064  private final @Nonnull Optional<ListenableFuture<R>> future;
    +065
    +066  /** If a `publisher` is present, this object adapts it to a `future`. */
    +067  private final @Nullable PublisherListenableFuture<R> publisherAdapter;
    +068
    +069  /** If a `future` is present, this object adapts it to a `publisher`. */
    +070  private final @Nullable ListenableFuturePublisher<R> futureAdapter;
    +071
    +072  /** If a `future` is present, this object adapts it to a `publisher`. */
    +073  private final @Nullable CompletableFuturePublisher<R> javaFutureAdapter;
    +074
    +075  /**
    +076   * Spawn a reactive/future adapter in a reactive context, from a {@link Publisher}. Constructing a reactive future in
    +077   * this manner causes the object to operate in a "publisher-backed" mode.
    +078   *
    +079   * @param publisher Publisher to work with.
    +080   */
    +081  private ReactiveFuture(@Nonnull Publisher<R> publisher) {
    +082    this.future = Optional.empty();
    +083    this.futureAdapter = null;
    +084    this.publisherAdapter = new PublisherListenableFuture<>(publisher);
    +085    this.javaFutureAdapter = null;
    +086  }
    +087
    +088  /**
    +089   * Spawn a reactive/future adapter in a future context, from a {@link ListenableFuture}. Constructing a reactive
    +090   * future in this manner causes the object to operate in a "future-backed" mode.
    +091   *
    +092   * @param future Future to work with.
    +093   * @param executor Executor to use when running callbacks.
    +094   */
    +095  private ReactiveFuture(@Nonnull ListenableFuture<R> future, @Nonnull Executor executor) {
    +096    this.future = Optional.of(future);
    +097    this.futureAdapter = new ListenableFuturePublisher<>(future, executor);
    +098    this.publisherAdapter = null;
    +099    this.javaFutureAdapter = null;
    +100  }
    +101
    +102  /**
    +103   * Spawn a reactive/future adapter in a future context, from a {@link CompletableFuture}. Constructing a reactive
    +104   * future in this manner causes the object to operate in a "future-backed" mode.
    +105   *
    +106   * @param future Future to work with.
    +107   * @param executor Executor to use when running callbacks.
    +108   */
    +109  private ReactiveFuture(@Nonnull CompletableFuture<R> future, @Nonnull Executor executor) {
    +110    this.future = Optional.empty();
    +111    this.futureAdapter = null;
    +112    this.publisherAdapter = null;
    +113    this.javaFutureAdapter = new CompletableFuturePublisher<>(future, executor);
    +114  }
    +115
    +116  /** @return Internal future representation. */
    +117  private @Nonnull ListenableFuture<R> resolveFuture() {
    +118    if (this.publisherAdapter != null)
    +119      return this.publisherAdapter;
    +120    else if (this.javaFutureAdapter != null)
    +121      return this.javaFutureAdapter;
    +122    //noinspection OptionalGetWithoutIsPresent
    +123    return this.future.get();
    +124  }
    +125
    +126  /** @return Internal publisher representation. */
    +127  private @Nonnull Publisher<R> resolvePublisher() {
    +128    if (this.futureAdapter != null)
    +129      return this.futureAdapter;
    +130    else if (this.javaFutureAdapter != null)
    +131      return this.javaFutureAdapter;
    +132    return Objects.requireNonNull(this.publisherAdapter);
    +133  }
    +134
    +135  // -- Public API -- //
    +136  /**
    +137   * Wrap a Reactive Java {@link Publisher} in a universal {@link ReactiveFuture}, such that it may be used with any
    +138   * interface requiring a supported async or future value.
    +139   *
    +140   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +141   * class docs for more information.</p>
    +142   *
    +143   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +144   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +145   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +146   *
    +147   * @see #wrap(ListenableFuture, Executor) Wraps a {@link ListenableFuture} from Guava.
    +148   * @param publisher Reactive publisher to wrap.
    +149   * @param <R> Return or emission type of the publisher.
    +150   * @return Wrapped reactive future object.
    +151   * @throws IllegalArgumentException If the passed `publisher` is `null`.
    +152   */
    +153  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull Publisher<R> publisher) {
    +154    //noinspection ConstantConditions
    +155    if (publisher == null) throw new IllegalArgumentException("Cannot wrap `null` publisher.");
    +156    return new ReactiveFuture<>(publisher);
    +157  }
    +158
    +159  /**
    +160   * Wrap a regular Java {@link CompletableFuture} in a universal {@link ReactiveFuture}, such that it may be used with
    +161   * any interface requiring support for that class.
    +162   *
    +163   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +164   * class docs for more information.</p>
    +165   *
    +166   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +167   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +168   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +169   *
    +170   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +171   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +172   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +173   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +174   *
    +175   * @param future Completable future to wrap.
    +176   * @param <R> Return or emission type of the future.
    +177   * @return Wrapped reactive future object.
    +178   */
    +179  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull CompletableFuture<R> future) {
    +180    //noinspection ConstantConditions
    +181    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` publisher.");
    +182    return wrap(future, MoreExecutors.directExecutor());
    +183  }
    +184
    +185  /**
    +186   * Wrap a regular Java {@link CompletableFuture} in a universal {@link ReactiveFuture}, such that it may be used with
    +187   * any interface requiring support for that class.
    +188   *
    +189   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +190   * class docs for more information.</p>
    +191   *
    +192   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +193   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +194   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +195   *
    +196   * @param future Completable future to wrap.
    +197   * @param executor Executor to use.
    +198   * @param <R> Return or emission type of the future.
    +199   * @return Wrapped reactive future object.
    +200   */
    +201  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull CompletableFuture<R> future, @Nonnull Executor executor) {
    +202    //noinspection ConstantConditions
    +203    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` future.");
    +204    //noinspection ConstantConditions
    +205    if (executor == null) throw new IllegalArgumentException("Cannot wrap future with `null` executor.");
    +206    return new ReactiveFuture<>(future, executor);
    +207  }
    +208
    +209  /**
    +210   * Wrap a Guava {@link ListenableFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +211   * interface requiring a supported async or future value.
    +212   *
    +213   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +214   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +215   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +216   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +217   *
    +218   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +219   * class docs for more information.</p>
    +220   *
    +221   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +222   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +223   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +224   *
    +225   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +226   * @param future Future value to wrap.
    +227   * @param <R> Return value type for the future.
    +228   * @return Wrapped reactive future object.
    +229   * @throws IllegalArgumentException If the passed `future` is `null`.
    +230   */
    +231  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ListenableFuture<R> future) {
    +232    return wrap(future, MoreExecutors.directExecutor());
    +233  }
    +234
    +235  /**
    +236   * Wrap a Guava {@link ListenableFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +237   * interface requiring a supported async or future value.
    +238   *
    +239   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +240   * class docs for more information.</p>
    +241   *
    +242   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +243   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +244   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +245   *
    +246   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +247   * @param future Future value to wrap.
    +248   * @param executor Executor to dispatch callbacks with.
    +249   * @param <R> Return value type for the future.
    +250   * @return Wrapped reactive future object.
    +251   * @throws IllegalArgumentException If the passed `future` is `null`.
    +252   */
    +253  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ListenableFuture<R> future, @Nonnull Executor executor) {
    +254    //noinspection ConstantConditions
    +255    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` future.");
    +256    //noinspection ConstantConditions
    +257    if (executor == null) throw new IllegalArgumentException("Cannot wrap future with `null` executor.");
    +258    return new ReactiveFuture<>(future, executor);
    +259  }
    +260
    +261  /**
    +262   * Wrap a Google APIs {@link ApiFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +263   * interface requiring a supported async or future value.
    +264   *
    +265   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +266   * class docs for more information.</p>
    +267   *
    +268   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +269   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +270   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +271   *
    +272   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +273   * @see #wrap(ListenableFuture, Executor) Wraps a regular Guava {@link ListenableFuture}.
    +274   * @param apiFuture API future to wrap.
    +275   * @param executor Executor to run callbacks with.
    +276   * @param <R> Return value type for the future.
    +277   * @return Wrapped reactive future object.
    +278   * @throws IllegalArgumentException If the passed `apiFuture` is `null`.
    +279   */
    +280  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ApiFuture<R> apiFuture, @Nonnull Executor executor) {
    +281    //noinspection ConstantConditions
    +282    if (apiFuture == null) throw new IllegalArgumentException("Cannot wrap `null` API future.");
    +283    return wrap(new ApiFutureToListenableFuture<>(apiFuture), executor);
    +284  }
    +285
    +286  /**
    +287   * Wrap a Google APIs {@link ApiFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +288   * interface requiring a supported async or future value.
    +289   *
    +290   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +291   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +292   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +293   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +294   *
    +295   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +296   * class docs for more information.</p>
    +297   *
    +298   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +299   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +300   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +301   *
    +302   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +303   * @see #wrap(ListenableFuture, Executor) Wraps a regular Guava {@link ListenableFuture}.
    +304   * @param apiFuture API future to wrap.
    +305   * @param <R> Return value type for the future.
    +306   * @return Wrapped reactive future object.
    +307   * @throws IllegalArgumentException If the passed `apiFuture` is `null`.
    +308   */
    +309  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ApiFuture<R> apiFuture) {
    +310    return wrap(apiFuture, MoreExecutors.directExecutor());
    +311  }
    +312
    +313  /**
    +314   * Create an already-resolved future, wrapping the provided value. The future will present as done as soon as it is
    +315   * returned from this method.
    +316   *
    +317   * <p>Under the hood, this is simply a {@link ReactiveFuture} wrapping a call to
    +318   * {@link Futures#immediateFuture(Object)}.</p>
    +319   *
    +320   * @param value Value to wrap in an already-completed future.
    +321   * @param <R> Return value generic type.
    +322   * @return Reactive future wrapping a finished value.
    +323   */
    +324  public static @Nonnull <R> ReactiveFuture<R> done(@Nonnull R value) {
    +325    return wrap(Futures.immediateFuture(value));
    +326  }
    +327
    +328  /**
    +329   * Create an already-failed future, wrapping the provided exception instance. The future will present as one as soon
    +330   * as it is returned from this method.
    +331   *
    +332   * <p>Calling {@link Future#get(long, TimeUnit)} or {@link Future#get()} on a failed future will surface the
    +333   * associated exception where invocation occurs. Under the hood, this is simply a {@link ReactiveFuture} wrapping a
    +334   * call to {@link Futures#immediateFailedFuture(Throwable)}.</p>
    +335   *
    +336   * @param error Error to wrap in an already-failed future.
    +337   * @param <R> Return value generic type.
    +338   * @return Reactive future wrapping a finished value.
    +339   */
    +340  public static @Nonnull <R> ReactiveFuture<R> failed(@Nonnull Throwable error) {
    +341    return wrap(Futures.immediateFailedFuture(error));
    +342  }
    +343
    +344  /**
    +345   * Create an already-cancelled future. The future will present as both done and cancelled as soon as it is returned
    +346   * from this method.
    +347   *
    +348   * <p>Under the hood, this is simply a {@link ReactiveFuture} wrapping a call to
    +349   * {@link Futures#immediateCancelledFuture()}.</p>
    +350   *
    +351   * @param <R> Return value generic type.
    +352   * @return Reactive future wrapping a cancelled operation.
    +353   */
    +354  public static @Nonnull <R> ReactiveFuture<R> cancelled() {
    +355    return wrap(Futures.immediateCancelledFuture());
    +356  }
    +357
    +358  // -- Compliance: Publisher -- //
    +359  /**
    +360   * Request {@link Publisher} to start streaming data.
    +361   *
    +362   * <p>This is a "factory method" and can be called multiple times, each time starting a new {@link Subscription}. Each
    +363   * {@link Subscription} will work for only a single {@link Subscriber}. A {@link Subscriber} should only subscribe
    +364   * once to a single {@link Publisher}. If the {@link Publisher} rejects the subscription attempt or otherwise fails it
    +365   * will signal the error via {@link Subscriber#onError}.</p>
    +366   *
    +367   * @param subscriber the {@link Subscriber} that will consume signals from this {@link Publisher}.
    +368   */
    +369  @Override
    +370  public void subscribe(Subscriber<? super R> subscriber) {
    +371    resolvePublisher().subscribe(subscriber);
    +372  }
    +373
    +374  // -- Compliance: Listenable Future -- //
    +375  /**
    +376   * Registers a listener to be {@linkplain Executor#execute(Runnable) run} on the given executor. The listener will run
    +377   * when the {@code Future}'s computation is {@linkplain Future#isDone() complete} or, if the computation is already
    +378   * complete, immediately.
    +379   *
    +380   * <p>There is no guaranteed ordering of execution of listeners, but any listener added through this method is
    +381   * guaranteed to be called once the computation is complete.</p>
    +382   *
    +383   * <p>Exceptions thrown by a listener will be propagated up to the executor. Any exception thrown during
    +384   * {@code Executor.execute} (e.g., a {@code RejectedExecutionException} or an exception thrown by
    +385   * {@linkplain MoreExecutors#directExecutor direct execution}) will be caught and logged.</p>
    +386   *
    +387   * <p>Note: For fast, lightweight listeners that would be safe to execute in any thread, consider
    +388   * {@link MoreExecutors#directExecutor}. Otherwise, avoid it. Heavyweight {@code directExecutor} listeners can cause
    +389   * problems, and these problems can be difficult to reproduce because they depend on timing. For example:</p>
    +390   * <ul>
    +391   *   <li>The listener may be executed by the caller of {@code addListener}. That caller may be a
    +392   *       UI thread or other latency-sensitive thread. This can harm UI responsiveness.
    +393   *   <li>The listener may be executed by the thread that completes this {@code Future}. That
    +394   *       thread may be an internal system thread such as an RPC network thread. Blocking that
    +395   *       thread may stall progress of the whole system. It may even cause a deadlock.
    +396   *   <li>The listener may delay other listeners, even listeners that are not themselves {@code
    +397   *       directExecutor} listeners.
    +398   * </ul>
    +399   *
    +400   * <p>This is the most general listener interface. For common operations performed using listeners, see
    +401   * {@link Futures}. For a simplified but general listener interface, see
    +402   * {@link Futures#addCallback addCallback()}.</p>
    +403   *
    +404   * <p>Memory consistency effects: Actions in a thread prior to adding a listener <a
    +405   * href="https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5"><i>happen-before</i></a> its
    +406   * execution begins, perhaps in another thread.</p>
    +407   *
    +408   * <p>Guava implementations of {@code ListenableFuture} promptly release references to listeners after executing
    +409   * them.</p>
    +410   *
    +411   * @param listener the listener to run when the computation is complete.
    +412   * @param executor the executor to run the listener in
    +413   * @throws RejectedExecutionException if we tried to execute the listener immediately but the executor rejecte it.
    +414   */
    +415  @Override
    +416  public void addListener(@Nonnull Runnable listener, @Nonnull Executor executor) throws RejectedExecutionException {
    +417    resolveFuture().addListener(listener, executor);
    +418  }
    +419
    +420  /**
    +421   * Attempts to cancel execution of this task.  This attempt will fail if the task has already completed, has already
    +422   * been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when
    +423   * {@code cancel} is called, this task should never run.  If the task has already started, then the
    +424   * {@code mayInterruptIfRunning} parameter determines whether the thread executing this task should be interrupted in
    +425   * an attempt to stop the task.
    +426   *
    +427   * <p>After this method returns, subsequent calls to {@link #isDone} will always return {@code true}.  Subsequent
    +428   * calls to {@link #isCancelled} will always return {@code true} if this method returned {@code true}.
    +429   *
    +430   * @param mayInterruptIfRunning {@code true} if the thread executing this task should be interrupted; otherwise,
    +431   *                              in-progress tasks are allowed to complete
    +432   * @return {@code false} if the task could not be cancelled, typically because it has already completed normally;
    +433   *         {@code true} otherwise.
    +434   */
    +435  @Override
    +436  public boolean cancel(boolean mayInterruptIfRunning) {
    +437    return resolveFuture().cancel(mayInterruptIfRunning);
    +438  }
    +439
    +440  /**
    +441   * Returns {@code true} if this task was cancelled before it completed normally. This defers to the underlying future,
    +442   * or a wrapped object if using a {@link Publisher}.
    +443   *
    +444   * @return {@code true} if this task was cancelled before it completed
    +445   */
    +446  @Override
    +447  public boolean isCancelled() {
    +448    return resolveFuture().isCancelled();
    +449  }
    +450
    +451  /**
    +452   * Returns {@code true} if this task completed. This defers to the underlying future, or a wrapped object if using a
    +453   * Reactive Java {@link Publisher}.
    +454   *
    +455   * Completion may be due to normal termination, an exception, or cancellation -- in all of these cases, this method
    +456   * will return {@code true}.
    +457   *
    +458   * @return {@code true} if this task completed.
    +459   */
    +460  @Override
    +461  public boolean isDone() {
    +462    return resolveFuture().isDone();
    +463  }
    +464
    +465  /**
    +466   * Waits if necessary for the computation to complete, and then retrieves its result.
    +467   *
    +468   * <p>It is generally recommended to use the variant of this method which specifies a timeout - one must handle the
    +469   * additional {@link TimeoutException}, but on the other hand the computation can never infinitely block if an async
    +470   * value does not materialize.</p>
    +471   *
    +472   * @see #get(long, TimeUnit) For a safer version of this method, which allows specifying a timeout.
    +473   * @return the computed result.
    +474   * @throws CancellationException if the computation was cancelled
    +475   * @throws ExecutionException    if the computation threw an exception
    +476   * @throws InterruptedException  if the current thread was interrupted while waiting
    +477   */
    +478  @Override
    +479  public R get() throws InterruptedException, ExecutionException {
    +480    return resolveFuture().get();
    +481  }
    +482
    +483  /**
    +484   * Waits if necessary for at most the given time for the computation to complete, and then retrieves its result, if
    +485   * available.
    +486   *
    +487   * @param timeout the maximum time to wait
    +488   * @param unit    the time unit of the timeout argument
    +489   * @return the computed result
    +490   * @throws CancellationException if the computation was cancelled
    +491   * @throws ExecutionException    if the computation threw an exception
    +492   * @throws InterruptedException  if the current thread was interrupted while waiting
    +493   * @throws TimeoutException      if the wait timed out
    +494   */
    +495  @Override
    +496  public R get(long timeout, @Nonnull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
    +497    return resolveFuture().get(timeout, unit);
    +498  }
    +499
    +500  /**
    +501   * Structure that adapts a {@link Publisher} to a {@link ListenableFuture} interface. We accomplish this by
    +502   * immediately subscribing to the publisher with a callback that dispatches a {@link SettableFuture}.
    +503   *
    +504   * <p>This object is used in the specific circumstance of wrapping a {@link Publisher}, and then using the wrapped
    +505   * object as a {@link ListenableFuture} (or any descendent or compliant implementation thereof).</p>
    +506   *
    +507   * @param <T> Generic type returned by the future.
    +508   */
    +509  @Immutable
    +510  @ThreadSafe
    +511  public final static class PublisherListenableFuture<T> implements ListenableFuture<T>, Publisher<T> {
    +512    /** Whether we have received a value. */
    +513    private final @Nonnull AtomicBoolean received = new AtomicBoolean(false);
    +514
    +515    /** Whether we have completed acquiring a value. */
    +516    private final @Nonnull AtomicBoolean completed = new AtomicBoolean(false);
    +517
    +518    /** Whether we have been cancelled. */
    +519    private final @Nonnull AtomicBoolean cancelled = new AtomicBoolean(false);
    +520
    +521    /** Describes the list of proxied subscribers. */
    +522    private final @Nonnull Map<String, Subscriber<? super T>> subscribers = new ConcurrentHashMap<>();
    +523
    +524    /** Converted/pass-through future value. */
    +525    private final @Nonnull SettableFuture<T> future;
    +526
    +527    /** Subscription, so we can propagate cancellation. */
    +528    private volatile Subscription subscription;
    +529
    +530    /**
    +531     * Private constructor.
    +532     *
    +533     * @param publisher Publisher to wrap.
    +534     */
    +535    private PublisherListenableFuture(@Nonnull Publisher<T> publisher) {
    +536      this.future = SettableFuture.create();
    +537      publisher.subscribe(new Subscriber<T>() {
    +538        @Override
    +539        public void onSubscribe(Subscription s) {
    +540          PublisherListenableFuture.this.subscription = s;
    +541        }
    +542
    +543        @Override
    +544        public void onNext(T t) {
    +545          if (received.compareAndSet(false, true)) {
    +546            PublisherListenableFuture.this.proxyExecute((sub) -> sub.onNext(t));
    +547            future.set(t);
    +548            return;
    +549          }
    +550          this.onError(new IllegalStateException(
    +551            "Cannot publish multiple items through `ReactiveFuture`."));
    +552        }
    +553
    +554        @Override
    +555        public void onError(Throwable t) {
    +556          if (!completed.get()) {
    +557            PublisherListenableFuture.this.proxyExecute((sub) -> sub.onError(t));
    +558            future.setException(t);
    +559          }
    +560        }
    +561
    +562        @Override
    +563        public void onComplete() {
    +564          if (completed.compareAndSet(false, true)) {
    +565            PublisherListenableFuture.this.proxyExecute(Subscriber::onComplete);
    +566            PublisherListenableFuture.this.clear();
    +567          }
    +568        }
    +569      });
    +570    }
    +571
    +572    /**
    +573     * Call something on each proxied publisher subscription, if any.
    +574     *
    +575     * @param operation Operation to execute. Called for each subscriber.
    +576     */
    +577    private void proxyExecute(@Nonnull Consumer<Subscriber<? super T>> operation) {
    +578      if (!this.subscribers.isEmpty()) {
    +579        this.subscribers.values().forEach(operation);
    +580      }
    +581    }
    +582
    +583    /**
    +584     * Remove all subscribers and clear references to futures/publishers/listeners.
    +585     */
    +586    private void clear() {
    +587      this.subscribers.clear();
    +588      this.subscription = null;
    +589    }
    +590
    +591    /**
    +592     * Drop a subscription (after proxied {@link Subscription#cancel()} is called).
    +593     *
    +594     * @param id ID of the subscription to drop.
    +595     */
    +596    private void dropSubscription(@Nonnull String id) {
    +597      this.subscribers.get(id).onComplete();
    +598      this.subscribers.remove(id);
    +599    }
    +600
    +601    // -- Interface Compliance: Publisher -- //
    +602
    +603    @Override
    +604    public void subscribe(Subscriber<? super T> s) {
    +605      final String id = String.valueOf(this.subscribers.size());
    +606      Subscription sub = new Subscription() {
    +607        @Override
    +608        public void request(long n) {
    +609          PublisherListenableFuture.this.subscription.request(n);
    +610        }
    +611
    +612        @Override
    +613        public void cancel() {
    +614          // kill self
    +615          PublisherListenableFuture.this.dropSubscription(id);
    +616        }
    +617      };
    +618      this.subscribers.put(id, s);
    +619      s.onSubscribe(sub);
    +620    }
    +621
    +622    // -- Interface Compliance: Listenable Future -- //
    +623
    +624    @Override
    +625    public void addListener(@Nonnull Runnable runnable, @Nonnull Executor executor) {
    +626      this.future.addListener(runnable, executor);
    +627    }
    +628
    +629    @Override
    +630    public boolean cancel(boolean mayInterruptIfRunning) {
    +631      boolean cancelled = false;
    +632      if (!this.completed.get() && this.cancelled.compareAndSet(false, true)) {
    +633        this.proxyExecute(Subscriber::onComplete);  // dispatch `onComplete` for any subscribers
    +634        this.subscription.cancel();  // cancel upwards
    +635        cancelled = this.future.cancel(mayInterruptIfRunning);  // cancel future
    +636        this.clear();  // clear references
    +637      }
    +638      return cancelled;
    +639    }
    +640
    +641    @Override
    +642    public boolean isCancelled() {
    +643      return this.cancelled.get();
    +644    }
    +645
    +646    @Override
    +647    public boolean isDone() {
    +648      return this.completed.get() || this.cancelled.get();
    +649    }
    +650
    +651    @Override
    +652    public T get() throws InterruptedException, ExecutionException {
    +653      return this.future.get();
    +654    }
    +655
    +656    @Override
    +657    public T get(long timeout, @Nonnull TimeUnit unit)
    +658        throws InterruptedException, ExecutionException, TimeoutException {
    +659      return this.future.get(timeout, unit);
    +660    }
    +661  }
    +662
    +663  /**
    +664   * Structure that adapts Java's {@link CompletableFuture} to a Reactive Java {@link Publisher}, which publishes one
    +665   * item - either the result of the computation, or an error.
    +666   *
    +667   * <p>This object is used in the specific circumstance that a {@link CompletableFuture} is wrapped by a
    +668   * {@link ReactiveFuture}, and then used within the Reactive Java or Guava ecosystems as a {@link Publisher} or a
    +669   * {@link ListenableFuture} (or {@link ApiFuture}), or a descendent thereof. As in {@link ListenableFuturePublisher},
    +670   * we simply set the callback for the future value, upon item-request (one cycle is allowed), and propagate any events
    +671   * received to the publisher.</p>
    +672   *
    +673   * @param <T> Emit type for this adapter. Matches the future it wraps.
    +674   */
    +675  public final static class CompletableFuturePublisher<T>
    +676      implements Publisher<T>, ListenableFuture<T>, CompletionStage<T> {
    +677    private final @Nonnull CompletableFuture<T> future;
    +678    private final @Nonnull CompletionStage<T> stage;
    +679    private final @Nonnull Executor callbackExecutor;
    +680
    +681    /**
    +682     * Construct an adapter that propagates signals from a {@link CompletableFuture} to a {@link Publisher}.
    +683     *
    +684     * @param future Completable future to wrap.
    +685     * @param callbackExecutor Callback executor to use.
    +686     */
    +687    private CompletableFuturePublisher(@Nonnull CompletableFuture<T> future,
    +688                                       @Nonnull Executor callbackExecutor) {
    +689      this.future = future;
    +690      this.stage = future;
    +691      this.callbackExecutor = callbackExecutor;
    +692    }
    +693
    +694    /* == `Future`/`ListenableFuture` Interface Compliance == */
    +695
    +696    /** @inheritDoc */
    +697    @Override public final void subscribe(Subscriber<? super T> subscriber) {
    +698      Objects.requireNonNull(subscriber, "Subscriber cannot be null");
    +699      subscriber.onSubscribe(new CompletableFutureSubscription(this.future, subscriber, this.callbackExecutor));
    +700    }
    +701
    +702    /** @inheritDoc */
    +703    @Override public void addListener(Runnable runnable, Executor executor) {
    +704      future.thenRunAsync(runnable, executor);
    +705    }
    +706
    +707    /** @inheritDoc */
    +708    @Override public boolean cancel(boolean mayInterruptIfRunning) {
    +709      return future.cancel(mayInterruptIfRunning);
    +710    }
    +711
    +712    /** @inheritDoc */
    +713    @Override public boolean isCancelled() {
    +714      return future.isCancelled();
    +715    }
    +716
    +717    /** @inheritDoc */
    +718    @Override public boolean isDone() {
    +719      return future.isDone();
    +720    }
    +721
    +722    /** @inheritDoc */
    +723    @Override public T get() throws InterruptedException, ExecutionException {
    +724      return future.get();
    +725    }
    +726
    +727    /** @inheritDoc */
    +728    @Override
    +729    public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
    +730      return future.get(timeout, unit);
    +731    }
    +732
    +733    /* == `CompletionStage` Interface Compliance == */
    +734
    +735    /** @inheritDoc */
    +736    @Override public <U> CompletionStage<U> thenApply(Function<? super T, ? extends U> fn) {
    +737      return stage.thenApply(fn);
    +738    }
    +739
    +740    /** @inheritDoc */
    +741    @Override public <U> CompletionStage<U> thenApplyAsync(Function<? super T, ? extends U> fn) {
    +742      return stage.thenApplyAsync(fn);
    +743    }
    +744
    +745    /** @inheritDoc */
    +746    @Override public <U> CompletionStage<U> thenApplyAsync(Function<? super T, ? extends U> fn, Executor executor) {
    +747      return stage.thenApplyAsync(fn, executor);
    +748    }
    +749
    +750    /** @inheritDoc */
    +751    @Override public CompletionStage<Void> thenAccept(Consumer<? super T> action) {
    +752      return stage.thenAccept(action);
    +753    }
    +754
    +755    /** @inheritDoc */
    +756    @Override public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action) {
    +757      return stage.thenAcceptAsync(action);
    +758    }
    +759
    +760    /** @inheritDoc */
    +761    @Override public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor) {
    +762      return stage.thenAcceptAsync(action, executor);
    +763    }
    +764
    +765    /** @inheritDoc */
    +766    @Override public CompletionStage<Void> thenRun(Runnable action) {
    +767      return stage.thenRun(action);
    +768    }
    +769
    +770    /** @inheritDoc */
    +771    @Override public CompletionStage<Void> thenRunAsync(Runnable action) {
    +772      return stage.thenRunAsync(action);
    +773    }
    +774
    +775    /** @inheritDoc */
    +776    @Override public CompletionStage<Void> thenRunAsync(Runnable action, Executor executor) {
    +777      return stage.thenRunAsync(action, executor);
    +778    }
    +779
    +780    /** @inheritDoc */
    +781    @Override public <U, V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,
    +782                                                           BiFunction<? super T, ? super U, ? extends V> fn) {
    +783      return stage.thenCombine(other, fn);
    +784    }
    +785
    +786    /** @inheritDoc */
    +787    @Override public <U, V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,
    +788                                                                BiFunction<? super T, ? super U, ? extends V> fn) {
    +789      return stage.thenCombineAsync(other, fn);
    +790    }
    +791
    +792    /** @inheritDoc */
    +793    @Override public <U, V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,
    +794                                                                BiFunction<? super T, ? super U, ? extends V> fn,
    +795                                                                Executor executor) {
    +796      return stage.thenCombineAsync(other, fn, executor);
    +797    }
    +798
    +799    /** @inheritDoc */
    +800    @Override public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,
    +801                                                              BiConsumer<? super T, ? super U> action) {
    +802      return stage.thenAcceptBoth(other, action);
    +803    }
    +804
    +805    /** @inheritDoc */
    +806    @Override public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
    +807                                                                   BiConsumer<? super T, ? super U> action) {
    +808      return stage.thenAcceptBothAsync(other, action);
    +809    }
    +810
    +811    /** @inheritDoc */
    +812    @Override
    +813    public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
    +814                                                         BiConsumer<? super T, ? super U> action,
    +815                                                         Executor executor) {
    +816      return stage.thenAcceptBothAsync(other, action, executor);
    +817    }
    +818
    +819    /** @inheritDoc */
    +820    @Override
    +821    public CompletionStage<Void> runAfterBoth(CompletionStage<?> other, Runnable action) {
    +822      return stage.runAfterBoth(other, action);
    +823    }
    +824
    +825    /** @inheritDoc */
    +826    @Override
    +827    public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action) {
    +828      return stage.runAfterBothAsync(other, action);
    +829    }
    +830
    +831    /** @inheritDoc */
    +832    @Override
    +833    public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action, Executor executor) {
    +834      return stage.runAfterBothAsync(other, action, executor);
    +835    }
    +836
    +837    /** @inheritDoc */
    +838    @Override
    +839    public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn) {
    +840      return stage.applyToEither(other, fn);
    +841    }
    +842
    +843    /** @inheritDoc */
    +844    @Override
    +845    public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn) {
    +846      return stage.applyToEitherAsync(other, fn);
    +847    }
    +848
    +849    /** @inheritDoc */
    +850    @Override
    +851    public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,
    +852                                                     Function<? super T, U> fn,
    +853                                                     Executor executor) {
    +854      return stage.applyToEitherAsync(other, fn, executor);
    +855    }
    +856
    +857    /** @inheritDoc */
    +858    @Override public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,
    +859                                                        Consumer<? super T> action) {
    +860      return stage.acceptEither(other, action);
    +861    }
    +862
    +863    /** @inheritDoc */
    +864    @Override public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,
    +865                                                             Consumer<? super T> action) {
    +866      return stage.acceptEitherAsync(other, action);
    +867    }
    +868
    +869    /** @inheritDoc */
    +870    @Override public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,
    +871                                                             Consumer<? super T> action,
    +872                                                             Executor executor) {
    +873      return stage.acceptEitherAsync(other, action, executor);
    +874    }
    +875
    +876    /** @inheritDoc */
    +877    @Override public CompletionStage<Void> runAfterEither(CompletionStage<?> other, Runnable action) {
    +878      return stage.runAfterEither(other, action);
    +879    }
    +880
    +881    /** @inheritDoc */
    +882    @Override public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action) {
    +883      return stage.runAfterEitherAsync(other, action);
    +884    }
    +885
    +886    /** @inheritDoc */
    +887    @Override public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action, Executor executor) {
    +888      return stage.runAfterEitherAsync(other, action, executor);
    +889    }
    +890
    +891    /** @inheritDoc */
    +892    @Override public <U> CompletionStage<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) {
    +893      return stage.thenCompose(fn);
    +894    }
    +895
    +896    /** @inheritDoc */
    +897    @Override public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) {
    +898      return stage.thenComposeAsync(fn);
    +899    }
    +900
    +901    /** @inheritDoc */
    +902    @Override public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn,
    +903                                                             Executor executor) {
    +904      return stage.thenComposeAsync(fn, executor);
    +905    }
    +906
    +907    /** @inheritDoc */
    +908    @Override public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) {
    +909      return stage.handle(fn);
    +910    }
    +911
    +912    /** @inheritDoc */
    +913    @Override public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) {
    +914      return stage.handleAsync(fn);
    +915    }
    +916
    +917    /** @inheritDoc */
    +918    @Override public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,
    +919                                                        Executor executor) {
    +920      return stage.handleAsync(fn, executor);
    +921    }
    +922
    +923    /** @inheritDoc */
    +924    @Override public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> action) {
    +925      return stage.whenComplete(action);
    +926    }
    +927
    +928    /** @inheritDoc */
    +929    @Override public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action) {
    +930      return stage.whenCompleteAsync(action);
    +931    }
    +932
    +933    /** @inheritDoc */
    +934    @Override public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action,
    +935                                                          Executor executor) {
    +936      return stage.whenCompleteAsync(action, executor);
    +937    }
    +938
    +939    /** @inheritDoc */
    +940    @Override public CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn) {
    +941      return stage.exceptionally(fn);
    +942    }
    +943
    +944    /** @inheritDoc */
    +945    @Override public CompletableFuture<T> toCompletableFuture() {
    +946      return stage.toCompletableFuture();
    +947    }
    +948
    +949    /**
    +950     * Models a Reactive Java {@link Subscription}, which is responsible for propagating events from a
    +951     * Concurrent Java {@link CompletableFuture} to a {@link Subscriber}.
    +952     *
    +953     * <p>This object is generally used internally by the {@link CompletableFuturePublisher}, once a {@link Subscriber}
    +954     * attaches itself to a {@link Publisher} that is actually a wrapped {@link CompletableFuture}. Error (exception)
    +955     * events and value events are both propagated. Subscribers based on this wrapping will only ever receive a maximum
    +956     * of <b>one value</b> or <b>one error</b>.</p>
    +957     */
    +958    @Immutable
    +959    @ThreadSafe
    +960    public final class CompletableFutureSubscription implements Subscription {
    +961      private final AtomicBoolean completed = new AtomicBoolean(false);
    +962      private final @Nonnull Subscriber<? super T> subscriber;
    +963      private final @Nonnull CompletableFuture<T> future;
    +964      private final @Nonnull Executor executor;
    +965
    +966      /**
    +967       * Private constructor, meant for use by {@link CompletableFuturePublisher} only.
    +968       *
    +969       * @param future Future value to adapt.
    +970       * @param subscriber The subscriber.
    +971       * @param executor Executor to run callbacks with.
    +972       */
    +973      CompletableFutureSubscription(@Nonnull CompletableFuture<T> future,
    +974                                    @Nonnull Subscriber<? super T> subscriber,
    +975                                    @Nonnull Executor executor) {
    +976        this.future = Objects.requireNonNull(future);
    +977        this.subscriber = Objects.requireNonNull(subscriber);
    +978        this.executor = Objects.requireNonNull(executor);
    +979      }
    +980
    +981      /**
    +982       * Request the specified number of items from the underlying {@link Subscription}. This must <b>always be
    +983       * <pre>1</pre></b>.
    +984       *
    +985       * @param n Number of elements to request to the upstream (must always be <pre>1</pre>).
    +986       * @throws IllegalArgumentException If any value other than <pre>1</pre> is passed in.
    +987       */
    +988      public synchronized void request(long n) {
    +989        if (n == 1 && !completed.get()) {
    +990          try {
    +991            CompletableFuture<T> future = this.future;
    +992            future.thenAcceptAsync(t -> {
    +993              T val = null;
    +994              Throwable err = null;
    +995              try {
    +996                val = future.get();
    +997              } catch (Exception exc) {
    +998                err = exc;
    +999              }
    +1000
    +1001              if (completed.compareAndSet(false, true)) {
    +1002                if (err != null) {
    +1003                  subscriber.onError(err);
    +1004                } else {
    +1005                  if (val != null) {
    +1006                    subscriber.onNext(val);
    +1007                  }
    +1008                  subscriber.onComplete();
    +1009                }
    +1010              }
    +1011            }, executor);
    +1012
    +1013          } catch (Exception e) {
    +1014            subscriber.onError(e);
    +1015          }
    +1016        } else if (n != 1) {
    +1017          IllegalArgumentException ex = new IllegalArgumentException(
    +1018              "Cannot request more or less than 1 item from a ReactiveFuture-wrapped publisher.");
    +1019          subscriber.onError(ex);
    +1020        }
    +1021      }
    +1022
    +1023      /**
    +1024       * Request the publisher to stop sending data and clean up resources.
    +1025       */
    +1026      public synchronized void cancel() {
    +1027        if (completed.compareAndSet(false, true)) {
    +1028          subscriber.onComplete();
    +1029          future.cancel(false);
    +1030        }
    +1031      }
    +1032    }
    +1033  }
    +1034
    +1035  /**
    +1036   * Structure that adapts Guava's {@link ListenableFuture} to a Reactive Java {@link Publisher}, which publishes one
    +1037   * item - either the result of the computation, or an error.
    +1038   *
    +1039   * <p>This object is used in the specific circumstance that a {@link ListenableFuture} is wrapped by a
    +1040   * {@link ReactiveFuture}, and then used within the Reactive Java ecosystem as a {@link Publisher}. We simply set a
    +1041   * callback for the future value, upon item-request (one cycle is allowed), and propagate any events received to the
    +1042   * publisher.</p>
    +1043   *
    +1044   * @param <T> Emit type for this adapter. Matches the publisher it wraps.
    +1045   */
    +1046  public final static class ListenableFuturePublisher<T> implements Publisher<T> {
    +1047    private final @Nonnull ListenableFuture<T> future;
    +1048    private final @Nonnull Executor callbackExecutor;
    +1049
    +1050    /**
    +1051     * Wrap a {@link ListenableFuture}. Private constructor for use by {@link ReactiveFuture} only.
    +1052     *
    +1053     * @param future The future to convert or wait on.
    +1054     * @param callbackExecutor Executor to run the callback on.
    +1055     */
    +1056    private ListenableFuturePublisher(@Nonnull ListenableFuture<T> future,
    +1057                                      @Nonnull Executor callbackExecutor) {
    +1058      this.future = future;
    +1059      this.callbackExecutor = callbackExecutor;
    +1060    }
    +1061
    +1062    @Override
    +1063    public final void subscribe(Subscriber<? super T> subscriber) {
    +1064      Objects.requireNonNull(subscriber, "Subscriber cannot be null");
    +1065      subscriber.onSubscribe(new ListenableFutureSubscription(this.future, subscriber, this.callbackExecutor));
    +1066    }
    +1067
    +1068    /**
    +1069     * Models a Reactive Java {@link Subscription}, which is responsible for propagating events from a
    +1070     * {@link ListenableFuture} to a {@link Subscriber}.
    +1071     *
    +1072     * <p>This object is generally used internally by the {@link ListenableFuturePublisher}, once a {@link Subscriber}
    +1073     * attaches itself to a {@link Publisher} that is actually a wrapped {@link ListenableFuture}. Error (exception)
    +1074     * events and value events are both propagated. Subscribers based on this wrapping will only ever receive a maximum
    +1075     * of <b>one value</b> or <b>one error</b>.</p>
    +1076     */
    +1077    @Immutable
    +1078    @ThreadSafe
    +1079    public final class ListenableFutureSubscription implements Subscription {
    +1080      private final AtomicBoolean completed = new AtomicBoolean(false);
    +1081      private final @Nonnull Subscriber<? super T> subscriber;
    +1082      private final @Nonnull ListenableFuture<T> future; // to allow cancellation
    +1083      private final @Nonnull Executor executor;  // executor to use when dispatching the callback
    +1084
    +1085      /**
    +1086       * Private constructor, meant for use by {@link ListenableFuturePublisher} only.
    +1087       *
    +1088       * @param future Future value to adapt.
    +1089       * @param subscriber The subscriber.
    +1090       * @param executor Executor to run callbacks with.
    +1091       */
    +1092      ListenableFutureSubscription(@Nonnull ListenableFuture<T> future,
    +1093                                   @Nonnull Subscriber<? super T> subscriber,
    +1094                                   @Nonnull Executor executor) {
    +1095        this.future = Objects.requireNonNull(future);
    +1096        this.subscriber = Objects.requireNonNull(subscriber);
    +1097        this.executor = Objects.requireNonNull(executor);
    +1098      }
    +1099
    +1100      /**
    +1101       * Request the specified number of items from the underlying {@link Subscription}. This must <b>always be
    +1102       * <pre>1</pre></b>.
    +1103       *
    +1104       * @param n Number of elements to request to the upstream (must always be <pre>1</pre>).
    +1105       * @throws IllegalArgumentException If any value other than <pre>1</pre> is passed in.
    +1106       */
    +1107      public synchronized void request(long n) {
    +1108        if (n == 1 && !completed.get()) {
    +1109          try {
    +1110            ListenableFuture<T> future = this.future;
    +1111            future.addListener(() -> {
    +1112              T val = null;
    +1113              Throwable err = null;
    +1114              try {
    +1115                val = this.future.get();
    +1116              } catch (Exception exc) {
    +1117                err = exc;
    +1118              }
    +1119
    +1120              if (completed.compareAndSet(false, true)) {
    +1121                if (err != null) {
    +1122                  subscriber.onError(err);
    +1123                } else {
    +1124                  if (val != null) {
    +1125                    subscriber.onNext(val);
    +1126                  }
    +1127                  subscriber.onComplete();
    +1128                }
    +1129              }
    +1130            }, this.executor);
    +1131          } catch (Exception e) {
    +1132            subscriber.onError(e);
    +1133          }
    +1134        } else if (n != 1) {
    +1135          IllegalArgumentException ex = new IllegalArgumentException(
    +1136            "Cannot request more or less than 1 item from a ReactiveFuture-wrapped publisher.");
    +1137          subscriber.onError(ex);
    +1138        }
    +1139      }
    +1140
    +1141      /**
    +1142       * Request the publisher to stop sending data and clean up resources.
    +1143       */
    +1144      public synchronized void cancel() {
    +1145        if (completed.compareAndSet(false, true)) {
    +1146          subscriber.onComplete();
    +1147          future.cancel(false);
    +1148        }
    +1149      }
    +1150    }
    +1151  }
    +1152}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/runtime/ReactiveFuture.CompletableFuturePublisher.html b/docs/java/src-html/gust/backend/runtime/ReactiveFuture.CompletableFuturePublisher.html new file mode 100644 index 000000000..a365627a5 --- /dev/null +++ b/docs/java/src-html/gust/backend/runtime/ReactiveFuture.CompletableFuturePublisher.html @@ -0,0 +1,1226 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.runtime;
    +014
    +015import com.google.api.core.ApiFuture;
    +016import com.google.api.core.ApiFutureToListenableFuture;
    +017import com.google.common.util.concurrent.Futures;
    +018import com.google.common.util.concurrent.ListenableFuture;
    +019import com.google.common.util.concurrent.MoreExecutors;
    +020import com.google.common.util.concurrent.SettableFuture;
    +021import org.reactivestreams.Publisher;
    +022import org.reactivestreams.Subscriber;
    +023import org.reactivestreams.Subscription;
    +024
    +025import javax.annotation.Nonnull;
    +026import javax.annotation.Nullable;
    +027import javax.annotation.concurrent.Immutable;
    +028import javax.annotation.concurrent.ThreadSafe;
    +029import java.util.*;
    +030import java.util.concurrent.*;
    +031import java.util.concurrent.atomic.AtomicBoolean;
    +032import java.util.function.BiConsumer;
    +033import java.util.function.BiFunction;
    +034import java.util.function.Consumer;
    +035import java.util.function.Function;
    +036
    +037
    +038/**
    +039 * Adapts future/async value containers from different frameworks (namely, Reactive Java, Guava, and the JDK).
    +040 *
    +041 * <p>Create a new {@link ReactiveFuture} by using any of the {@code wrap)} factory methods. The resulting object is
    +042 * usable as a {@link Publisher}, {@link ListenableFuture}, or {@link ApiFuture}. This object simply wraps whatever
    +043 * inner object is provided, and as such instances are lightweight; there is no default functionality after immediate
    +044 * construction in most cases.</p>
    +045 *
    +046 * <p><b>Caveat:</b> when using a {@link Publisher} as a {@link ListenableFuture} (i.e. wrapping a {@link Publisher} and
    +047 * then using any of the typical future methods, like {@link ListenableFuture#addListener(Runnable, Executor)}), the
    +048 * underlying publisher may not publish more than one value. This is to prevent dropping intermediate values on the
    +049 * floor, silently, before dispatching the future's callbacks, which generally only accept one value. Other than this,
    +050 * things should work "as expected" whether you're looking at them from a Guava, JDK, or Reactive perspective.</p>
    +051 *
    +052 * @see Publisher Reactive Java type adapted by this object.
    +053 * @see ListenableFuture Guava's extension of the JDK's basic {@link Future}, which adds listener support.
    +054 * @see ApiFuture Lightweight Guava-like future meant to avoid dependencies on Java in API libraries.
    +055 * @see #wrap(Publisher) To wrap a {@link Publisher}.
    +056 * @see #wrap(ListenableFuture, Executor) To wrap a {@link ListenableFuture}.
    +057 * @see #wrap(ApiFuture, Executor) To wrap an {@link ApiFuture}.
    +058 */
    +059@Immutable
    +060@ThreadSafe
    +061@SuppressWarnings("UnstableApiUsage")
    +062public final class ReactiveFuture<R> implements Publisher<R>, ListenableFuture<R>, ApiFuture<R> {
    +063  /** Inner future, if one is set. Otherwise {@link Optional#empty()}. */
    +064  private final @Nonnull Optional<ListenableFuture<R>> future;
    +065
    +066  /** If a `publisher` is present, this object adapts it to a `future`. */
    +067  private final @Nullable PublisherListenableFuture<R> publisherAdapter;
    +068
    +069  /** If a `future` is present, this object adapts it to a `publisher`. */
    +070  private final @Nullable ListenableFuturePublisher<R> futureAdapter;
    +071
    +072  /** If a `future` is present, this object adapts it to a `publisher`. */
    +073  private final @Nullable CompletableFuturePublisher<R> javaFutureAdapter;
    +074
    +075  /**
    +076   * Spawn a reactive/future adapter in a reactive context, from a {@link Publisher}. Constructing a reactive future in
    +077   * this manner causes the object to operate in a "publisher-backed" mode.
    +078   *
    +079   * @param publisher Publisher to work with.
    +080   */
    +081  private ReactiveFuture(@Nonnull Publisher<R> publisher) {
    +082    this.future = Optional.empty();
    +083    this.futureAdapter = null;
    +084    this.publisherAdapter = new PublisherListenableFuture<>(publisher);
    +085    this.javaFutureAdapter = null;
    +086  }
    +087
    +088  /**
    +089   * Spawn a reactive/future adapter in a future context, from a {@link ListenableFuture}. Constructing a reactive
    +090   * future in this manner causes the object to operate in a "future-backed" mode.
    +091   *
    +092   * @param future Future to work with.
    +093   * @param executor Executor to use when running callbacks.
    +094   */
    +095  private ReactiveFuture(@Nonnull ListenableFuture<R> future, @Nonnull Executor executor) {
    +096    this.future = Optional.of(future);
    +097    this.futureAdapter = new ListenableFuturePublisher<>(future, executor);
    +098    this.publisherAdapter = null;
    +099    this.javaFutureAdapter = null;
    +100  }
    +101
    +102  /**
    +103   * Spawn a reactive/future adapter in a future context, from a {@link CompletableFuture}. Constructing a reactive
    +104   * future in this manner causes the object to operate in a "future-backed" mode.
    +105   *
    +106   * @param future Future to work with.
    +107   * @param executor Executor to use when running callbacks.
    +108   */
    +109  private ReactiveFuture(@Nonnull CompletableFuture<R> future, @Nonnull Executor executor) {
    +110    this.future = Optional.empty();
    +111    this.futureAdapter = null;
    +112    this.publisherAdapter = null;
    +113    this.javaFutureAdapter = new CompletableFuturePublisher<>(future, executor);
    +114  }
    +115
    +116  /** @return Internal future representation. */
    +117  private @Nonnull ListenableFuture<R> resolveFuture() {
    +118    if (this.publisherAdapter != null)
    +119      return this.publisherAdapter;
    +120    else if (this.javaFutureAdapter != null)
    +121      return this.javaFutureAdapter;
    +122    //noinspection OptionalGetWithoutIsPresent
    +123    return this.future.get();
    +124  }
    +125
    +126  /** @return Internal publisher representation. */
    +127  private @Nonnull Publisher<R> resolvePublisher() {
    +128    if (this.futureAdapter != null)
    +129      return this.futureAdapter;
    +130    else if (this.javaFutureAdapter != null)
    +131      return this.javaFutureAdapter;
    +132    return Objects.requireNonNull(this.publisherAdapter);
    +133  }
    +134
    +135  // -- Public API -- //
    +136  /**
    +137   * Wrap a Reactive Java {@link Publisher} in a universal {@link ReactiveFuture}, such that it may be used with any
    +138   * interface requiring a supported async or future value.
    +139   *
    +140   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +141   * class docs for more information.</p>
    +142   *
    +143   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +144   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +145   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +146   *
    +147   * @see #wrap(ListenableFuture, Executor) Wraps a {@link ListenableFuture} from Guava.
    +148   * @param publisher Reactive publisher to wrap.
    +149   * @param <R> Return or emission type of the publisher.
    +150   * @return Wrapped reactive future object.
    +151   * @throws IllegalArgumentException If the passed `publisher` is `null`.
    +152   */
    +153  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull Publisher<R> publisher) {
    +154    //noinspection ConstantConditions
    +155    if (publisher == null) throw new IllegalArgumentException("Cannot wrap `null` publisher.");
    +156    return new ReactiveFuture<>(publisher);
    +157  }
    +158
    +159  /**
    +160   * Wrap a regular Java {@link CompletableFuture} in a universal {@link ReactiveFuture}, such that it may be used with
    +161   * any interface requiring support for that class.
    +162   *
    +163   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +164   * class docs for more information.</p>
    +165   *
    +166   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +167   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +168   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +169   *
    +170   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +171   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +172   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +173   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +174   *
    +175   * @param future Completable future to wrap.
    +176   * @param <R> Return or emission type of the future.
    +177   * @return Wrapped reactive future object.
    +178   */
    +179  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull CompletableFuture<R> future) {
    +180    //noinspection ConstantConditions
    +181    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` publisher.");
    +182    return wrap(future, MoreExecutors.directExecutor());
    +183  }
    +184
    +185  /**
    +186   * Wrap a regular Java {@link CompletableFuture} in a universal {@link ReactiveFuture}, such that it may be used with
    +187   * any interface requiring support for that class.
    +188   *
    +189   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +190   * class docs for more information.</p>
    +191   *
    +192   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +193   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +194   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +195   *
    +196   * @param future Completable future to wrap.
    +197   * @param executor Executor to use.
    +198   * @param <R> Return or emission type of the future.
    +199   * @return Wrapped reactive future object.
    +200   */
    +201  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull CompletableFuture<R> future, @Nonnull Executor executor) {
    +202    //noinspection ConstantConditions
    +203    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` future.");
    +204    //noinspection ConstantConditions
    +205    if (executor == null) throw new IllegalArgumentException("Cannot wrap future with `null` executor.");
    +206    return new ReactiveFuture<>(future, executor);
    +207  }
    +208
    +209  /**
    +210   * Wrap a Guava {@link ListenableFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +211   * interface requiring a supported async or future value.
    +212   *
    +213   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +214   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +215   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +216   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +217   *
    +218   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +219   * class docs for more information.</p>
    +220   *
    +221   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +222   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +223   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +224   *
    +225   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +226   * @param future Future value to wrap.
    +227   * @param <R> Return value type for the future.
    +228   * @return Wrapped reactive future object.
    +229   * @throws IllegalArgumentException If the passed `future` is `null`.
    +230   */
    +231  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ListenableFuture<R> future) {
    +232    return wrap(future, MoreExecutors.directExecutor());
    +233  }
    +234
    +235  /**
    +236   * Wrap a Guava {@link ListenableFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +237   * interface requiring a supported async or future value.
    +238   *
    +239   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +240   * class docs for more information.</p>
    +241   *
    +242   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +243   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +244   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +245   *
    +246   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +247   * @param future Future value to wrap.
    +248   * @param executor Executor to dispatch callbacks with.
    +249   * @param <R> Return value type for the future.
    +250   * @return Wrapped reactive future object.
    +251   * @throws IllegalArgumentException If the passed `future` is `null`.
    +252   */
    +253  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ListenableFuture<R> future, @Nonnull Executor executor) {
    +254    //noinspection ConstantConditions
    +255    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` future.");
    +256    //noinspection ConstantConditions
    +257    if (executor == null) throw new IllegalArgumentException("Cannot wrap future with `null` executor.");
    +258    return new ReactiveFuture<>(future, executor);
    +259  }
    +260
    +261  /**
    +262   * Wrap a Google APIs {@link ApiFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +263   * interface requiring a supported async or future value.
    +264   *
    +265   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +266   * class docs for more information.</p>
    +267   *
    +268   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +269   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +270   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +271   *
    +272   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +273   * @see #wrap(ListenableFuture, Executor) Wraps a regular Guava {@link ListenableFuture}.
    +274   * @param apiFuture API future to wrap.
    +275   * @param executor Executor to run callbacks with.
    +276   * @param <R> Return value type for the future.
    +277   * @return Wrapped reactive future object.
    +278   * @throws IllegalArgumentException If the passed `apiFuture` is `null`.
    +279   */
    +280  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ApiFuture<R> apiFuture, @Nonnull Executor executor) {
    +281    //noinspection ConstantConditions
    +282    if (apiFuture == null) throw new IllegalArgumentException("Cannot wrap `null` API future.");
    +283    return wrap(new ApiFutureToListenableFuture<>(apiFuture), executor);
    +284  }
    +285
    +286  /**
    +287   * Wrap a Google APIs {@link ApiFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +288   * interface requiring a supported async or future value.
    +289   *
    +290   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +291   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +292   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +293   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +294   *
    +295   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +296   * class docs for more information.</p>
    +297   *
    +298   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +299   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +300   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +301   *
    +302   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +303   * @see #wrap(ListenableFuture, Executor) Wraps a regular Guava {@link ListenableFuture}.
    +304   * @param apiFuture API future to wrap.
    +305   * @param <R> Return value type for the future.
    +306   * @return Wrapped reactive future object.
    +307   * @throws IllegalArgumentException If the passed `apiFuture` is `null`.
    +308   */
    +309  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ApiFuture<R> apiFuture) {
    +310    return wrap(apiFuture, MoreExecutors.directExecutor());
    +311  }
    +312
    +313  /**
    +314   * Create an already-resolved future, wrapping the provided value. The future will present as done as soon as it is
    +315   * returned from this method.
    +316   *
    +317   * <p>Under the hood, this is simply a {@link ReactiveFuture} wrapping a call to
    +318   * {@link Futures#immediateFuture(Object)}.</p>
    +319   *
    +320   * @param value Value to wrap in an already-completed future.
    +321   * @param <R> Return value generic type.
    +322   * @return Reactive future wrapping a finished value.
    +323   */
    +324  public static @Nonnull <R> ReactiveFuture<R> done(@Nonnull R value) {
    +325    return wrap(Futures.immediateFuture(value));
    +326  }
    +327
    +328  /**
    +329   * Create an already-failed future, wrapping the provided exception instance. The future will present as one as soon
    +330   * as it is returned from this method.
    +331   *
    +332   * <p>Calling {@link Future#get(long, TimeUnit)} or {@link Future#get()} on a failed future will surface the
    +333   * associated exception where invocation occurs. Under the hood, this is simply a {@link ReactiveFuture} wrapping a
    +334   * call to {@link Futures#immediateFailedFuture(Throwable)}.</p>
    +335   *
    +336   * @param error Error to wrap in an already-failed future.
    +337   * @param <R> Return value generic type.
    +338   * @return Reactive future wrapping a finished value.
    +339   */
    +340  public static @Nonnull <R> ReactiveFuture<R> failed(@Nonnull Throwable error) {
    +341    return wrap(Futures.immediateFailedFuture(error));
    +342  }
    +343
    +344  /**
    +345   * Create an already-cancelled future. The future will present as both done and cancelled as soon as it is returned
    +346   * from this method.
    +347   *
    +348   * <p>Under the hood, this is simply a {@link ReactiveFuture} wrapping a call to
    +349   * {@link Futures#immediateCancelledFuture()}.</p>
    +350   *
    +351   * @param <R> Return value generic type.
    +352   * @return Reactive future wrapping a cancelled operation.
    +353   */
    +354  public static @Nonnull <R> ReactiveFuture<R> cancelled() {
    +355    return wrap(Futures.immediateCancelledFuture());
    +356  }
    +357
    +358  // -- Compliance: Publisher -- //
    +359  /**
    +360   * Request {@link Publisher} to start streaming data.
    +361   *
    +362   * <p>This is a "factory method" and can be called multiple times, each time starting a new {@link Subscription}. Each
    +363   * {@link Subscription} will work for only a single {@link Subscriber}. A {@link Subscriber} should only subscribe
    +364   * once to a single {@link Publisher}. If the {@link Publisher} rejects the subscription attempt or otherwise fails it
    +365   * will signal the error via {@link Subscriber#onError}.</p>
    +366   *
    +367   * @param subscriber the {@link Subscriber} that will consume signals from this {@link Publisher}.
    +368   */
    +369  @Override
    +370  public void subscribe(Subscriber<? super R> subscriber) {
    +371    resolvePublisher().subscribe(subscriber);
    +372  }
    +373
    +374  // -- Compliance: Listenable Future -- //
    +375  /**
    +376   * Registers a listener to be {@linkplain Executor#execute(Runnable) run} on the given executor. The listener will run
    +377   * when the {@code Future}'s computation is {@linkplain Future#isDone() complete} or, if the computation is already
    +378   * complete, immediately.
    +379   *
    +380   * <p>There is no guaranteed ordering of execution of listeners, but any listener added through this method is
    +381   * guaranteed to be called once the computation is complete.</p>
    +382   *
    +383   * <p>Exceptions thrown by a listener will be propagated up to the executor. Any exception thrown during
    +384   * {@code Executor.execute} (e.g., a {@code RejectedExecutionException} or an exception thrown by
    +385   * {@linkplain MoreExecutors#directExecutor direct execution}) will be caught and logged.</p>
    +386   *
    +387   * <p>Note: For fast, lightweight listeners that would be safe to execute in any thread, consider
    +388   * {@link MoreExecutors#directExecutor}. Otherwise, avoid it. Heavyweight {@code directExecutor} listeners can cause
    +389   * problems, and these problems can be difficult to reproduce because they depend on timing. For example:</p>
    +390   * <ul>
    +391   *   <li>The listener may be executed by the caller of {@code addListener}. That caller may be a
    +392   *       UI thread or other latency-sensitive thread. This can harm UI responsiveness.
    +393   *   <li>The listener may be executed by the thread that completes this {@code Future}. That
    +394   *       thread may be an internal system thread such as an RPC network thread. Blocking that
    +395   *       thread may stall progress of the whole system. It may even cause a deadlock.
    +396   *   <li>The listener may delay other listeners, even listeners that are not themselves {@code
    +397   *       directExecutor} listeners.
    +398   * </ul>
    +399   *
    +400   * <p>This is the most general listener interface. For common operations performed using listeners, see
    +401   * {@link Futures}. For a simplified but general listener interface, see
    +402   * {@link Futures#addCallback addCallback()}.</p>
    +403   *
    +404   * <p>Memory consistency effects: Actions in a thread prior to adding a listener <a
    +405   * href="https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5"><i>happen-before</i></a> its
    +406   * execution begins, perhaps in another thread.</p>
    +407   *
    +408   * <p>Guava implementations of {@code ListenableFuture} promptly release references to listeners after executing
    +409   * them.</p>
    +410   *
    +411   * @param listener the listener to run when the computation is complete.
    +412   * @param executor the executor to run the listener in
    +413   * @throws RejectedExecutionException if we tried to execute the listener immediately but the executor rejecte it.
    +414   */
    +415  @Override
    +416  public void addListener(@Nonnull Runnable listener, @Nonnull Executor executor) throws RejectedExecutionException {
    +417    resolveFuture().addListener(listener, executor);
    +418  }
    +419
    +420  /**
    +421   * Attempts to cancel execution of this task.  This attempt will fail if the task has already completed, has already
    +422   * been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when
    +423   * {@code cancel} is called, this task should never run.  If the task has already started, then the
    +424   * {@code mayInterruptIfRunning} parameter determines whether the thread executing this task should be interrupted in
    +425   * an attempt to stop the task.
    +426   *
    +427   * <p>After this method returns, subsequent calls to {@link #isDone} will always return {@code true}.  Subsequent
    +428   * calls to {@link #isCancelled} will always return {@code true} if this method returned {@code true}.
    +429   *
    +430   * @param mayInterruptIfRunning {@code true} if the thread executing this task should be interrupted; otherwise,
    +431   *                              in-progress tasks are allowed to complete
    +432   * @return {@code false} if the task could not be cancelled, typically because it has already completed normally;
    +433   *         {@code true} otherwise.
    +434   */
    +435  @Override
    +436  public boolean cancel(boolean mayInterruptIfRunning) {
    +437    return resolveFuture().cancel(mayInterruptIfRunning);
    +438  }
    +439
    +440  /**
    +441   * Returns {@code true} if this task was cancelled before it completed normally. This defers to the underlying future,
    +442   * or a wrapped object if using a {@link Publisher}.
    +443   *
    +444   * @return {@code true} if this task was cancelled before it completed
    +445   */
    +446  @Override
    +447  public boolean isCancelled() {
    +448    return resolveFuture().isCancelled();
    +449  }
    +450
    +451  /**
    +452   * Returns {@code true} if this task completed. This defers to the underlying future, or a wrapped object if using a
    +453   * Reactive Java {@link Publisher}.
    +454   *
    +455   * Completion may be due to normal termination, an exception, or cancellation -- in all of these cases, this method
    +456   * will return {@code true}.
    +457   *
    +458   * @return {@code true} if this task completed.
    +459   */
    +460  @Override
    +461  public boolean isDone() {
    +462    return resolveFuture().isDone();
    +463  }
    +464
    +465  /**
    +466   * Waits if necessary for the computation to complete, and then retrieves its result.
    +467   *
    +468   * <p>It is generally recommended to use the variant of this method which specifies a timeout - one must handle the
    +469   * additional {@link TimeoutException}, but on the other hand the computation can never infinitely block if an async
    +470   * value does not materialize.</p>
    +471   *
    +472   * @see #get(long, TimeUnit) For a safer version of this method, which allows specifying a timeout.
    +473   * @return the computed result.
    +474   * @throws CancellationException if the computation was cancelled
    +475   * @throws ExecutionException    if the computation threw an exception
    +476   * @throws InterruptedException  if the current thread was interrupted while waiting
    +477   */
    +478  @Override
    +479  public R get() throws InterruptedException, ExecutionException {
    +480    return resolveFuture().get();
    +481  }
    +482
    +483  /**
    +484   * Waits if necessary for at most the given time for the computation to complete, and then retrieves its result, if
    +485   * available.
    +486   *
    +487   * @param timeout the maximum time to wait
    +488   * @param unit    the time unit of the timeout argument
    +489   * @return the computed result
    +490   * @throws CancellationException if the computation was cancelled
    +491   * @throws ExecutionException    if the computation threw an exception
    +492   * @throws InterruptedException  if the current thread was interrupted while waiting
    +493   * @throws TimeoutException      if the wait timed out
    +494   */
    +495  @Override
    +496  public R get(long timeout, @Nonnull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
    +497    return resolveFuture().get(timeout, unit);
    +498  }
    +499
    +500  /**
    +501   * Structure that adapts a {@link Publisher} to a {@link ListenableFuture} interface. We accomplish this by
    +502   * immediately subscribing to the publisher with a callback that dispatches a {@link SettableFuture}.
    +503   *
    +504   * <p>This object is used in the specific circumstance of wrapping a {@link Publisher}, and then using the wrapped
    +505   * object as a {@link ListenableFuture} (or any descendent or compliant implementation thereof).</p>
    +506   *
    +507   * @param <T> Generic type returned by the future.
    +508   */
    +509  @Immutable
    +510  @ThreadSafe
    +511  public final static class PublisherListenableFuture<T> implements ListenableFuture<T>, Publisher<T> {
    +512    /** Whether we have received a value. */
    +513    private final @Nonnull AtomicBoolean received = new AtomicBoolean(false);
    +514
    +515    /** Whether we have completed acquiring a value. */
    +516    private final @Nonnull AtomicBoolean completed = new AtomicBoolean(false);
    +517
    +518    /** Whether we have been cancelled. */
    +519    private final @Nonnull AtomicBoolean cancelled = new AtomicBoolean(false);
    +520
    +521    /** Describes the list of proxied subscribers. */
    +522    private final @Nonnull Map<String, Subscriber<? super T>> subscribers = new ConcurrentHashMap<>();
    +523
    +524    /** Converted/pass-through future value. */
    +525    private final @Nonnull SettableFuture<T> future;
    +526
    +527    /** Subscription, so we can propagate cancellation. */
    +528    private volatile Subscription subscription;
    +529
    +530    /**
    +531     * Private constructor.
    +532     *
    +533     * @param publisher Publisher to wrap.
    +534     */
    +535    private PublisherListenableFuture(@Nonnull Publisher<T> publisher) {
    +536      this.future = SettableFuture.create();
    +537      publisher.subscribe(new Subscriber<T>() {
    +538        @Override
    +539        public void onSubscribe(Subscription s) {
    +540          PublisherListenableFuture.this.subscription = s;
    +541        }
    +542
    +543        @Override
    +544        public void onNext(T t) {
    +545          if (received.compareAndSet(false, true)) {
    +546            PublisherListenableFuture.this.proxyExecute((sub) -> sub.onNext(t));
    +547            future.set(t);
    +548            return;
    +549          }
    +550          this.onError(new IllegalStateException(
    +551            "Cannot publish multiple items through `ReactiveFuture`."));
    +552        }
    +553
    +554        @Override
    +555        public void onError(Throwable t) {
    +556          if (!completed.get()) {
    +557            PublisherListenableFuture.this.proxyExecute((sub) -> sub.onError(t));
    +558            future.setException(t);
    +559          }
    +560        }
    +561
    +562        @Override
    +563        public void onComplete() {
    +564          if (completed.compareAndSet(false, true)) {
    +565            PublisherListenableFuture.this.proxyExecute(Subscriber::onComplete);
    +566            PublisherListenableFuture.this.clear();
    +567          }
    +568        }
    +569      });
    +570    }
    +571
    +572    /**
    +573     * Call something on each proxied publisher subscription, if any.
    +574     *
    +575     * @param operation Operation to execute. Called for each subscriber.
    +576     */
    +577    private void proxyExecute(@Nonnull Consumer<Subscriber<? super T>> operation) {
    +578      if (!this.subscribers.isEmpty()) {
    +579        this.subscribers.values().forEach(operation);
    +580      }
    +581    }
    +582
    +583    /**
    +584     * Remove all subscribers and clear references to futures/publishers/listeners.
    +585     */
    +586    private void clear() {
    +587      this.subscribers.clear();
    +588      this.subscription = null;
    +589    }
    +590
    +591    /**
    +592     * Drop a subscription (after proxied {@link Subscription#cancel()} is called).
    +593     *
    +594     * @param id ID of the subscription to drop.
    +595     */
    +596    private void dropSubscription(@Nonnull String id) {
    +597      this.subscribers.get(id).onComplete();
    +598      this.subscribers.remove(id);
    +599    }
    +600
    +601    // -- Interface Compliance: Publisher -- //
    +602
    +603    @Override
    +604    public void subscribe(Subscriber<? super T> s) {
    +605      final String id = String.valueOf(this.subscribers.size());
    +606      Subscription sub = new Subscription() {
    +607        @Override
    +608        public void request(long n) {
    +609          PublisherListenableFuture.this.subscription.request(n);
    +610        }
    +611
    +612        @Override
    +613        public void cancel() {
    +614          // kill self
    +615          PublisherListenableFuture.this.dropSubscription(id);
    +616        }
    +617      };
    +618      this.subscribers.put(id, s);
    +619      s.onSubscribe(sub);
    +620    }
    +621
    +622    // -- Interface Compliance: Listenable Future -- //
    +623
    +624    @Override
    +625    public void addListener(@Nonnull Runnable runnable, @Nonnull Executor executor) {
    +626      this.future.addListener(runnable, executor);
    +627    }
    +628
    +629    @Override
    +630    public boolean cancel(boolean mayInterruptIfRunning) {
    +631      boolean cancelled = false;
    +632      if (!this.completed.get() && this.cancelled.compareAndSet(false, true)) {
    +633        this.proxyExecute(Subscriber::onComplete);  // dispatch `onComplete` for any subscribers
    +634        this.subscription.cancel();  // cancel upwards
    +635        cancelled = this.future.cancel(mayInterruptIfRunning);  // cancel future
    +636        this.clear();  // clear references
    +637      }
    +638      return cancelled;
    +639    }
    +640
    +641    @Override
    +642    public boolean isCancelled() {
    +643      return this.cancelled.get();
    +644    }
    +645
    +646    @Override
    +647    public boolean isDone() {
    +648      return this.completed.get() || this.cancelled.get();
    +649    }
    +650
    +651    @Override
    +652    public T get() throws InterruptedException, ExecutionException {
    +653      return this.future.get();
    +654    }
    +655
    +656    @Override
    +657    public T get(long timeout, @Nonnull TimeUnit unit)
    +658        throws InterruptedException, ExecutionException, TimeoutException {
    +659      return this.future.get(timeout, unit);
    +660    }
    +661  }
    +662
    +663  /**
    +664   * Structure that adapts Java's {@link CompletableFuture} to a Reactive Java {@link Publisher}, which publishes one
    +665   * item - either the result of the computation, or an error.
    +666   *
    +667   * <p>This object is used in the specific circumstance that a {@link CompletableFuture} is wrapped by a
    +668   * {@link ReactiveFuture}, and then used within the Reactive Java or Guava ecosystems as a {@link Publisher} or a
    +669   * {@link ListenableFuture} (or {@link ApiFuture}), or a descendent thereof. As in {@link ListenableFuturePublisher},
    +670   * we simply set the callback for the future value, upon item-request (one cycle is allowed), and propagate any events
    +671   * received to the publisher.</p>
    +672   *
    +673   * @param <T> Emit type for this adapter. Matches the future it wraps.
    +674   */
    +675  public final static class CompletableFuturePublisher<T>
    +676      implements Publisher<T>, ListenableFuture<T>, CompletionStage<T> {
    +677    private final @Nonnull CompletableFuture<T> future;
    +678    private final @Nonnull CompletionStage<T> stage;
    +679    private final @Nonnull Executor callbackExecutor;
    +680
    +681    /**
    +682     * Construct an adapter that propagates signals from a {@link CompletableFuture} to a {@link Publisher}.
    +683     *
    +684     * @param future Completable future to wrap.
    +685     * @param callbackExecutor Callback executor to use.
    +686     */
    +687    private CompletableFuturePublisher(@Nonnull CompletableFuture<T> future,
    +688                                       @Nonnull Executor callbackExecutor) {
    +689      this.future = future;
    +690      this.stage = future;
    +691      this.callbackExecutor = callbackExecutor;
    +692    }
    +693
    +694    /* == `Future`/`ListenableFuture` Interface Compliance == */
    +695
    +696    /** @inheritDoc */
    +697    @Override public final void subscribe(Subscriber<? super T> subscriber) {
    +698      Objects.requireNonNull(subscriber, "Subscriber cannot be null");
    +699      subscriber.onSubscribe(new CompletableFutureSubscription(this.future, subscriber, this.callbackExecutor));
    +700    }
    +701
    +702    /** @inheritDoc */
    +703    @Override public void addListener(Runnable runnable, Executor executor) {
    +704      future.thenRunAsync(runnable, executor);
    +705    }
    +706
    +707    /** @inheritDoc */
    +708    @Override public boolean cancel(boolean mayInterruptIfRunning) {
    +709      return future.cancel(mayInterruptIfRunning);
    +710    }
    +711
    +712    /** @inheritDoc */
    +713    @Override public boolean isCancelled() {
    +714      return future.isCancelled();
    +715    }
    +716
    +717    /** @inheritDoc */
    +718    @Override public boolean isDone() {
    +719      return future.isDone();
    +720    }
    +721
    +722    /** @inheritDoc */
    +723    @Override public T get() throws InterruptedException, ExecutionException {
    +724      return future.get();
    +725    }
    +726
    +727    /** @inheritDoc */
    +728    @Override
    +729    public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
    +730      return future.get(timeout, unit);
    +731    }
    +732
    +733    /* == `CompletionStage` Interface Compliance == */
    +734
    +735    /** @inheritDoc */
    +736    @Override public <U> CompletionStage<U> thenApply(Function<? super T, ? extends U> fn) {
    +737      return stage.thenApply(fn);
    +738    }
    +739
    +740    /** @inheritDoc */
    +741    @Override public <U> CompletionStage<U> thenApplyAsync(Function<? super T, ? extends U> fn) {
    +742      return stage.thenApplyAsync(fn);
    +743    }
    +744
    +745    /** @inheritDoc */
    +746    @Override public <U> CompletionStage<U> thenApplyAsync(Function<? super T, ? extends U> fn, Executor executor) {
    +747      return stage.thenApplyAsync(fn, executor);
    +748    }
    +749
    +750    /** @inheritDoc */
    +751    @Override public CompletionStage<Void> thenAccept(Consumer<? super T> action) {
    +752      return stage.thenAccept(action);
    +753    }
    +754
    +755    /** @inheritDoc */
    +756    @Override public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action) {
    +757      return stage.thenAcceptAsync(action);
    +758    }
    +759
    +760    /** @inheritDoc */
    +761    @Override public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor) {
    +762      return stage.thenAcceptAsync(action, executor);
    +763    }
    +764
    +765    /** @inheritDoc */
    +766    @Override public CompletionStage<Void> thenRun(Runnable action) {
    +767      return stage.thenRun(action);
    +768    }
    +769
    +770    /** @inheritDoc */
    +771    @Override public CompletionStage<Void> thenRunAsync(Runnable action) {
    +772      return stage.thenRunAsync(action);
    +773    }
    +774
    +775    /** @inheritDoc */
    +776    @Override public CompletionStage<Void> thenRunAsync(Runnable action, Executor executor) {
    +777      return stage.thenRunAsync(action, executor);
    +778    }
    +779
    +780    /** @inheritDoc */
    +781    @Override public <U, V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,
    +782                                                           BiFunction<? super T, ? super U, ? extends V> fn) {
    +783      return stage.thenCombine(other, fn);
    +784    }
    +785
    +786    /** @inheritDoc */
    +787    @Override public <U, V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,
    +788                                                                BiFunction<? super T, ? super U, ? extends V> fn) {
    +789      return stage.thenCombineAsync(other, fn);
    +790    }
    +791
    +792    /** @inheritDoc */
    +793    @Override public <U, V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,
    +794                                                                BiFunction<? super T, ? super U, ? extends V> fn,
    +795                                                                Executor executor) {
    +796      return stage.thenCombineAsync(other, fn, executor);
    +797    }
    +798
    +799    /** @inheritDoc */
    +800    @Override public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,
    +801                                                              BiConsumer<? super T, ? super U> action) {
    +802      return stage.thenAcceptBoth(other, action);
    +803    }
    +804
    +805    /** @inheritDoc */
    +806    @Override public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
    +807                                                                   BiConsumer<? super T, ? super U> action) {
    +808      return stage.thenAcceptBothAsync(other, action);
    +809    }
    +810
    +811    /** @inheritDoc */
    +812    @Override
    +813    public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
    +814                                                         BiConsumer<? super T, ? super U> action,
    +815                                                         Executor executor) {
    +816      return stage.thenAcceptBothAsync(other, action, executor);
    +817    }
    +818
    +819    /** @inheritDoc */
    +820    @Override
    +821    public CompletionStage<Void> runAfterBoth(CompletionStage<?> other, Runnable action) {
    +822      return stage.runAfterBoth(other, action);
    +823    }
    +824
    +825    /** @inheritDoc */
    +826    @Override
    +827    public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action) {
    +828      return stage.runAfterBothAsync(other, action);
    +829    }
    +830
    +831    /** @inheritDoc */
    +832    @Override
    +833    public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action, Executor executor) {
    +834      return stage.runAfterBothAsync(other, action, executor);
    +835    }
    +836
    +837    /** @inheritDoc */
    +838    @Override
    +839    public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn) {
    +840      return stage.applyToEither(other, fn);
    +841    }
    +842
    +843    /** @inheritDoc */
    +844    @Override
    +845    public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn) {
    +846      return stage.applyToEitherAsync(other, fn);
    +847    }
    +848
    +849    /** @inheritDoc */
    +850    @Override
    +851    public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,
    +852                                                     Function<? super T, U> fn,
    +853                                                     Executor executor) {
    +854      return stage.applyToEitherAsync(other, fn, executor);
    +855    }
    +856
    +857    /** @inheritDoc */
    +858    @Override public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,
    +859                                                        Consumer<? super T> action) {
    +860      return stage.acceptEither(other, action);
    +861    }
    +862
    +863    /** @inheritDoc */
    +864    @Override public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,
    +865                                                             Consumer<? super T> action) {
    +866      return stage.acceptEitherAsync(other, action);
    +867    }
    +868
    +869    /** @inheritDoc */
    +870    @Override public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,
    +871                                                             Consumer<? super T> action,
    +872                                                             Executor executor) {
    +873      return stage.acceptEitherAsync(other, action, executor);
    +874    }
    +875
    +876    /** @inheritDoc */
    +877    @Override public CompletionStage<Void> runAfterEither(CompletionStage<?> other, Runnable action) {
    +878      return stage.runAfterEither(other, action);
    +879    }
    +880
    +881    /** @inheritDoc */
    +882    @Override public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action) {
    +883      return stage.runAfterEitherAsync(other, action);
    +884    }
    +885
    +886    /** @inheritDoc */
    +887    @Override public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action, Executor executor) {
    +888      return stage.runAfterEitherAsync(other, action, executor);
    +889    }
    +890
    +891    /** @inheritDoc */
    +892    @Override public <U> CompletionStage<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) {
    +893      return stage.thenCompose(fn);
    +894    }
    +895
    +896    /** @inheritDoc */
    +897    @Override public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) {
    +898      return stage.thenComposeAsync(fn);
    +899    }
    +900
    +901    /** @inheritDoc */
    +902    @Override public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn,
    +903                                                             Executor executor) {
    +904      return stage.thenComposeAsync(fn, executor);
    +905    }
    +906
    +907    /** @inheritDoc */
    +908    @Override public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) {
    +909      return stage.handle(fn);
    +910    }
    +911
    +912    /** @inheritDoc */
    +913    @Override public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) {
    +914      return stage.handleAsync(fn);
    +915    }
    +916
    +917    /** @inheritDoc */
    +918    @Override public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,
    +919                                                        Executor executor) {
    +920      return stage.handleAsync(fn, executor);
    +921    }
    +922
    +923    /** @inheritDoc */
    +924    @Override public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> action) {
    +925      return stage.whenComplete(action);
    +926    }
    +927
    +928    /** @inheritDoc */
    +929    @Override public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action) {
    +930      return stage.whenCompleteAsync(action);
    +931    }
    +932
    +933    /** @inheritDoc */
    +934    @Override public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action,
    +935                                                          Executor executor) {
    +936      return stage.whenCompleteAsync(action, executor);
    +937    }
    +938
    +939    /** @inheritDoc */
    +940    @Override public CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn) {
    +941      return stage.exceptionally(fn);
    +942    }
    +943
    +944    /** @inheritDoc */
    +945    @Override public CompletableFuture<T> toCompletableFuture() {
    +946      return stage.toCompletableFuture();
    +947    }
    +948
    +949    /**
    +950     * Models a Reactive Java {@link Subscription}, which is responsible for propagating events from a
    +951     * Concurrent Java {@link CompletableFuture} to a {@link Subscriber}.
    +952     *
    +953     * <p>This object is generally used internally by the {@link CompletableFuturePublisher}, once a {@link Subscriber}
    +954     * attaches itself to a {@link Publisher} that is actually a wrapped {@link CompletableFuture}. Error (exception)
    +955     * events and value events are both propagated. Subscribers based on this wrapping will only ever receive a maximum
    +956     * of <b>one value</b> or <b>one error</b>.</p>
    +957     */
    +958    @Immutable
    +959    @ThreadSafe
    +960    public final class CompletableFutureSubscription implements Subscription {
    +961      private final AtomicBoolean completed = new AtomicBoolean(false);
    +962      private final @Nonnull Subscriber<? super T> subscriber;
    +963      private final @Nonnull CompletableFuture<T> future;
    +964      private final @Nonnull Executor executor;
    +965
    +966      /**
    +967       * Private constructor, meant for use by {@link CompletableFuturePublisher} only.
    +968       *
    +969       * @param future Future value to adapt.
    +970       * @param subscriber The subscriber.
    +971       * @param executor Executor to run callbacks with.
    +972       */
    +973      CompletableFutureSubscription(@Nonnull CompletableFuture<T> future,
    +974                                    @Nonnull Subscriber<? super T> subscriber,
    +975                                    @Nonnull Executor executor) {
    +976        this.future = Objects.requireNonNull(future);
    +977        this.subscriber = Objects.requireNonNull(subscriber);
    +978        this.executor = Objects.requireNonNull(executor);
    +979      }
    +980
    +981      /**
    +982       * Request the specified number of items from the underlying {@link Subscription}. This must <b>always be
    +983       * <pre>1</pre></b>.
    +984       *
    +985       * @param n Number of elements to request to the upstream (must always be <pre>1</pre>).
    +986       * @throws IllegalArgumentException If any value other than <pre>1</pre> is passed in.
    +987       */
    +988      public synchronized void request(long n) {
    +989        if (n == 1 && !completed.get()) {
    +990          try {
    +991            CompletableFuture<T> future = this.future;
    +992            future.thenAcceptAsync(t -> {
    +993              T val = null;
    +994              Throwable err = null;
    +995              try {
    +996                val = future.get();
    +997              } catch (Exception exc) {
    +998                err = exc;
    +999              }
    +1000
    +1001              if (completed.compareAndSet(false, true)) {
    +1002                if (err != null) {
    +1003                  subscriber.onError(err);
    +1004                } else {
    +1005                  if (val != null) {
    +1006                    subscriber.onNext(val);
    +1007                  }
    +1008                  subscriber.onComplete();
    +1009                }
    +1010              }
    +1011            }, executor);
    +1012
    +1013          } catch (Exception e) {
    +1014            subscriber.onError(e);
    +1015          }
    +1016        } else if (n != 1) {
    +1017          IllegalArgumentException ex = new IllegalArgumentException(
    +1018              "Cannot request more or less than 1 item from a ReactiveFuture-wrapped publisher.");
    +1019          subscriber.onError(ex);
    +1020        }
    +1021      }
    +1022
    +1023      /**
    +1024       * Request the publisher to stop sending data and clean up resources.
    +1025       */
    +1026      public synchronized void cancel() {
    +1027        if (completed.compareAndSet(false, true)) {
    +1028          subscriber.onComplete();
    +1029          future.cancel(false);
    +1030        }
    +1031      }
    +1032    }
    +1033  }
    +1034
    +1035  /**
    +1036   * Structure that adapts Guava's {@link ListenableFuture} to a Reactive Java {@link Publisher}, which publishes one
    +1037   * item - either the result of the computation, or an error.
    +1038   *
    +1039   * <p>This object is used in the specific circumstance that a {@link ListenableFuture} is wrapped by a
    +1040   * {@link ReactiveFuture}, and then used within the Reactive Java ecosystem as a {@link Publisher}. We simply set a
    +1041   * callback for the future value, upon item-request (one cycle is allowed), and propagate any events received to the
    +1042   * publisher.</p>
    +1043   *
    +1044   * @param <T> Emit type for this adapter. Matches the publisher it wraps.
    +1045   */
    +1046  public final static class ListenableFuturePublisher<T> implements Publisher<T> {
    +1047    private final @Nonnull ListenableFuture<T> future;
    +1048    private final @Nonnull Executor callbackExecutor;
    +1049
    +1050    /**
    +1051     * Wrap a {@link ListenableFuture}. Private constructor for use by {@link ReactiveFuture} only.
    +1052     *
    +1053     * @param future The future to convert or wait on.
    +1054     * @param callbackExecutor Executor to run the callback on.
    +1055     */
    +1056    private ListenableFuturePublisher(@Nonnull ListenableFuture<T> future,
    +1057                                      @Nonnull Executor callbackExecutor) {
    +1058      this.future = future;
    +1059      this.callbackExecutor = callbackExecutor;
    +1060    }
    +1061
    +1062    @Override
    +1063    public final void subscribe(Subscriber<? super T> subscriber) {
    +1064      Objects.requireNonNull(subscriber, "Subscriber cannot be null");
    +1065      subscriber.onSubscribe(new ListenableFutureSubscription(this.future, subscriber, this.callbackExecutor));
    +1066    }
    +1067
    +1068    /**
    +1069     * Models a Reactive Java {@link Subscription}, which is responsible for propagating events from a
    +1070     * {@link ListenableFuture} to a {@link Subscriber}.
    +1071     *
    +1072     * <p>This object is generally used internally by the {@link ListenableFuturePublisher}, once a {@link Subscriber}
    +1073     * attaches itself to a {@link Publisher} that is actually a wrapped {@link ListenableFuture}. Error (exception)
    +1074     * events and value events are both propagated. Subscribers based on this wrapping will only ever receive a maximum
    +1075     * of <b>one value</b> or <b>one error</b>.</p>
    +1076     */
    +1077    @Immutable
    +1078    @ThreadSafe
    +1079    public final class ListenableFutureSubscription implements Subscription {
    +1080      private final AtomicBoolean completed = new AtomicBoolean(false);
    +1081      private final @Nonnull Subscriber<? super T> subscriber;
    +1082      private final @Nonnull ListenableFuture<T> future; // to allow cancellation
    +1083      private final @Nonnull Executor executor;  // executor to use when dispatching the callback
    +1084
    +1085      /**
    +1086       * Private constructor, meant for use by {@link ListenableFuturePublisher} only.
    +1087       *
    +1088       * @param future Future value to adapt.
    +1089       * @param subscriber The subscriber.
    +1090       * @param executor Executor to run callbacks with.
    +1091       */
    +1092      ListenableFutureSubscription(@Nonnull ListenableFuture<T> future,
    +1093                                   @Nonnull Subscriber<? super T> subscriber,
    +1094                                   @Nonnull Executor executor) {
    +1095        this.future = Objects.requireNonNull(future);
    +1096        this.subscriber = Objects.requireNonNull(subscriber);
    +1097        this.executor = Objects.requireNonNull(executor);
    +1098      }
    +1099
    +1100      /**
    +1101       * Request the specified number of items from the underlying {@link Subscription}. This must <b>always be
    +1102       * <pre>1</pre></b>.
    +1103       *
    +1104       * @param n Number of elements to request to the upstream (must always be <pre>1</pre>).
    +1105       * @throws IllegalArgumentException If any value other than <pre>1</pre> is passed in.
    +1106       */
    +1107      public synchronized void request(long n) {
    +1108        if (n == 1 && !completed.get()) {
    +1109          try {
    +1110            ListenableFuture<T> future = this.future;
    +1111            future.addListener(() -> {
    +1112              T val = null;
    +1113              Throwable err = null;
    +1114              try {
    +1115                val = this.future.get();
    +1116              } catch (Exception exc) {
    +1117                err = exc;
    +1118              }
    +1119
    +1120              if (completed.compareAndSet(false, true)) {
    +1121                if (err != null) {
    +1122                  subscriber.onError(err);
    +1123                } else {
    +1124                  if (val != null) {
    +1125                    subscriber.onNext(val);
    +1126                  }
    +1127                  subscriber.onComplete();
    +1128                }
    +1129              }
    +1130            }, this.executor);
    +1131          } catch (Exception e) {
    +1132            subscriber.onError(e);
    +1133          }
    +1134        } else if (n != 1) {
    +1135          IllegalArgumentException ex = new IllegalArgumentException(
    +1136            "Cannot request more or less than 1 item from a ReactiveFuture-wrapped publisher.");
    +1137          subscriber.onError(ex);
    +1138        }
    +1139      }
    +1140
    +1141      /**
    +1142       * Request the publisher to stop sending data and clean up resources.
    +1143       */
    +1144      public synchronized void cancel() {
    +1145        if (completed.compareAndSet(false, true)) {
    +1146          subscriber.onComplete();
    +1147          future.cancel(false);
    +1148        }
    +1149      }
    +1150    }
    +1151  }
    +1152}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/runtime/ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription.html b/docs/java/src-html/gust/backend/runtime/ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription.html new file mode 100644 index 000000000..a365627a5 --- /dev/null +++ b/docs/java/src-html/gust/backend/runtime/ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription.html @@ -0,0 +1,1226 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.runtime;
    +014
    +015import com.google.api.core.ApiFuture;
    +016import com.google.api.core.ApiFutureToListenableFuture;
    +017import com.google.common.util.concurrent.Futures;
    +018import com.google.common.util.concurrent.ListenableFuture;
    +019import com.google.common.util.concurrent.MoreExecutors;
    +020import com.google.common.util.concurrent.SettableFuture;
    +021import org.reactivestreams.Publisher;
    +022import org.reactivestreams.Subscriber;
    +023import org.reactivestreams.Subscription;
    +024
    +025import javax.annotation.Nonnull;
    +026import javax.annotation.Nullable;
    +027import javax.annotation.concurrent.Immutable;
    +028import javax.annotation.concurrent.ThreadSafe;
    +029import java.util.*;
    +030import java.util.concurrent.*;
    +031import java.util.concurrent.atomic.AtomicBoolean;
    +032import java.util.function.BiConsumer;
    +033import java.util.function.BiFunction;
    +034import java.util.function.Consumer;
    +035import java.util.function.Function;
    +036
    +037
    +038/**
    +039 * Adapts future/async value containers from different frameworks (namely, Reactive Java, Guava, and the JDK).
    +040 *
    +041 * <p>Create a new {@link ReactiveFuture} by using any of the {@code wrap)} factory methods. The resulting object is
    +042 * usable as a {@link Publisher}, {@link ListenableFuture}, or {@link ApiFuture}. This object simply wraps whatever
    +043 * inner object is provided, and as such instances are lightweight; there is no default functionality after immediate
    +044 * construction in most cases.</p>
    +045 *
    +046 * <p><b>Caveat:</b> when using a {@link Publisher} as a {@link ListenableFuture} (i.e. wrapping a {@link Publisher} and
    +047 * then using any of the typical future methods, like {@link ListenableFuture#addListener(Runnable, Executor)}), the
    +048 * underlying publisher may not publish more than one value. This is to prevent dropping intermediate values on the
    +049 * floor, silently, before dispatching the future's callbacks, which generally only accept one value. Other than this,
    +050 * things should work "as expected" whether you're looking at them from a Guava, JDK, or Reactive perspective.</p>
    +051 *
    +052 * @see Publisher Reactive Java type adapted by this object.
    +053 * @see ListenableFuture Guava's extension of the JDK's basic {@link Future}, which adds listener support.
    +054 * @see ApiFuture Lightweight Guava-like future meant to avoid dependencies on Java in API libraries.
    +055 * @see #wrap(Publisher) To wrap a {@link Publisher}.
    +056 * @see #wrap(ListenableFuture, Executor) To wrap a {@link ListenableFuture}.
    +057 * @see #wrap(ApiFuture, Executor) To wrap an {@link ApiFuture}.
    +058 */
    +059@Immutable
    +060@ThreadSafe
    +061@SuppressWarnings("UnstableApiUsage")
    +062public final class ReactiveFuture<R> implements Publisher<R>, ListenableFuture<R>, ApiFuture<R> {
    +063  /** Inner future, if one is set. Otherwise {@link Optional#empty()}. */
    +064  private final @Nonnull Optional<ListenableFuture<R>> future;
    +065
    +066  /** If a `publisher` is present, this object adapts it to a `future`. */
    +067  private final @Nullable PublisherListenableFuture<R> publisherAdapter;
    +068
    +069  /** If a `future` is present, this object adapts it to a `publisher`. */
    +070  private final @Nullable ListenableFuturePublisher<R> futureAdapter;
    +071
    +072  /** If a `future` is present, this object adapts it to a `publisher`. */
    +073  private final @Nullable CompletableFuturePublisher<R> javaFutureAdapter;
    +074
    +075  /**
    +076   * Spawn a reactive/future adapter in a reactive context, from a {@link Publisher}. Constructing a reactive future in
    +077   * this manner causes the object to operate in a "publisher-backed" mode.
    +078   *
    +079   * @param publisher Publisher to work with.
    +080   */
    +081  private ReactiveFuture(@Nonnull Publisher<R> publisher) {
    +082    this.future = Optional.empty();
    +083    this.futureAdapter = null;
    +084    this.publisherAdapter = new PublisherListenableFuture<>(publisher);
    +085    this.javaFutureAdapter = null;
    +086  }
    +087
    +088  /**
    +089   * Spawn a reactive/future adapter in a future context, from a {@link ListenableFuture}. Constructing a reactive
    +090   * future in this manner causes the object to operate in a "future-backed" mode.
    +091   *
    +092   * @param future Future to work with.
    +093   * @param executor Executor to use when running callbacks.
    +094   */
    +095  private ReactiveFuture(@Nonnull ListenableFuture<R> future, @Nonnull Executor executor) {
    +096    this.future = Optional.of(future);
    +097    this.futureAdapter = new ListenableFuturePublisher<>(future, executor);
    +098    this.publisherAdapter = null;
    +099    this.javaFutureAdapter = null;
    +100  }
    +101
    +102  /**
    +103   * Spawn a reactive/future adapter in a future context, from a {@link CompletableFuture}. Constructing a reactive
    +104   * future in this manner causes the object to operate in a "future-backed" mode.
    +105   *
    +106   * @param future Future to work with.
    +107   * @param executor Executor to use when running callbacks.
    +108   */
    +109  private ReactiveFuture(@Nonnull CompletableFuture<R> future, @Nonnull Executor executor) {
    +110    this.future = Optional.empty();
    +111    this.futureAdapter = null;
    +112    this.publisherAdapter = null;
    +113    this.javaFutureAdapter = new CompletableFuturePublisher<>(future, executor);
    +114  }
    +115
    +116  /** @return Internal future representation. */
    +117  private @Nonnull ListenableFuture<R> resolveFuture() {
    +118    if (this.publisherAdapter != null)
    +119      return this.publisherAdapter;
    +120    else if (this.javaFutureAdapter != null)
    +121      return this.javaFutureAdapter;
    +122    //noinspection OptionalGetWithoutIsPresent
    +123    return this.future.get();
    +124  }
    +125
    +126  /** @return Internal publisher representation. */
    +127  private @Nonnull Publisher<R> resolvePublisher() {
    +128    if (this.futureAdapter != null)
    +129      return this.futureAdapter;
    +130    else if (this.javaFutureAdapter != null)
    +131      return this.javaFutureAdapter;
    +132    return Objects.requireNonNull(this.publisherAdapter);
    +133  }
    +134
    +135  // -- Public API -- //
    +136  /**
    +137   * Wrap a Reactive Java {@link Publisher} in a universal {@link ReactiveFuture}, such that it may be used with any
    +138   * interface requiring a supported async or future value.
    +139   *
    +140   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +141   * class docs for more information.</p>
    +142   *
    +143   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +144   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +145   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +146   *
    +147   * @see #wrap(ListenableFuture, Executor) Wraps a {@link ListenableFuture} from Guava.
    +148   * @param publisher Reactive publisher to wrap.
    +149   * @param <R> Return or emission type of the publisher.
    +150   * @return Wrapped reactive future object.
    +151   * @throws IllegalArgumentException If the passed `publisher` is `null`.
    +152   */
    +153  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull Publisher<R> publisher) {
    +154    //noinspection ConstantConditions
    +155    if (publisher == null) throw new IllegalArgumentException("Cannot wrap `null` publisher.");
    +156    return new ReactiveFuture<>(publisher);
    +157  }
    +158
    +159  /**
    +160   * Wrap a regular Java {@link CompletableFuture} in a universal {@link ReactiveFuture}, such that it may be used with
    +161   * any interface requiring support for that class.
    +162   *
    +163   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +164   * class docs for more information.</p>
    +165   *
    +166   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +167   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +168   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +169   *
    +170   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +171   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +172   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +173   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +174   *
    +175   * @param future Completable future to wrap.
    +176   * @param <R> Return or emission type of the future.
    +177   * @return Wrapped reactive future object.
    +178   */
    +179  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull CompletableFuture<R> future) {
    +180    //noinspection ConstantConditions
    +181    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` publisher.");
    +182    return wrap(future, MoreExecutors.directExecutor());
    +183  }
    +184
    +185  /**
    +186   * Wrap a regular Java {@link CompletableFuture} in a universal {@link ReactiveFuture}, such that it may be used with
    +187   * any interface requiring support for that class.
    +188   *
    +189   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +190   * class docs for more information.</p>
    +191   *
    +192   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +193   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +194   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +195   *
    +196   * @param future Completable future to wrap.
    +197   * @param executor Executor to use.
    +198   * @param <R> Return or emission type of the future.
    +199   * @return Wrapped reactive future object.
    +200   */
    +201  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull CompletableFuture<R> future, @Nonnull Executor executor) {
    +202    //noinspection ConstantConditions
    +203    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` future.");
    +204    //noinspection ConstantConditions
    +205    if (executor == null) throw new IllegalArgumentException("Cannot wrap future with `null` executor.");
    +206    return new ReactiveFuture<>(future, executor);
    +207  }
    +208
    +209  /**
    +210   * Wrap a Guava {@link ListenableFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +211   * interface requiring a supported async or future value.
    +212   *
    +213   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +214   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +215   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +216   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +217   *
    +218   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +219   * class docs for more information.</p>
    +220   *
    +221   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +222   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +223   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +224   *
    +225   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +226   * @param future Future value to wrap.
    +227   * @param <R> Return value type for the future.
    +228   * @return Wrapped reactive future object.
    +229   * @throws IllegalArgumentException If the passed `future` is `null`.
    +230   */
    +231  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ListenableFuture<R> future) {
    +232    return wrap(future, MoreExecutors.directExecutor());
    +233  }
    +234
    +235  /**
    +236   * Wrap a Guava {@link ListenableFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +237   * interface requiring a supported async or future value.
    +238   *
    +239   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +240   * class docs for more information.</p>
    +241   *
    +242   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +243   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +244   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +245   *
    +246   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +247   * @param future Future value to wrap.
    +248   * @param executor Executor to dispatch callbacks with.
    +249   * @param <R> Return value type for the future.
    +250   * @return Wrapped reactive future object.
    +251   * @throws IllegalArgumentException If the passed `future` is `null`.
    +252   */
    +253  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ListenableFuture<R> future, @Nonnull Executor executor) {
    +254    //noinspection ConstantConditions
    +255    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` future.");
    +256    //noinspection ConstantConditions
    +257    if (executor == null) throw new IllegalArgumentException("Cannot wrap future with `null` executor.");
    +258    return new ReactiveFuture<>(future, executor);
    +259  }
    +260
    +261  /**
    +262   * Wrap a Google APIs {@link ApiFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +263   * interface requiring a supported async or future value.
    +264   *
    +265   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +266   * class docs for more information.</p>
    +267   *
    +268   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +269   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +270   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +271   *
    +272   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +273   * @see #wrap(ListenableFuture, Executor) Wraps a regular Guava {@link ListenableFuture}.
    +274   * @param apiFuture API future to wrap.
    +275   * @param executor Executor to run callbacks with.
    +276   * @param <R> Return value type for the future.
    +277   * @return Wrapped reactive future object.
    +278   * @throws IllegalArgumentException If the passed `apiFuture` is `null`.
    +279   */
    +280  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ApiFuture<R> apiFuture, @Nonnull Executor executor) {
    +281    //noinspection ConstantConditions
    +282    if (apiFuture == null) throw new IllegalArgumentException("Cannot wrap `null` API future.");
    +283    return wrap(new ApiFutureToListenableFuture<>(apiFuture), executor);
    +284  }
    +285
    +286  /**
    +287   * Wrap a Google APIs {@link ApiFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +288   * interface requiring a supported async or future value.
    +289   *
    +290   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +291   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +292   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +293   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +294   *
    +295   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +296   * class docs for more information.</p>
    +297   *
    +298   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +299   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +300   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +301   *
    +302   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +303   * @see #wrap(ListenableFuture, Executor) Wraps a regular Guava {@link ListenableFuture}.
    +304   * @param apiFuture API future to wrap.
    +305   * @param <R> Return value type for the future.
    +306   * @return Wrapped reactive future object.
    +307   * @throws IllegalArgumentException If the passed `apiFuture` is `null`.
    +308   */
    +309  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ApiFuture<R> apiFuture) {
    +310    return wrap(apiFuture, MoreExecutors.directExecutor());
    +311  }
    +312
    +313  /**
    +314   * Create an already-resolved future, wrapping the provided value. The future will present as done as soon as it is
    +315   * returned from this method.
    +316   *
    +317   * <p>Under the hood, this is simply a {@link ReactiveFuture} wrapping a call to
    +318   * {@link Futures#immediateFuture(Object)}.</p>
    +319   *
    +320   * @param value Value to wrap in an already-completed future.
    +321   * @param <R> Return value generic type.
    +322   * @return Reactive future wrapping a finished value.
    +323   */
    +324  public static @Nonnull <R> ReactiveFuture<R> done(@Nonnull R value) {
    +325    return wrap(Futures.immediateFuture(value));
    +326  }
    +327
    +328  /**
    +329   * Create an already-failed future, wrapping the provided exception instance. The future will present as one as soon
    +330   * as it is returned from this method.
    +331   *
    +332   * <p>Calling {@link Future#get(long, TimeUnit)} or {@link Future#get()} on a failed future will surface the
    +333   * associated exception where invocation occurs. Under the hood, this is simply a {@link ReactiveFuture} wrapping a
    +334   * call to {@link Futures#immediateFailedFuture(Throwable)}.</p>
    +335   *
    +336   * @param error Error to wrap in an already-failed future.
    +337   * @param <R> Return value generic type.
    +338   * @return Reactive future wrapping a finished value.
    +339   */
    +340  public static @Nonnull <R> ReactiveFuture<R> failed(@Nonnull Throwable error) {
    +341    return wrap(Futures.immediateFailedFuture(error));
    +342  }
    +343
    +344  /**
    +345   * Create an already-cancelled future. The future will present as both done and cancelled as soon as it is returned
    +346   * from this method.
    +347   *
    +348   * <p>Under the hood, this is simply a {@link ReactiveFuture} wrapping a call to
    +349   * {@link Futures#immediateCancelledFuture()}.</p>
    +350   *
    +351   * @param <R> Return value generic type.
    +352   * @return Reactive future wrapping a cancelled operation.
    +353   */
    +354  public static @Nonnull <R> ReactiveFuture<R> cancelled() {
    +355    return wrap(Futures.immediateCancelledFuture());
    +356  }
    +357
    +358  // -- Compliance: Publisher -- //
    +359  /**
    +360   * Request {@link Publisher} to start streaming data.
    +361   *
    +362   * <p>This is a "factory method" and can be called multiple times, each time starting a new {@link Subscription}. Each
    +363   * {@link Subscription} will work for only a single {@link Subscriber}. A {@link Subscriber} should only subscribe
    +364   * once to a single {@link Publisher}. If the {@link Publisher} rejects the subscription attempt or otherwise fails it
    +365   * will signal the error via {@link Subscriber#onError}.</p>
    +366   *
    +367   * @param subscriber the {@link Subscriber} that will consume signals from this {@link Publisher}.
    +368   */
    +369  @Override
    +370  public void subscribe(Subscriber<? super R> subscriber) {
    +371    resolvePublisher().subscribe(subscriber);
    +372  }
    +373
    +374  // -- Compliance: Listenable Future -- //
    +375  /**
    +376   * Registers a listener to be {@linkplain Executor#execute(Runnable) run} on the given executor. The listener will run
    +377   * when the {@code Future}'s computation is {@linkplain Future#isDone() complete} or, if the computation is already
    +378   * complete, immediately.
    +379   *
    +380   * <p>There is no guaranteed ordering of execution of listeners, but any listener added through this method is
    +381   * guaranteed to be called once the computation is complete.</p>
    +382   *
    +383   * <p>Exceptions thrown by a listener will be propagated up to the executor. Any exception thrown during
    +384   * {@code Executor.execute} (e.g., a {@code RejectedExecutionException} or an exception thrown by
    +385   * {@linkplain MoreExecutors#directExecutor direct execution}) will be caught and logged.</p>
    +386   *
    +387   * <p>Note: For fast, lightweight listeners that would be safe to execute in any thread, consider
    +388   * {@link MoreExecutors#directExecutor}. Otherwise, avoid it. Heavyweight {@code directExecutor} listeners can cause
    +389   * problems, and these problems can be difficult to reproduce because they depend on timing. For example:</p>
    +390   * <ul>
    +391   *   <li>The listener may be executed by the caller of {@code addListener}. That caller may be a
    +392   *       UI thread or other latency-sensitive thread. This can harm UI responsiveness.
    +393   *   <li>The listener may be executed by the thread that completes this {@code Future}. That
    +394   *       thread may be an internal system thread such as an RPC network thread. Blocking that
    +395   *       thread may stall progress of the whole system. It may even cause a deadlock.
    +396   *   <li>The listener may delay other listeners, even listeners that are not themselves {@code
    +397   *       directExecutor} listeners.
    +398   * </ul>
    +399   *
    +400   * <p>This is the most general listener interface. For common operations performed using listeners, see
    +401   * {@link Futures}. For a simplified but general listener interface, see
    +402   * {@link Futures#addCallback addCallback()}.</p>
    +403   *
    +404   * <p>Memory consistency effects: Actions in a thread prior to adding a listener <a
    +405   * href="https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5"><i>happen-before</i></a> its
    +406   * execution begins, perhaps in another thread.</p>
    +407   *
    +408   * <p>Guava implementations of {@code ListenableFuture} promptly release references to listeners after executing
    +409   * them.</p>
    +410   *
    +411   * @param listener the listener to run when the computation is complete.
    +412   * @param executor the executor to run the listener in
    +413   * @throws RejectedExecutionException if we tried to execute the listener immediately but the executor rejecte it.
    +414   */
    +415  @Override
    +416  public void addListener(@Nonnull Runnable listener, @Nonnull Executor executor) throws RejectedExecutionException {
    +417    resolveFuture().addListener(listener, executor);
    +418  }
    +419
    +420  /**
    +421   * Attempts to cancel execution of this task.  This attempt will fail if the task has already completed, has already
    +422   * been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when
    +423   * {@code cancel} is called, this task should never run.  If the task has already started, then the
    +424   * {@code mayInterruptIfRunning} parameter determines whether the thread executing this task should be interrupted in
    +425   * an attempt to stop the task.
    +426   *
    +427   * <p>After this method returns, subsequent calls to {@link #isDone} will always return {@code true}.  Subsequent
    +428   * calls to {@link #isCancelled} will always return {@code true} if this method returned {@code true}.
    +429   *
    +430   * @param mayInterruptIfRunning {@code true} if the thread executing this task should be interrupted; otherwise,
    +431   *                              in-progress tasks are allowed to complete
    +432   * @return {@code false} if the task could not be cancelled, typically because it has already completed normally;
    +433   *         {@code true} otherwise.
    +434   */
    +435  @Override
    +436  public boolean cancel(boolean mayInterruptIfRunning) {
    +437    return resolveFuture().cancel(mayInterruptIfRunning);
    +438  }
    +439
    +440  /**
    +441   * Returns {@code true} if this task was cancelled before it completed normally. This defers to the underlying future,
    +442   * or a wrapped object if using a {@link Publisher}.
    +443   *
    +444   * @return {@code true} if this task was cancelled before it completed
    +445   */
    +446  @Override
    +447  public boolean isCancelled() {
    +448    return resolveFuture().isCancelled();
    +449  }
    +450
    +451  /**
    +452   * Returns {@code true} if this task completed. This defers to the underlying future, or a wrapped object if using a
    +453   * Reactive Java {@link Publisher}.
    +454   *
    +455   * Completion may be due to normal termination, an exception, or cancellation -- in all of these cases, this method
    +456   * will return {@code true}.
    +457   *
    +458   * @return {@code true} if this task completed.
    +459   */
    +460  @Override
    +461  public boolean isDone() {
    +462    return resolveFuture().isDone();
    +463  }
    +464
    +465  /**
    +466   * Waits if necessary for the computation to complete, and then retrieves its result.
    +467   *
    +468   * <p>It is generally recommended to use the variant of this method which specifies a timeout - one must handle the
    +469   * additional {@link TimeoutException}, but on the other hand the computation can never infinitely block if an async
    +470   * value does not materialize.</p>
    +471   *
    +472   * @see #get(long, TimeUnit) For a safer version of this method, which allows specifying a timeout.
    +473   * @return the computed result.
    +474   * @throws CancellationException if the computation was cancelled
    +475   * @throws ExecutionException    if the computation threw an exception
    +476   * @throws InterruptedException  if the current thread was interrupted while waiting
    +477   */
    +478  @Override
    +479  public R get() throws InterruptedException, ExecutionException {
    +480    return resolveFuture().get();
    +481  }
    +482
    +483  /**
    +484   * Waits if necessary for at most the given time for the computation to complete, and then retrieves its result, if
    +485   * available.
    +486   *
    +487   * @param timeout the maximum time to wait
    +488   * @param unit    the time unit of the timeout argument
    +489   * @return the computed result
    +490   * @throws CancellationException if the computation was cancelled
    +491   * @throws ExecutionException    if the computation threw an exception
    +492   * @throws InterruptedException  if the current thread was interrupted while waiting
    +493   * @throws TimeoutException      if the wait timed out
    +494   */
    +495  @Override
    +496  public R get(long timeout, @Nonnull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
    +497    return resolveFuture().get(timeout, unit);
    +498  }
    +499
    +500  /**
    +501   * Structure that adapts a {@link Publisher} to a {@link ListenableFuture} interface. We accomplish this by
    +502   * immediately subscribing to the publisher with a callback that dispatches a {@link SettableFuture}.
    +503   *
    +504   * <p>This object is used in the specific circumstance of wrapping a {@link Publisher}, and then using the wrapped
    +505   * object as a {@link ListenableFuture} (or any descendent or compliant implementation thereof).</p>
    +506   *
    +507   * @param <T> Generic type returned by the future.
    +508   */
    +509  @Immutable
    +510  @ThreadSafe
    +511  public final static class PublisherListenableFuture<T> implements ListenableFuture<T>, Publisher<T> {
    +512    /** Whether we have received a value. */
    +513    private final @Nonnull AtomicBoolean received = new AtomicBoolean(false);
    +514
    +515    /** Whether we have completed acquiring a value. */
    +516    private final @Nonnull AtomicBoolean completed = new AtomicBoolean(false);
    +517
    +518    /** Whether we have been cancelled. */
    +519    private final @Nonnull AtomicBoolean cancelled = new AtomicBoolean(false);
    +520
    +521    /** Describes the list of proxied subscribers. */
    +522    private final @Nonnull Map<String, Subscriber<? super T>> subscribers = new ConcurrentHashMap<>();
    +523
    +524    /** Converted/pass-through future value. */
    +525    private final @Nonnull SettableFuture<T> future;
    +526
    +527    /** Subscription, so we can propagate cancellation. */
    +528    private volatile Subscription subscription;
    +529
    +530    /**
    +531     * Private constructor.
    +532     *
    +533     * @param publisher Publisher to wrap.
    +534     */
    +535    private PublisherListenableFuture(@Nonnull Publisher<T> publisher) {
    +536      this.future = SettableFuture.create();
    +537      publisher.subscribe(new Subscriber<T>() {
    +538        @Override
    +539        public void onSubscribe(Subscription s) {
    +540          PublisherListenableFuture.this.subscription = s;
    +541        }
    +542
    +543        @Override
    +544        public void onNext(T t) {
    +545          if (received.compareAndSet(false, true)) {
    +546            PublisherListenableFuture.this.proxyExecute((sub) -> sub.onNext(t));
    +547            future.set(t);
    +548            return;
    +549          }
    +550          this.onError(new IllegalStateException(
    +551            "Cannot publish multiple items through `ReactiveFuture`."));
    +552        }
    +553
    +554        @Override
    +555        public void onError(Throwable t) {
    +556          if (!completed.get()) {
    +557            PublisherListenableFuture.this.proxyExecute((sub) -> sub.onError(t));
    +558            future.setException(t);
    +559          }
    +560        }
    +561
    +562        @Override
    +563        public void onComplete() {
    +564          if (completed.compareAndSet(false, true)) {
    +565            PublisherListenableFuture.this.proxyExecute(Subscriber::onComplete);
    +566            PublisherListenableFuture.this.clear();
    +567          }
    +568        }
    +569      });
    +570    }
    +571
    +572    /**
    +573     * Call something on each proxied publisher subscription, if any.
    +574     *
    +575     * @param operation Operation to execute. Called for each subscriber.
    +576     */
    +577    private void proxyExecute(@Nonnull Consumer<Subscriber<? super T>> operation) {
    +578      if (!this.subscribers.isEmpty()) {
    +579        this.subscribers.values().forEach(operation);
    +580      }
    +581    }
    +582
    +583    /**
    +584     * Remove all subscribers and clear references to futures/publishers/listeners.
    +585     */
    +586    private void clear() {
    +587      this.subscribers.clear();
    +588      this.subscription = null;
    +589    }
    +590
    +591    /**
    +592     * Drop a subscription (after proxied {@link Subscription#cancel()} is called).
    +593     *
    +594     * @param id ID of the subscription to drop.
    +595     */
    +596    private void dropSubscription(@Nonnull String id) {
    +597      this.subscribers.get(id).onComplete();
    +598      this.subscribers.remove(id);
    +599    }
    +600
    +601    // -- Interface Compliance: Publisher -- //
    +602
    +603    @Override
    +604    public void subscribe(Subscriber<? super T> s) {
    +605      final String id = String.valueOf(this.subscribers.size());
    +606      Subscription sub = new Subscription() {
    +607        @Override
    +608        public void request(long n) {
    +609          PublisherListenableFuture.this.subscription.request(n);
    +610        }
    +611
    +612        @Override
    +613        public void cancel() {
    +614          // kill self
    +615          PublisherListenableFuture.this.dropSubscription(id);
    +616        }
    +617      };
    +618      this.subscribers.put(id, s);
    +619      s.onSubscribe(sub);
    +620    }
    +621
    +622    // -- Interface Compliance: Listenable Future -- //
    +623
    +624    @Override
    +625    public void addListener(@Nonnull Runnable runnable, @Nonnull Executor executor) {
    +626      this.future.addListener(runnable, executor);
    +627    }
    +628
    +629    @Override
    +630    public boolean cancel(boolean mayInterruptIfRunning) {
    +631      boolean cancelled = false;
    +632      if (!this.completed.get() && this.cancelled.compareAndSet(false, true)) {
    +633        this.proxyExecute(Subscriber::onComplete);  // dispatch `onComplete` for any subscribers
    +634        this.subscription.cancel();  // cancel upwards
    +635        cancelled = this.future.cancel(mayInterruptIfRunning);  // cancel future
    +636        this.clear();  // clear references
    +637      }
    +638      return cancelled;
    +639    }
    +640
    +641    @Override
    +642    public boolean isCancelled() {
    +643      return this.cancelled.get();
    +644    }
    +645
    +646    @Override
    +647    public boolean isDone() {
    +648      return this.completed.get() || this.cancelled.get();
    +649    }
    +650
    +651    @Override
    +652    public T get() throws InterruptedException, ExecutionException {
    +653      return this.future.get();
    +654    }
    +655
    +656    @Override
    +657    public T get(long timeout, @Nonnull TimeUnit unit)
    +658        throws InterruptedException, ExecutionException, TimeoutException {
    +659      return this.future.get(timeout, unit);
    +660    }
    +661  }
    +662
    +663  /**
    +664   * Structure that adapts Java's {@link CompletableFuture} to a Reactive Java {@link Publisher}, which publishes one
    +665   * item - either the result of the computation, or an error.
    +666   *
    +667   * <p>This object is used in the specific circumstance that a {@link CompletableFuture} is wrapped by a
    +668   * {@link ReactiveFuture}, and then used within the Reactive Java or Guava ecosystems as a {@link Publisher} or a
    +669   * {@link ListenableFuture} (or {@link ApiFuture}), or a descendent thereof. As in {@link ListenableFuturePublisher},
    +670   * we simply set the callback for the future value, upon item-request (one cycle is allowed), and propagate any events
    +671   * received to the publisher.</p>
    +672   *
    +673   * @param <T> Emit type for this adapter. Matches the future it wraps.
    +674   */
    +675  public final static class CompletableFuturePublisher<T>
    +676      implements Publisher<T>, ListenableFuture<T>, CompletionStage<T> {
    +677    private final @Nonnull CompletableFuture<T> future;
    +678    private final @Nonnull CompletionStage<T> stage;
    +679    private final @Nonnull Executor callbackExecutor;
    +680
    +681    /**
    +682     * Construct an adapter that propagates signals from a {@link CompletableFuture} to a {@link Publisher}.
    +683     *
    +684     * @param future Completable future to wrap.
    +685     * @param callbackExecutor Callback executor to use.
    +686     */
    +687    private CompletableFuturePublisher(@Nonnull CompletableFuture<T> future,
    +688                                       @Nonnull Executor callbackExecutor) {
    +689      this.future = future;
    +690      this.stage = future;
    +691      this.callbackExecutor = callbackExecutor;
    +692    }
    +693
    +694    /* == `Future`/`ListenableFuture` Interface Compliance == */
    +695
    +696    /** @inheritDoc */
    +697    @Override public final void subscribe(Subscriber<? super T> subscriber) {
    +698      Objects.requireNonNull(subscriber, "Subscriber cannot be null");
    +699      subscriber.onSubscribe(new CompletableFutureSubscription(this.future, subscriber, this.callbackExecutor));
    +700    }
    +701
    +702    /** @inheritDoc */
    +703    @Override public void addListener(Runnable runnable, Executor executor) {
    +704      future.thenRunAsync(runnable, executor);
    +705    }
    +706
    +707    /** @inheritDoc */
    +708    @Override public boolean cancel(boolean mayInterruptIfRunning) {
    +709      return future.cancel(mayInterruptIfRunning);
    +710    }
    +711
    +712    /** @inheritDoc */
    +713    @Override public boolean isCancelled() {
    +714      return future.isCancelled();
    +715    }
    +716
    +717    /** @inheritDoc */
    +718    @Override public boolean isDone() {
    +719      return future.isDone();
    +720    }
    +721
    +722    /** @inheritDoc */
    +723    @Override public T get() throws InterruptedException, ExecutionException {
    +724      return future.get();
    +725    }
    +726
    +727    /** @inheritDoc */
    +728    @Override
    +729    public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
    +730      return future.get(timeout, unit);
    +731    }
    +732
    +733    /* == `CompletionStage` Interface Compliance == */
    +734
    +735    /** @inheritDoc */
    +736    @Override public <U> CompletionStage<U> thenApply(Function<? super T, ? extends U> fn) {
    +737      return stage.thenApply(fn);
    +738    }
    +739
    +740    /** @inheritDoc */
    +741    @Override public <U> CompletionStage<U> thenApplyAsync(Function<? super T, ? extends U> fn) {
    +742      return stage.thenApplyAsync(fn);
    +743    }
    +744
    +745    /** @inheritDoc */
    +746    @Override public <U> CompletionStage<U> thenApplyAsync(Function<? super T, ? extends U> fn, Executor executor) {
    +747      return stage.thenApplyAsync(fn, executor);
    +748    }
    +749
    +750    /** @inheritDoc */
    +751    @Override public CompletionStage<Void> thenAccept(Consumer<? super T> action) {
    +752      return stage.thenAccept(action);
    +753    }
    +754
    +755    /** @inheritDoc */
    +756    @Override public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action) {
    +757      return stage.thenAcceptAsync(action);
    +758    }
    +759
    +760    /** @inheritDoc */
    +761    @Override public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor) {
    +762      return stage.thenAcceptAsync(action, executor);
    +763    }
    +764
    +765    /** @inheritDoc */
    +766    @Override public CompletionStage<Void> thenRun(Runnable action) {
    +767      return stage.thenRun(action);
    +768    }
    +769
    +770    /** @inheritDoc */
    +771    @Override public CompletionStage<Void> thenRunAsync(Runnable action) {
    +772      return stage.thenRunAsync(action);
    +773    }
    +774
    +775    /** @inheritDoc */
    +776    @Override public CompletionStage<Void> thenRunAsync(Runnable action, Executor executor) {
    +777      return stage.thenRunAsync(action, executor);
    +778    }
    +779
    +780    /** @inheritDoc */
    +781    @Override public <U, V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,
    +782                                                           BiFunction<? super T, ? super U, ? extends V> fn) {
    +783      return stage.thenCombine(other, fn);
    +784    }
    +785
    +786    /** @inheritDoc */
    +787    @Override public <U, V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,
    +788                                                                BiFunction<? super T, ? super U, ? extends V> fn) {
    +789      return stage.thenCombineAsync(other, fn);
    +790    }
    +791
    +792    /** @inheritDoc */
    +793    @Override public <U, V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,
    +794                                                                BiFunction<? super T, ? super U, ? extends V> fn,
    +795                                                                Executor executor) {
    +796      return stage.thenCombineAsync(other, fn, executor);
    +797    }
    +798
    +799    /** @inheritDoc */
    +800    @Override public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,
    +801                                                              BiConsumer<? super T, ? super U> action) {
    +802      return stage.thenAcceptBoth(other, action);
    +803    }
    +804
    +805    /** @inheritDoc */
    +806    @Override public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
    +807                                                                   BiConsumer<? super T, ? super U> action) {
    +808      return stage.thenAcceptBothAsync(other, action);
    +809    }
    +810
    +811    /** @inheritDoc */
    +812    @Override
    +813    public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
    +814                                                         BiConsumer<? super T, ? super U> action,
    +815                                                         Executor executor) {
    +816      return stage.thenAcceptBothAsync(other, action, executor);
    +817    }
    +818
    +819    /** @inheritDoc */
    +820    @Override
    +821    public CompletionStage<Void> runAfterBoth(CompletionStage<?> other, Runnable action) {
    +822      return stage.runAfterBoth(other, action);
    +823    }
    +824
    +825    /** @inheritDoc */
    +826    @Override
    +827    public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action) {
    +828      return stage.runAfterBothAsync(other, action);
    +829    }
    +830
    +831    /** @inheritDoc */
    +832    @Override
    +833    public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action, Executor executor) {
    +834      return stage.runAfterBothAsync(other, action, executor);
    +835    }
    +836
    +837    /** @inheritDoc */
    +838    @Override
    +839    public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn) {
    +840      return stage.applyToEither(other, fn);
    +841    }
    +842
    +843    /** @inheritDoc */
    +844    @Override
    +845    public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn) {
    +846      return stage.applyToEitherAsync(other, fn);
    +847    }
    +848
    +849    /** @inheritDoc */
    +850    @Override
    +851    public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,
    +852                                                     Function<? super T, U> fn,
    +853                                                     Executor executor) {
    +854      return stage.applyToEitherAsync(other, fn, executor);
    +855    }
    +856
    +857    /** @inheritDoc */
    +858    @Override public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,
    +859                                                        Consumer<? super T> action) {
    +860      return stage.acceptEither(other, action);
    +861    }
    +862
    +863    /** @inheritDoc */
    +864    @Override public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,
    +865                                                             Consumer<? super T> action) {
    +866      return stage.acceptEitherAsync(other, action);
    +867    }
    +868
    +869    /** @inheritDoc */
    +870    @Override public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,
    +871                                                             Consumer<? super T> action,
    +872                                                             Executor executor) {
    +873      return stage.acceptEitherAsync(other, action, executor);
    +874    }
    +875
    +876    /** @inheritDoc */
    +877    @Override public CompletionStage<Void> runAfterEither(CompletionStage<?> other, Runnable action) {
    +878      return stage.runAfterEither(other, action);
    +879    }
    +880
    +881    /** @inheritDoc */
    +882    @Override public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action) {
    +883      return stage.runAfterEitherAsync(other, action);
    +884    }
    +885
    +886    /** @inheritDoc */
    +887    @Override public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action, Executor executor) {
    +888      return stage.runAfterEitherAsync(other, action, executor);
    +889    }
    +890
    +891    /** @inheritDoc */
    +892    @Override public <U> CompletionStage<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) {
    +893      return stage.thenCompose(fn);
    +894    }
    +895
    +896    /** @inheritDoc */
    +897    @Override public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) {
    +898      return stage.thenComposeAsync(fn);
    +899    }
    +900
    +901    /** @inheritDoc */
    +902    @Override public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn,
    +903                                                             Executor executor) {
    +904      return stage.thenComposeAsync(fn, executor);
    +905    }
    +906
    +907    /** @inheritDoc */
    +908    @Override public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) {
    +909      return stage.handle(fn);
    +910    }
    +911
    +912    /** @inheritDoc */
    +913    @Override public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) {
    +914      return stage.handleAsync(fn);
    +915    }
    +916
    +917    /** @inheritDoc */
    +918    @Override public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,
    +919                                                        Executor executor) {
    +920      return stage.handleAsync(fn, executor);
    +921    }
    +922
    +923    /** @inheritDoc */
    +924    @Override public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> action) {
    +925      return stage.whenComplete(action);
    +926    }
    +927
    +928    /** @inheritDoc */
    +929    @Override public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action) {
    +930      return stage.whenCompleteAsync(action);
    +931    }
    +932
    +933    /** @inheritDoc */
    +934    @Override public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action,
    +935                                                          Executor executor) {
    +936      return stage.whenCompleteAsync(action, executor);
    +937    }
    +938
    +939    /** @inheritDoc */
    +940    @Override public CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn) {
    +941      return stage.exceptionally(fn);
    +942    }
    +943
    +944    /** @inheritDoc */
    +945    @Override public CompletableFuture<T> toCompletableFuture() {
    +946      return stage.toCompletableFuture();
    +947    }
    +948
    +949    /**
    +950     * Models a Reactive Java {@link Subscription}, which is responsible for propagating events from a
    +951     * Concurrent Java {@link CompletableFuture} to a {@link Subscriber}.
    +952     *
    +953     * <p>This object is generally used internally by the {@link CompletableFuturePublisher}, once a {@link Subscriber}
    +954     * attaches itself to a {@link Publisher} that is actually a wrapped {@link CompletableFuture}. Error (exception)
    +955     * events and value events are both propagated. Subscribers based on this wrapping will only ever receive a maximum
    +956     * of <b>one value</b> or <b>one error</b>.</p>
    +957     */
    +958    @Immutable
    +959    @ThreadSafe
    +960    public final class CompletableFutureSubscription implements Subscription {
    +961      private final AtomicBoolean completed = new AtomicBoolean(false);
    +962      private final @Nonnull Subscriber<? super T> subscriber;
    +963      private final @Nonnull CompletableFuture<T> future;
    +964      private final @Nonnull Executor executor;
    +965
    +966      /**
    +967       * Private constructor, meant for use by {@link CompletableFuturePublisher} only.
    +968       *
    +969       * @param future Future value to adapt.
    +970       * @param subscriber The subscriber.
    +971       * @param executor Executor to run callbacks with.
    +972       */
    +973      CompletableFutureSubscription(@Nonnull CompletableFuture<T> future,
    +974                                    @Nonnull Subscriber<? super T> subscriber,
    +975                                    @Nonnull Executor executor) {
    +976        this.future = Objects.requireNonNull(future);
    +977        this.subscriber = Objects.requireNonNull(subscriber);
    +978        this.executor = Objects.requireNonNull(executor);
    +979      }
    +980
    +981      /**
    +982       * Request the specified number of items from the underlying {@link Subscription}. This must <b>always be
    +983       * <pre>1</pre></b>.
    +984       *
    +985       * @param n Number of elements to request to the upstream (must always be <pre>1</pre>).
    +986       * @throws IllegalArgumentException If any value other than <pre>1</pre> is passed in.
    +987       */
    +988      public synchronized void request(long n) {
    +989        if (n == 1 && !completed.get()) {
    +990          try {
    +991            CompletableFuture<T> future = this.future;
    +992            future.thenAcceptAsync(t -> {
    +993              T val = null;
    +994              Throwable err = null;
    +995              try {
    +996                val = future.get();
    +997              } catch (Exception exc) {
    +998                err = exc;
    +999              }
    +1000
    +1001              if (completed.compareAndSet(false, true)) {
    +1002                if (err != null) {
    +1003                  subscriber.onError(err);
    +1004                } else {
    +1005                  if (val != null) {
    +1006                    subscriber.onNext(val);
    +1007                  }
    +1008                  subscriber.onComplete();
    +1009                }
    +1010              }
    +1011            }, executor);
    +1012
    +1013          } catch (Exception e) {
    +1014            subscriber.onError(e);
    +1015          }
    +1016        } else if (n != 1) {
    +1017          IllegalArgumentException ex = new IllegalArgumentException(
    +1018              "Cannot request more or less than 1 item from a ReactiveFuture-wrapped publisher.");
    +1019          subscriber.onError(ex);
    +1020        }
    +1021      }
    +1022
    +1023      /**
    +1024       * Request the publisher to stop sending data and clean up resources.
    +1025       */
    +1026      public synchronized void cancel() {
    +1027        if (completed.compareAndSet(false, true)) {
    +1028          subscriber.onComplete();
    +1029          future.cancel(false);
    +1030        }
    +1031      }
    +1032    }
    +1033  }
    +1034
    +1035  /**
    +1036   * Structure that adapts Guava's {@link ListenableFuture} to a Reactive Java {@link Publisher}, which publishes one
    +1037   * item - either the result of the computation, or an error.
    +1038   *
    +1039   * <p>This object is used in the specific circumstance that a {@link ListenableFuture} is wrapped by a
    +1040   * {@link ReactiveFuture}, and then used within the Reactive Java ecosystem as a {@link Publisher}. We simply set a
    +1041   * callback for the future value, upon item-request (one cycle is allowed), and propagate any events received to the
    +1042   * publisher.</p>
    +1043   *
    +1044   * @param <T> Emit type for this adapter. Matches the publisher it wraps.
    +1045   */
    +1046  public final static class ListenableFuturePublisher<T> implements Publisher<T> {
    +1047    private final @Nonnull ListenableFuture<T> future;
    +1048    private final @Nonnull Executor callbackExecutor;
    +1049
    +1050    /**
    +1051     * Wrap a {@link ListenableFuture}. Private constructor for use by {@link ReactiveFuture} only.
    +1052     *
    +1053     * @param future The future to convert or wait on.
    +1054     * @param callbackExecutor Executor to run the callback on.
    +1055     */
    +1056    private ListenableFuturePublisher(@Nonnull ListenableFuture<T> future,
    +1057                                      @Nonnull Executor callbackExecutor) {
    +1058      this.future = future;
    +1059      this.callbackExecutor = callbackExecutor;
    +1060    }
    +1061
    +1062    @Override
    +1063    public final void subscribe(Subscriber<? super T> subscriber) {
    +1064      Objects.requireNonNull(subscriber, "Subscriber cannot be null");
    +1065      subscriber.onSubscribe(new ListenableFutureSubscription(this.future, subscriber, this.callbackExecutor));
    +1066    }
    +1067
    +1068    /**
    +1069     * Models a Reactive Java {@link Subscription}, which is responsible for propagating events from a
    +1070     * {@link ListenableFuture} to a {@link Subscriber}.
    +1071     *
    +1072     * <p>This object is generally used internally by the {@link ListenableFuturePublisher}, once a {@link Subscriber}
    +1073     * attaches itself to a {@link Publisher} that is actually a wrapped {@link ListenableFuture}. Error (exception)
    +1074     * events and value events are both propagated. Subscribers based on this wrapping will only ever receive a maximum
    +1075     * of <b>one value</b> or <b>one error</b>.</p>
    +1076     */
    +1077    @Immutable
    +1078    @ThreadSafe
    +1079    public final class ListenableFutureSubscription implements Subscription {
    +1080      private final AtomicBoolean completed = new AtomicBoolean(false);
    +1081      private final @Nonnull Subscriber<? super T> subscriber;
    +1082      private final @Nonnull ListenableFuture<T> future; // to allow cancellation
    +1083      private final @Nonnull Executor executor;  // executor to use when dispatching the callback
    +1084
    +1085      /**
    +1086       * Private constructor, meant for use by {@link ListenableFuturePublisher} only.
    +1087       *
    +1088       * @param future Future value to adapt.
    +1089       * @param subscriber The subscriber.
    +1090       * @param executor Executor to run callbacks with.
    +1091       */
    +1092      ListenableFutureSubscription(@Nonnull ListenableFuture<T> future,
    +1093                                   @Nonnull Subscriber<? super T> subscriber,
    +1094                                   @Nonnull Executor executor) {
    +1095        this.future = Objects.requireNonNull(future);
    +1096        this.subscriber = Objects.requireNonNull(subscriber);
    +1097        this.executor = Objects.requireNonNull(executor);
    +1098      }
    +1099
    +1100      /**
    +1101       * Request the specified number of items from the underlying {@link Subscription}. This must <b>always be
    +1102       * <pre>1</pre></b>.
    +1103       *
    +1104       * @param n Number of elements to request to the upstream (must always be <pre>1</pre>).
    +1105       * @throws IllegalArgumentException If any value other than <pre>1</pre> is passed in.
    +1106       */
    +1107      public synchronized void request(long n) {
    +1108        if (n == 1 && !completed.get()) {
    +1109          try {
    +1110            ListenableFuture<T> future = this.future;
    +1111            future.addListener(() -> {
    +1112              T val = null;
    +1113              Throwable err = null;
    +1114              try {
    +1115                val = this.future.get();
    +1116              } catch (Exception exc) {
    +1117                err = exc;
    +1118              }
    +1119
    +1120              if (completed.compareAndSet(false, true)) {
    +1121                if (err != null) {
    +1122                  subscriber.onError(err);
    +1123                } else {
    +1124                  if (val != null) {
    +1125                    subscriber.onNext(val);
    +1126                  }
    +1127                  subscriber.onComplete();
    +1128                }
    +1129              }
    +1130            }, this.executor);
    +1131          } catch (Exception e) {
    +1132            subscriber.onError(e);
    +1133          }
    +1134        } else if (n != 1) {
    +1135          IllegalArgumentException ex = new IllegalArgumentException(
    +1136            "Cannot request more or less than 1 item from a ReactiveFuture-wrapped publisher.");
    +1137          subscriber.onError(ex);
    +1138        }
    +1139      }
    +1140
    +1141      /**
    +1142       * Request the publisher to stop sending data and clean up resources.
    +1143       */
    +1144      public synchronized void cancel() {
    +1145        if (completed.compareAndSet(false, true)) {
    +1146          subscriber.onComplete();
    +1147          future.cancel(false);
    +1148        }
    +1149      }
    +1150    }
    +1151  }
    +1152}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/runtime/ReactiveFuture.ListenableFuturePublisher.html b/docs/java/src-html/gust/backend/runtime/ReactiveFuture.ListenableFuturePublisher.html new file mode 100644 index 000000000..a365627a5 --- /dev/null +++ b/docs/java/src-html/gust/backend/runtime/ReactiveFuture.ListenableFuturePublisher.html @@ -0,0 +1,1226 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.runtime;
    +014
    +015import com.google.api.core.ApiFuture;
    +016import com.google.api.core.ApiFutureToListenableFuture;
    +017import com.google.common.util.concurrent.Futures;
    +018import com.google.common.util.concurrent.ListenableFuture;
    +019import com.google.common.util.concurrent.MoreExecutors;
    +020import com.google.common.util.concurrent.SettableFuture;
    +021import org.reactivestreams.Publisher;
    +022import org.reactivestreams.Subscriber;
    +023import org.reactivestreams.Subscription;
    +024
    +025import javax.annotation.Nonnull;
    +026import javax.annotation.Nullable;
    +027import javax.annotation.concurrent.Immutable;
    +028import javax.annotation.concurrent.ThreadSafe;
    +029import java.util.*;
    +030import java.util.concurrent.*;
    +031import java.util.concurrent.atomic.AtomicBoolean;
    +032import java.util.function.BiConsumer;
    +033import java.util.function.BiFunction;
    +034import java.util.function.Consumer;
    +035import java.util.function.Function;
    +036
    +037
    +038/**
    +039 * Adapts future/async value containers from different frameworks (namely, Reactive Java, Guava, and the JDK).
    +040 *
    +041 * <p>Create a new {@link ReactiveFuture} by using any of the {@code wrap)} factory methods. The resulting object is
    +042 * usable as a {@link Publisher}, {@link ListenableFuture}, or {@link ApiFuture}. This object simply wraps whatever
    +043 * inner object is provided, and as such instances are lightweight; there is no default functionality after immediate
    +044 * construction in most cases.</p>
    +045 *
    +046 * <p><b>Caveat:</b> when using a {@link Publisher} as a {@link ListenableFuture} (i.e. wrapping a {@link Publisher} and
    +047 * then using any of the typical future methods, like {@link ListenableFuture#addListener(Runnable, Executor)}), the
    +048 * underlying publisher may not publish more than one value. This is to prevent dropping intermediate values on the
    +049 * floor, silently, before dispatching the future's callbacks, which generally only accept one value. Other than this,
    +050 * things should work "as expected" whether you're looking at them from a Guava, JDK, or Reactive perspective.</p>
    +051 *
    +052 * @see Publisher Reactive Java type adapted by this object.
    +053 * @see ListenableFuture Guava's extension of the JDK's basic {@link Future}, which adds listener support.
    +054 * @see ApiFuture Lightweight Guava-like future meant to avoid dependencies on Java in API libraries.
    +055 * @see #wrap(Publisher) To wrap a {@link Publisher}.
    +056 * @see #wrap(ListenableFuture, Executor) To wrap a {@link ListenableFuture}.
    +057 * @see #wrap(ApiFuture, Executor) To wrap an {@link ApiFuture}.
    +058 */
    +059@Immutable
    +060@ThreadSafe
    +061@SuppressWarnings("UnstableApiUsage")
    +062public final class ReactiveFuture<R> implements Publisher<R>, ListenableFuture<R>, ApiFuture<R> {
    +063  /** Inner future, if one is set. Otherwise {@link Optional#empty()}. */
    +064  private final @Nonnull Optional<ListenableFuture<R>> future;
    +065
    +066  /** If a `publisher` is present, this object adapts it to a `future`. */
    +067  private final @Nullable PublisherListenableFuture<R> publisherAdapter;
    +068
    +069  /** If a `future` is present, this object adapts it to a `publisher`. */
    +070  private final @Nullable ListenableFuturePublisher<R> futureAdapter;
    +071
    +072  /** If a `future` is present, this object adapts it to a `publisher`. */
    +073  private final @Nullable CompletableFuturePublisher<R> javaFutureAdapter;
    +074
    +075  /**
    +076   * Spawn a reactive/future adapter in a reactive context, from a {@link Publisher}. Constructing a reactive future in
    +077   * this manner causes the object to operate in a "publisher-backed" mode.
    +078   *
    +079   * @param publisher Publisher to work with.
    +080   */
    +081  private ReactiveFuture(@Nonnull Publisher<R> publisher) {
    +082    this.future = Optional.empty();
    +083    this.futureAdapter = null;
    +084    this.publisherAdapter = new PublisherListenableFuture<>(publisher);
    +085    this.javaFutureAdapter = null;
    +086  }
    +087
    +088  /**
    +089   * Spawn a reactive/future adapter in a future context, from a {@link ListenableFuture}. Constructing a reactive
    +090   * future in this manner causes the object to operate in a "future-backed" mode.
    +091   *
    +092   * @param future Future to work with.
    +093   * @param executor Executor to use when running callbacks.
    +094   */
    +095  private ReactiveFuture(@Nonnull ListenableFuture<R> future, @Nonnull Executor executor) {
    +096    this.future = Optional.of(future);
    +097    this.futureAdapter = new ListenableFuturePublisher<>(future, executor);
    +098    this.publisherAdapter = null;
    +099    this.javaFutureAdapter = null;
    +100  }
    +101
    +102  /**
    +103   * Spawn a reactive/future adapter in a future context, from a {@link CompletableFuture}. Constructing a reactive
    +104   * future in this manner causes the object to operate in a "future-backed" mode.
    +105   *
    +106   * @param future Future to work with.
    +107   * @param executor Executor to use when running callbacks.
    +108   */
    +109  private ReactiveFuture(@Nonnull CompletableFuture<R> future, @Nonnull Executor executor) {
    +110    this.future = Optional.empty();
    +111    this.futureAdapter = null;
    +112    this.publisherAdapter = null;
    +113    this.javaFutureAdapter = new CompletableFuturePublisher<>(future, executor);
    +114  }
    +115
    +116  /** @return Internal future representation. */
    +117  private @Nonnull ListenableFuture<R> resolveFuture() {
    +118    if (this.publisherAdapter != null)
    +119      return this.publisherAdapter;
    +120    else if (this.javaFutureAdapter != null)
    +121      return this.javaFutureAdapter;
    +122    //noinspection OptionalGetWithoutIsPresent
    +123    return this.future.get();
    +124  }
    +125
    +126  /** @return Internal publisher representation. */
    +127  private @Nonnull Publisher<R> resolvePublisher() {
    +128    if (this.futureAdapter != null)
    +129      return this.futureAdapter;
    +130    else if (this.javaFutureAdapter != null)
    +131      return this.javaFutureAdapter;
    +132    return Objects.requireNonNull(this.publisherAdapter);
    +133  }
    +134
    +135  // -- Public API -- //
    +136  /**
    +137   * Wrap a Reactive Java {@link Publisher} in a universal {@link ReactiveFuture}, such that it may be used with any
    +138   * interface requiring a supported async or future value.
    +139   *
    +140   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +141   * class docs for more information.</p>
    +142   *
    +143   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +144   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +145   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +146   *
    +147   * @see #wrap(ListenableFuture, Executor) Wraps a {@link ListenableFuture} from Guava.
    +148   * @param publisher Reactive publisher to wrap.
    +149   * @param <R> Return or emission type of the publisher.
    +150   * @return Wrapped reactive future object.
    +151   * @throws IllegalArgumentException If the passed `publisher` is `null`.
    +152   */
    +153  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull Publisher<R> publisher) {
    +154    //noinspection ConstantConditions
    +155    if (publisher == null) throw new IllegalArgumentException("Cannot wrap `null` publisher.");
    +156    return new ReactiveFuture<>(publisher);
    +157  }
    +158
    +159  /**
    +160   * Wrap a regular Java {@link CompletableFuture} in a universal {@link ReactiveFuture}, such that it may be used with
    +161   * any interface requiring support for that class.
    +162   *
    +163   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +164   * class docs for more information.</p>
    +165   *
    +166   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +167   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +168   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +169   *
    +170   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +171   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +172   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +173   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +174   *
    +175   * @param future Completable future to wrap.
    +176   * @param <R> Return or emission type of the future.
    +177   * @return Wrapped reactive future object.
    +178   */
    +179  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull CompletableFuture<R> future) {
    +180    //noinspection ConstantConditions
    +181    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` publisher.");
    +182    return wrap(future, MoreExecutors.directExecutor());
    +183  }
    +184
    +185  /**
    +186   * Wrap a regular Java {@link CompletableFuture} in a universal {@link ReactiveFuture}, such that it may be used with
    +187   * any interface requiring support for that class.
    +188   *
    +189   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +190   * class docs for more information.</p>
    +191   *
    +192   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +193   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +194   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +195   *
    +196   * @param future Completable future to wrap.
    +197   * @param executor Executor to use.
    +198   * @param <R> Return or emission type of the future.
    +199   * @return Wrapped reactive future object.
    +200   */
    +201  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull CompletableFuture<R> future, @Nonnull Executor executor) {
    +202    //noinspection ConstantConditions
    +203    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` future.");
    +204    //noinspection ConstantConditions
    +205    if (executor == null) throw new IllegalArgumentException("Cannot wrap future with `null` executor.");
    +206    return new ReactiveFuture<>(future, executor);
    +207  }
    +208
    +209  /**
    +210   * Wrap a Guava {@link ListenableFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +211   * interface requiring a supported async or future value.
    +212   *
    +213   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +214   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +215   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +216   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +217   *
    +218   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +219   * class docs for more information.</p>
    +220   *
    +221   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +222   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +223   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +224   *
    +225   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +226   * @param future Future value to wrap.
    +227   * @param <R> Return value type for the future.
    +228   * @return Wrapped reactive future object.
    +229   * @throws IllegalArgumentException If the passed `future` is `null`.
    +230   */
    +231  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ListenableFuture<R> future) {
    +232    return wrap(future, MoreExecutors.directExecutor());
    +233  }
    +234
    +235  /**
    +236   * Wrap a Guava {@link ListenableFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +237   * interface requiring a supported async or future value.
    +238   *
    +239   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +240   * class docs for more information.</p>
    +241   *
    +242   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +243   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +244   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +245   *
    +246   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +247   * @param future Future value to wrap.
    +248   * @param executor Executor to dispatch callbacks with.
    +249   * @param <R> Return value type for the future.
    +250   * @return Wrapped reactive future object.
    +251   * @throws IllegalArgumentException If the passed `future` is `null`.
    +252   */
    +253  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ListenableFuture<R> future, @Nonnull Executor executor) {
    +254    //noinspection ConstantConditions
    +255    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` future.");
    +256    //noinspection ConstantConditions
    +257    if (executor == null) throw new IllegalArgumentException("Cannot wrap future with `null` executor.");
    +258    return new ReactiveFuture<>(future, executor);
    +259  }
    +260
    +261  /**
    +262   * Wrap a Google APIs {@link ApiFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +263   * interface requiring a supported async or future value.
    +264   *
    +265   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +266   * class docs for more information.</p>
    +267   *
    +268   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +269   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +270   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +271   *
    +272   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +273   * @see #wrap(ListenableFuture, Executor) Wraps a regular Guava {@link ListenableFuture}.
    +274   * @param apiFuture API future to wrap.
    +275   * @param executor Executor to run callbacks with.
    +276   * @param <R> Return value type for the future.
    +277   * @return Wrapped reactive future object.
    +278   * @throws IllegalArgumentException If the passed `apiFuture` is `null`.
    +279   */
    +280  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ApiFuture<R> apiFuture, @Nonnull Executor executor) {
    +281    //noinspection ConstantConditions
    +282    if (apiFuture == null) throw new IllegalArgumentException("Cannot wrap `null` API future.");
    +283    return wrap(new ApiFutureToListenableFuture<>(apiFuture), executor);
    +284  }
    +285
    +286  /**
    +287   * Wrap a Google APIs {@link ApiFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +288   * interface requiring a supported async or future value.
    +289   *
    +290   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +291   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +292   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +293   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +294   *
    +295   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +296   * class docs for more information.</p>
    +297   *
    +298   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +299   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +300   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +301   *
    +302   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +303   * @see #wrap(ListenableFuture, Executor) Wraps a regular Guava {@link ListenableFuture}.
    +304   * @param apiFuture API future to wrap.
    +305   * @param <R> Return value type for the future.
    +306   * @return Wrapped reactive future object.
    +307   * @throws IllegalArgumentException If the passed `apiFuture` is `null`.
    +308   */
    +309  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ApiFuture<R> apiFuture) {
    +310    return wrap(apiFuture, MoreExecutors.directExecutor());
    +311  }
    +312
    +313  /**
    +314   * Create an already-resolved future, wrapping the provided value. The future will present as done as soon as it is
    +315   * returned from this method.
    +316   *
    +317   * <p>Under the hood, this is simply a {@link ReactiveFuture} wrapping a call to
    +318   * {@link Futures#immediateFuture(Object)}.</p>
    +319   *
    +320   * @param value Value to wrap in an already-completed future.
    +321   * @param <R> Return value generic type.
    +322   * @return Reactive future wrapping a finished value.
    +323   */
    +324  public static @Nonnull <R> ReactiveFuture<R> done(@Nonnull R value) {
    +325    return wrap(Futures.immediateFuture(value));
    +326  }
    +327
    +328  /**
    +329   * Create an already-failed future, wrapping the provided exception instance. The future will present as one as soon
    +330   * as it is returned from this method.
    +331   *
    +332   * <p>Calling {@link Future#get(long, TimeUnit)} or {@link Future#get()} on a failed future will surface the
    +333   * associated exception where invocation occurs. Under the hood, this is simply a {@link ReactiveFuture} wrapping a
    +334   * call to {@link Futures#immediateFailedFuture(Throwable)}.</p>
    +335   *
    +336   * @param error Error to wrap in an already-failed future.
    +337   * @param <R> Return value generic type.
    +338   * @return Reactive future wrapping a finished value.
    +339   */
    +340  public static @Nonnull <R> ReactiveFuture<R> failed(@Nonnull Throwable error) {
    +341    return wrap(Futures.immediateFailedFuture(error));
    +342  }
    +343
    +344  /**
    +345   * Create an already-cancelled future. The future will present as both done and cancelled as soon as it is returned
    +346   * from this method.
    +347   *
    +348   * <p>Under the hood, this is simply a {@link ReactiveFuture} wrapping a call to
    +349   * {@link Futures#immediateCancelledFuture()}.</p>
    +350   *
    +351   * @param <R> Return value generic type.
    +352   * @return Reactive future wrapping a cancelled operation.
    +353   */
    +354  public static @Nonnull <R> ReactiveFuture<R> cancelled() {
    +355    return wrap(Futures.immediateCancelledFuture());
    +356  }
    +357
    +358  // -- Compliance: Publisher -- //
    +359  /**
    +360   * Request {@link Publisher} to start streaming data.
    +361   *
    +362   * <p>This is a "factory method" and can be called multiple times, each time starting a new {@link Subscription}. Each
    +363   * {@link Subscription} will work for only a single {@link Subscriber}. A {@link Subscriber} should only subscribe
    +364   * once to a single {@link Publisher}. If the {@link Publisher} rejects the subscription attempt or otherwise fails it
    +365   * will signal the error via {@link Subscriber#onError}.</p>
    +366   *
    +367   * @param subscriber the {@link Subscriber} that will consume signals from this {@link Publisher}.
    +368   */
    +369  @Override
    +370  public void subscribe(Subscriber<? super R> subscriber) {
    +371    resolvePublisher().subscribe(subscriber);
    +372  }
    +373
    +374  // -- Compliance: Listenable Future -- //
    +375  /**
    +376   * Registers a listener to be {@linkplain Executor#execute(Runnable) run} on the given executor. The listener will run
    +377   * when the {@code Future}'s computation is {@linkplain Future#isDone() complete} or, if the computation is already
    +378   * complete, immediately.
    +379   *
    +380   * <p>There is no guaranteed ordering of execution of listeners, but any listener added through this method is
    +381   * guaranteed to be called once the computation is complete.</p>
    +382   *
    +383   * <p>Exceptions thrown by a listener will be propagated up to the executor. Any exception thrown during
    +384   * {@code Executor.execute} (e.g., a {@code RejectedExecutionException} or an exception thrown by
    +385   * {@linkplain MoreExecutors#directExecutor direct execution}) will be caught and logged.</p>
    +386   *
    +387   * <p>Note: For fast, lightweight listeners that would be safe to execute in any thread, consider
    +388   * {@link MoreExecutors#directExecutor}. Otherwise, avoid it. Heavyweight {@code directExecutor} listeners can cause
    +389   * problems, and these problems can be difficult to reproduce because they depend on timing. For example:</p>
    +390   * <ul>
    +391   *   <li>The listener may be executed by the caller of {@code addListener}. That caller may be a
    +392   *       UI thread or other latency-sensitive thread. This can harm UI responsiveness.
    +393   *   <li>The listener may be executed by the thread that completes this {@code Future}. That
    +394   *       thread may be an internal system thread such as an RPC network thread. Blocking that
    +395   *       thread may stall progress of the whole system. It may even cause a deadlock.
    +396   *   <li>The listener may delay other listeners, even listeners that are not themselves {@code
    +397   *       directExecutor} listeners.
    +398   * </ul>
    +399   *
    +400   * <p>This is the most general listener interface. For common operations performed using listeners, see
    +401   * {@link Futures}. For a simplified but general listener interface, see
    +402   * {@link Futures#addCallback addCallback()}.</p>
    +403   *
    +404   * <p>Memory consistency effects: Actions in a thread prior to adding a listener <a
    +405   * href="https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5"><i>happen-before</i></a> its
    +406   * execution begins, perhaps in another thread.</p>
    +407   *
    +408   * <p>Guava implementations of {@code ListenableFuture} promptly release references to listeners after executing
    +409   * them.</p>
    +410   *
    +411   * @param listener the listener to run when the computation is complete.
    +412   * @param executor the executor to run the listener in
    +413   * @throws RejectedExecutionException if we tried to execute the listener immediately but the executor rejecte it.
    +414   */
    +415  @Override
    +416  public void addListener(@Nonnull Runnable listener, @Nonnull Executor executor) throws RejectedExecutionException {
    +417    resolveFuture().addListener(listener, executor);
    +418  }
    +419
    +420  /**
    +421   * Attempts to cancel execution of this task.  This attempt will fail if the task has already completed, has already
    +422   * been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when
    +423   * {@code cancel} is called, this task should never run.  If the task has already started, then the
    +424   * {@code mayInterruptIfRunning} parameter determines whether the thread executing this task should be interrupted in
    +425   * an attempt to stop the task.
    +426   *
    +427   * <p>After this method returns, subsequent calls to {@link #isDone} will always return {@code true}.  Subsequent
    +428   * calls to {@link #isCancelled} will always return {@code true} if this method returned {@code true}.
    +429   *
    +430   * @param mayInterruptIfRunning {@code true} if the thread executing this task should be interrupted; otherwise,
    +431   *                              in-progress tasks are allowed to complete
    +432   * @return {@code false} if the task could not be cancelled, typically because it has already completed normally;
    +433   *         {@code true} otherwise.
    +434   */
    +435  @Override
    +436  public boolean cancel(boolean mayInterruptIfRunning) {
    +437    return resolveFuture().cancel(mayInterruptIfRunning);
    +438  }
    +439
    +440  /**
    +441   * Returns {@code true} if this task was cancelled before it completed normally. This defers to the underlying future,
    +442   * or a wrapped object if using a {@link Publisher}.
    +443   *
    +444   * @return {@code true} if this task was cancelled before it completed
    +445   */
    +446  @Override
    +447  public boolean isCancelled() {
    +448    return resolveFuture().isCancelled();
    +449  }
    +450
    +451  /**
    +452   * Returns {@code true} if this task completed. This defers to the underlying future, or a wrapped object if using a
    +453   * Reactive Java {@link Publisher}.
    +454   *
    +455   * Completion may be due to normal termination, an exception, or cancellation -- in all of these cases, this method
    +456   * will return {@code true}.
    +457   *
    +458   * @return {@code true} if this task completed.
    +459   */
    +460  @Override
    +461  public boolean isDone() {
    +462    return resolveFuture().isDone();
    +463  }
    +464
    +465  /**
    +466   * Waits if necessary for the computation to complete, and then retrieves its result.
    +467   *
    +468   * <p>It is generally recommended to use the variant of this method which specifies a timeout - one must handle the
    +469   * additional {@link TimeoutException}, but on the other hand the computation can never infinitely block if an async
    +470   * value does not materialize.</p>
    +471   *
    +472   * @see #get(long, TimeUnit) For a safer version of this method, which allows specifying a timeout.
    +473   * @return the computed result.
    +474   * @throws CancellationException if the computation was cancelled
    +475   * @throws ExecutionException    if the computation threw an exception
    +476   * @throws InterruptedException  if the current thread was interrupted while waiting
    +477   */
    +478  @Override
    +479  public R get() throws InterruptedException, ExecutionException {
    +480    return resolveFuture().get();
    +481  }
    +482
    +483  /**
    +484   * Waits if necessary for at most the given time for the computation to complete, and then retrieves its result, if
    +485   * available.
    +486   *
    +487   * @param timeout the maximum time to wait
    +488   * @param unit    the time unit of the timeout argument
    +489   * @return the computed result
    +490   * @throws CancellationException if the computation was cancelled
    +491   * @throws ExecutionException    if the computation threw an exception
    +492   * @throws InterruptedException  if the current thread was interrupted while waiting
    +493   * @throws TimeoutException      if the wait timed out
    +494   */
    +495  @Override
    +496  public R get(long timeout, @Nonnull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
    +497    return resolveFuture().get(timeout, unit);
    +498  }
    +499
    +500  /**
    +501   * Structure that adapts a {@link Publisher} to a {@link ListenableFuture} interface. We accomplish this by
    +502   * immediately subscribing to the publisher with a callback that dispatches a {@link SettableFuture}.
    +503   *
    +504   * <p>This object is used in the specific circumstance of wrapping a {@link Publisher}, and then using the wrapped
    +505   * object as a {@link ListenableFuture} (or any descendent or compliant implementation thereof).</p>
    +506   *
    +507   * @param <T> Generic type returned by the future.
    +508   */
    +509  @Immutable
    +510  @ThreadSafe
    +511  public final static class PublisherListenableFuture<T> implements ListenableFuture<T>, Publisher<T> {
    +512    /** Whether we have received a value. */
    +513    private final @Nonnull AtomicBoolean received = new AtomicBoolean(false);
    +514
    +515    /** Whether we have completed acquiring a value. */
    +516    private final @Nonnull AtomicBoolean completed = new AtomicBoolean(false);
    +517
    +518    /** Whether we have been cancelled. */
    +519    private final @Nonnull AtomicBoolean cancelled = new AtomicBoolean(false);
    +520
    +521    /** Describes the list of proxied subscribers. */
    +522    private final @Nonnull Map<String, Subscriber<? super T>> subscribers = new ConcurrentHashMap<>();
    +523
    +524    /** Converted/pass-through future value. */
    +525    private final @Nonnull SettableFuture<T> future;
    +526
    +527    /** Subscription, so we can propagate cancellation. */
    +528    private volatile Subscription subscription;
    +529
    +530    /**
    +531     * Private constructor.
    +532     *
    +533     * @param publisher Publisher to wrap.
    +534     */
    +535    private PublisherListenableFuture(@Nonnull Publisher<T> publisher) {
    +536      this.future = SettableFuture.create();
    +537      publisher.subscribe(new Subscriber<T>() {
    +538        @Override
    +539        public void onSubscribe(Subscription s) {
    +540          PublisherListenableFuture.this.subscription = s;
    +541        }
    +542
    +543        @Override
    +544        public void onNext(T t) {
    +545          if (received.compareAndSet(false, true)) {
    +546            PublisherListenableFuture.this.proxyExecute((sub) -> sub.onNext(t));
    +547            future.set(t);
    +548            return;
    +549          }
    +550          this.onError(new IllegalStateException(
    +551            "Cannot publish multiple items through `ReactiveFuture`."));
    +552        }
    +553
    +554        @Override
    +555        public void onError(Throwable t) {
    +556          if (!completed.get()) {
    +557            PublisherListenableFuture.this.proxyExecute((sub) -> sub.onError(t));
    +558            future.setException(t);
    +559          }
    +560        }
    +561
    +562        @Override
    +563        public void onComplete() {
    +564          if (completed.compareAndSet(false, true)) {
    +565            PublisherListenableFuture.this.proxyExecute(Subscriber::onComplete);
    +566            PublisherListenableFuture.this.clear();
    +567          }
    +568        }
    +569      });
    +570    }
    +571
    +572    /**
    +573     * Call something on each proxied publisher subscription, if any.
    +574     *
    +575     * @param operation Operation to execute. Called for each subscriber.
    +576     */
    +577    private void proxyExecute(@Nonnull Consumer<Subscriber<? super T>> operation) {
    +578      if (!this.subscribers.isEmpty()) {
    +579        this.subscribers.values().forEach(operation);
    +580      }
    +581    }
    +582
    +583    /**
    +584     * Remove all subscribers and clear references to futures/publishers/listeners.
    +585     */
    +586    private void clear() {
    +587      this.subscribers.clear();
    +588      this.subscription = null;
    +589    }
    +590
    +591    /**
    +592     * Drop a subscription (after proxied {@link Subscription#cancel()} is called).
    +593     *
    +594     * @param id ID of the subscription to drop.
    +595     */
    +596    private void dropSubscription(@Nonnull String id) {
    +597      this.subscribers.get(id).onComplete();
    +598      this.subscribers.remove(id);
    +599    }
    +600
    +601    // -- Interface Compliance: Publisher -- //
    +602
    +603    @Override
    +604    public void subscribe(Subscriber<? super T> s) {
    +605      final String id = String.valueOf(this.subscribers.size());
    +606      Subscription sub = new Subscription() {
    +607        @Override
    +608        public void request(long n) {
    +609          PublisherListenableFuture.this.subscription.request(n);
    +610        }
    +611
    +612        @Override
    +613        public void cancel() {
    +614          // kill self
    +615          PublisherListenableFuture.this.dropSubscription(id);
    +616        }
    +617      };
    +618      this.subscribers.put(id, s);
    +619      s.onSubscribe(sub);
    +620    }
    +621
    +622    // -- Interface Compliance: Listenable Future -- //
    +623
    +624    @Override
    +625    public void addListener(@Nonnull Runnable runnable, @Nonnull Executor executor) {
    +626      this.future.addListener(runnable, executor);
    +627    }
    +628
    +629    @Override
    +630    public boolean cancel(boolean mayInterruptIfRunning) {
    +631      boolean cancelled = false;
    +632      if (!this.completed.get() && this.cancelled.compareAndSet(false, true)) {
    +633        this.proxyExecute(Subscriber::onComplete);  // dispatch `onComplete` for any subscribers
    +634        this.subscription.cancel();  // cancel upwards
    +635        cancelled = this.future.cancel(mayInterruptIfRunning);  // cancel future
    +636        this.clear();  // clear references
    +637      }
    +638      return cancelled;
    +639    }
    +640
    +641    @Override
    +642    public boolean isCancelled() {
    +643      return this.cancelled.get();
    +644    }
    +645
    +646    @Override
    +647    public boolean isDone() {
    +648      return this.completed.get() || this.cancelled.get();
    +649    }
    +650
    +651    @Override
    +652    public T get() throws InterruptedException, ExecutionException {
    +653      return this.future.get();
    +654    }
    +655
    +656    @Override
    +657    public T get(long timeout, @Nonnull TimeUnit unit)
    +658        throws InterruptedException, ExecutionException, TimeoutException {
    +659      return this.future.get(timeout, unit);
    +660    }
    +661  }
    +662
    +663  /**
    +664   * Structure that adapts Java's {@link CompletableFuture} to a Reactive Java {@link Publisher}, which publishes one
    +665   * item - either the result of the computation, or an error.
    +666   *
    +667   * <p>This object is used in the specific circumstance that a {@link CompletableFuture} is wrapped by a
    +668   * {@link ReactiveFuture}, and then used within the Reactive Java or Guava ecosystems as a {@link Publisher} or a
    +669   * {@link ListenableFuture} (or {@link ApiFuture}), or a descendent thereof. As in {@link ListenableFuturePublisher},
    +670   * we simply set the callback for the future value, upon item-request (one cycle is allowed), and propagate any events
    +671   * received to the publisher.</p>
    +672   *
    +673   * @param <T> Emit type for this adapter. Matches the future it wraps.
    +674   */
    +675  public final static class CompletableFuturePublisher<T>
    +676      implements Publisher<T>, ListenableFuture<T>, CompletionStage<T> {
    +677    private final @Nonnull CompletableFuture<T> future;
    +678    private final @Nonnull CompletionStage<T> stage;
    +679    private final @Nonnull Executor callbackExecutor;
    +680
    +681    /**
    +682     * Construct an adapter that propagates signals from a {@link CompletableFuture} to a {@link Publisher}.
    +683     *
    +684     * @param future Completable future to wrap.
    +685     * @param callbackExecutor Callback executor to use.
    +686     */
    +687    private CompletableFuturePublisher(@Nonnull CompletableFuture<T> future,
    +688                                       @Nonnull Executor callbackExecutor) {
    +689      this.future = future;
    +690      this.stage = future;
    +691      this.callbackExecutor = callbackExecutor;
    +692    }
    +693
    +694    /* == `Future`/`ListenableFuture` Interface Compliance == */
    +695
    +696    /** @inheritDoc */
    +697    @Override public final void subscribe(Subscriber<? super T> subscriber) {
    +698      Objects.requireNonNull(subscriber, "Subscriber cannot be null");
    +699      subscriber.onSubscribe(new CompletableFutureSubscription(this.future, subscriber, this.callbackExecutor));
    +700    }
    +701
    +702    /** @inheritDoc */
    +703    @Override public void addListener(Runnable runnable, Executor executor) {
    +704      future.thenRunAsync(runnable, executor);
    +705    }
    +706
    +707    /** @inheritDoc */
    +708    @Override public boolean cancel(boolean mayInterruptIfRunning) {
    +709      return future.cancel(mayInterruptIfRunning);
    +710    }
    +711
    +712    /** @inheritDoc */
    +713    @Override public boolean isCancelled() {
    +714      return future.isCancelled();
    +715    }
    +716
    +717    /** @inheritDoc */
    +718    @Override public boolean isDone() {
    +719      return future.isDone();
    +720    }
    +721
    +722    /** @inheritDoc */
    +723    @Override public T get() throws InterruptedException, ExecutionException {
    +724      return future.get();
    +725    }
    +726
    +727    /** @inheritDoc */
    +728    @Override
    +729    public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
    +730      return future.get(timeout, unit);
    +731    }
    +732
    +733    /* == `CompletionStage` Interface Compliance == */
    +734
    +735    /** @inheritDoc */
    +736    @Override public <U> CompletionStage<U> thenApply(Function<? super T, ? extends U> fn) {
    +737      return stage.thenApply(fn);
    +738    }
    +739
    +740    /** @inheritDoc */
    +741    @Override public <U> CompletionStage<U> thenApplyAsync(Function<? super T, ? extends U> fn) {
    +742      return stage.thenApplyAsync(fn);
    +743    }
    +744
    +745    /** @inheritDoc */
    +746    @Override public <U> CompletionStage<U> thenApplyAsync(Function<? super T, ? extends U> fn, Executor executor) {
    +747      return stage.thenApplyAsync(fn, executor);
    +748    }
    +749
    +750    /** @inheritDoc */
    +751    @Override public CompletionStage<Void> thenAccept(Consumer<? super T> action) {
    +752      return stage.thenAccept(action);
    +753    }
    +754
    +755    /** @inheritDoc */
    +756    @Override public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action) {
    +757      return stage.thenAcceptAsync(action);
    +758    }
    +759
    +760    /** @inheritDoc */
    +761    @Override public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor) {
    +762      return stage.thenAcceptAsync(action, executor);
    +763    }
    +764
    +765    /** @inheritDoc */
    +766    @Override public CompletionStage<Void> thenRun(Runnable action) {
    +767      return stage.thenRun(action);
    +768    }
    +769
    +770    /** @inheritDoc */
    +771    @Override public CompletionStage<Void> thenRunAsync(Runnable action) {
    +772      return stage.thenRunAsync(action);
    +773    }
    +774
    +775    /** @inheritDoc */
    +776    @Override public CompletionStage<Void> thenRunAsync(Runnable action, Executor executor) {
    +777      return stage.thenRunAsync(action, executor);
    +778    }
    +779
    +780    /** @inheritDoc */
    +781    @Override public <U, V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,
    +782                                                           BiFunction<? super T, ? super U, ? extends V> fn) {
    +783      return stage.thenCombine(other, fn);
    +784    }
    +785
    +786    /** @inheritDoc */
    +787    @Override public <U, V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,
    +788                                                                BiFunction<? super T, ? super U, ? extends V> fn) {
    +789      return stage.thenCombineAsync(other, fn);
    +790    }
    +791
    +792    /** @inheritDoc */
    +793    @Override public <U, V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,
    +794                                                                BiFunction<? super T, ? super U, ? extends V> fn,
    +795                                                                Executor executor) {
    +796      return stage.thenCombineAsync(other, fn, executor);
    +797    }
    +798
    +799    /** @inheritDoc */
    +800    @Override public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,
    +801                                                              BiConsumer<? super T, ? super U> action) {
    +802      return stage.thenAcceptBoth(other, action);
    +803    }
    +804
    +805    /** @inheritDoc */
    +806    @Override public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
    +807                                                                   BiConsumer<? super T, ? super U> action) {
    +808      return stage.thenAcceptBothAsync(other, action);
    +809    }
    +810
    +811    /** @inheritDoc */
    +812    @Override
    +813    public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
    +814                                                         BiConsumer<? super T, ? super U> action,
    +815                                                         Executor executor) {
    +816      return stage.thenAcceptBothAsync(other, action, executor);
    +817    }
    +818
    +819    /** @inheritDoc */
    +820    @Override
    +821    public CompletionStage<Void> runAfterBoth(CompletionStage<?> other, Runnable action) {
    +822      return stage.runAfterBoth(other, action);
    +823    }
    +824
    +825    /** @inheritDoc */
    +826    @Override
    +827    public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action) {
    +828      return stage.runAfterBothAsync(other, action);
    +829    }
    +830
    +831    /** @inheritDoc */
    +832    @Override
    +833    public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action, Executor executor) {
    +834      return stage.runAfterBothAsync(other, action, executor);
    +835    }
    +836
    +837    /** @inheritDoc */
    +838    @Override
    +839    public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn) {
    +840      return stage.applyToEither(other, fn);
    +841    }
    +842
    +843    /** @inheritDoc */
    +844    @Override
    +845    public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn) {
    +846      return stage.applyToEitherAsync(other, fn);
    +847    }
    +848
    +849    /** @inheritDoc */
    +850    @Override
    +851    public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,
    +852                                                     Function<? super T, U> fn,
    +853                                                     Executor executor) {
    +854      return stage.applyToEitherAsync(other, fn, executor);
    +855    }
    +856
    +857    /** @inheritDoc */
    +858    @Override public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,
    +859                                                        Consumer<? super T> action) {
    +860      return stage.acceptEither(other, action);
    +861    }
    +862
    +863    /** @inheritDoc */
    +864    @Override public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,
    +865                                                             Consumer<? super T> action) {
    +866      return stage.acceptEitherAsync(other, action);
    +867    }
    +868
    +869    /** @inheritDoc */
    +870    @Override public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,
    +871                                                             Consumer<? super T> action,
    +872                                                             Executor executor) {
    +873      return stage.acceptEitherAsync(other, action, executor);
    +874    }
    +875
    +876    /** @inheritDoc */
    +877    @Override public CompletionStage<Void> runAfterEither(CompletionStage<?> other, Runnable action) {
    +878      return stage.runAfterEither(other, action);
    +879    }
    +880
    +881    /** @inheritDoc */
    +882    @Override public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action) {
    +883      return stage.runAfterEitherAsync(other, action);
    +884    }
    +885
    +886    /** @inheritDoc */
    +887    @Override public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action, Executor executor) {
    +888      return stage.runAfterEitherAsync(other, action, executor);
    +889    }
    +890
    +891    /** @inheritDoc */
    +892    @Override public <U> CompletionStage<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) {
    +893      return stage.thenCompose(fn);
    +894    }
    +895
    +896    /** @inheritDoc */
    +897    @Override public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) {
    +898      return stage.thenComposeAsync(fn);
    +899    }
    +900
    +901    /** @inheritDoc */
    +902    @Override public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn,
    +903                                                             Executor executor) {
    +904      return stage.thenComposeAsync(fn, executor);
    +905    }
    +906
    +907    /** @inheritDoc */
    +908    @Override public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) {
    +909      return stage.handle(fn);
    +910    }
    +911
    +912    /** @inheritDoc */
    +913    @Override public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) {
    +914      return stage.handleAsync(fn);
    +915    }
    +916
    +917    /** @inheritDoc */
    +918    @Override public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,
    +919                                                        Executor executor) {
    +920      return stage.handleAsync(fn, executor);
    +921    }
    +922
    +923    /** @inheritDoc */
    +924    @Override public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> action) {
    +925      return stage.whenComplete(action);
    +926    }
    +927
    +928    /** @inheritDoc */
    +929    @Override public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action) {
    +930      return stage.whenCompleteAsync(action);
    +931    }
    +932
    +933    /** @inheritDoc */
    +934    @Override public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action,
    +935                                                          Executor executor) {
    +936      return stage.whenCompleteAsync(action, executor);
    +937    }
    +938
    +939    /** @inheritDoc */
    +940    @Override public CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn) {
    +941      return stage.exceptionally(fn);
    +942    }
    +943
    +944    /** @inheritDoc */
    +945    @Override public CompletableFuture<T> toCompletableFuture() {
    +946      return stage.toCompletableFuture();
    +947    }
    +948
    +949    /**
    +950     * Models a Reactive Java {@link Subscription}, which is responsible for propagating events from a
    +951     * Concurrent Java {@link CompletableFuture} to a {@link Subscriber}.
    +952     *
    +953     * <p>This object is generally used internally by the {@link CompletableFuturePublisher}, once a {@link Subscriber}
    +954     * attaches itself to a {@link Publisher} that is actually a wrapped {@link CompletableFuture}. Error (exception)
    +955     * events and value events are both propagated. Subscribers based on this wrapping will only ever receive a maximum
    +956     * of <b>one value</b> or <b>one error</b>.</p>
    +957     */
    +958    @Immutable
    +959    @ThreadSafe
    +960    public final class CompletableFutureSubscription implements Subscription {
    +961      private final AtomicBoolean completed = new AtomicBoolean(false);
    +962      private final @Nonnull Subscriber<? super T> subscriber;
    +963      private final @Nonnull CompletableFuture<T> future;
    +964      private final @Nonnull Executor executor;
    +965
    +966      /**
    +967       * Private constructor, meant for use by {@link CompletableFuturePublisher} only.
    +968       *
    +969       * @param future Future value to adapt.
    +970       * @param subscriber The subscriber.
    +971       * @param executor Executor to run callbacks with.
    +972       */
    +973      CompletableFutureSubscription(@Nonnull CompletableFuture<T> future,
    +974                                    @Nonnull Subscriber<? super T> subscriber,
    +975                                    @Nonnull Executor executor) {
    +976        this.future = Objects.requireNonNull(future);
    +977        this.subscriber = Objects.requireNonNull(subscriber);
    +978        this.executor = Objects.requireNonNull(executor);
    +979      }
    +980
    +981      /**
    +982       * Request the specified number of items from the underlying {@link Subscription}. This must <b>always be
    +983       * <pre>1</pre></b>.
    +984       *
    +985       * @param n Number of elements to request to the upstream (must always be <pre>1</pre>).
    +986       * @throws IllegalArgumentException If any value other than <pre>1</pre> is passed in.
    +987       */
    +988      public synchronized void request(long n) {
    +989        if (n == 1 && !completed.get()) {
    +990          try {
    +991            CompletableFuture<T> future = this.future;
    +992            future.thenAcceptAsync(t -> {
    +993              T val = null;
    +994              Throwable err = null;
    +995              try {
    +996                val = future.get();
    +997              } catch (Exception exc) {
    +998                err = exc;
    +999              }
    +1000
    +1001              if (completed.compareAndSet(false, true)) {
    +1002                if (err != null) {
    +1003                  subscriber.onError(err);
    +1004                } else {
    +1005                  if (val != null) {
    +1006                    subscriber.onNext(val);
    +1007                  }
    +1008                  subscriber.onComplete();
    +1009                }
    +1010              }
    +1011            }, executor);
    +1012
    +1013          } catch (Exception e) {
    +1014            subscriber.onError(e);
    +1015          }
    +1016        } else if (n != 1) {
    +1017          IllegalArgumentException ex = new IllegalArgumentException(
    +1018              "Cannot request more or less than 1 item from a ReactiveFuture-wrapped publisher.");
    +1019          subscriber.onError(ex);
    +1020        }
    +1021      }
    +1022
    +1023      /**
    +1024       * Request the publisher to stop sending data and clean up resources.
    +1025       */
    +1026      public synchronized void cancel() {
    +1027        if (completed.compareAndSet(false, true)) {
    +1028          subscriber.onComplete();
    +1029          future.cancel(false);
    +1030        }
    +1031      }
    +1032    }
    +1033  }
    +1034
    +1035  /**
    +1036   * Structure that adapts Guava's {@link ListenableFuture} to a Reactive Java {@link Publisher}, which publishes one
    +1037   * item - either the result of the computation, or an error.
    +1038   *
    +1039   * <p>This object is used in the specific circumstance that a {@link ListenableFuture} is wrapped by a
    +1040   * {@link ReactiveFuture}, and then used within the Reactive Java ecosystem as a {@link Publisher}. We simply set a
    +1041   * callback for the future value, upon item-request (one cycle is allowed), and propagate any events received to the
    +1042   * publisher.</p>
    +1043   *
    +1044   * @param <T> Emit type for this adapter. Matches the publisher it wraps.
    +1045   */
    +1046  public final static class ListenableFuturePublisher<T> implements Publisher<T> {
    +1047    private final @Nonnull ListenableFuture<T> future;
    +1048    private final @Nonnull Executor callbackExecutor;
    +1049
    +1050    /**
    +1051     * Wrap a {@link ListenableFuture}. Private constructor for use by {@link ReactiveFuture} only.
    +1052     *
    +1053     * @param future The future to convert or wait on.
    +1054     * @param callbackExecutor Executor to run the callback on.
    +1055     */
    +1056    private ListenableFuturePublisher(@Nonnull ListenableFuture<T> future,
    +1057                                      @Nonnull Executor callbackExecutor) {
    +1058      this.future = future;
    +1059      this.callbackExecutor = callbackExecutor;
    +1060    }
    +1061
    +1062    @Override
    +1063    public final void subscribe(Subscriber<? super T> subscriber) {
    +1064      Objects.requireNonNull(subscriber, "Subscriber cannot be null");
    +1065      subscriber.onSubscribe(new ListenableFutureSubscription(this.future, subscriber, this.callbackExecutor));
    +1066    }
    +1067
    +1068    /**
    +1069     * Models a Reactive Java {@link Subscription}, which is responsible for propagating events from a
    +1070     * {@link ListenableFuture} to a {@link Subscriber}.
    +1071     *
    +1072     * <p>This object is generally used internally by the {@link ListenableFuturePublisher}, once a {@link Subscriber}
    +1073     * attaches itself to a {@link Publisher} that is actually a wrapped {@link ListenableFuture}. Error (exception)
    +1074     * events and value events are both propagated. Subscribers based on this wrapping will only ever receive a maximum
    +1075     * of <b>one value</b> or <b>one error</b>.</p>
    +1076     */
    +1077    @Immutable
    +1078    @ThreadSafe
    +1079    public final class ListenableFutureSubscription implements Subscription {
    +1080      private final AtomicBoolean completed = new AtomicBoolean(false);
    +1081      private final @Nonnull Subscriber<? super T> subscriber;
    +1082      private final @Nonnull ListenableFuture<T> future; // to allow cancellation
    +1083      private final @Nonnull Executor executor;  // executor to use when dispatching the callback
    +1084
    +1085      /**
    +1086       * Private constructor, meant for use by {@link ListenableFuturePublisher} only.
    +1087       *
    +1088       * @param future Future value to adapt.
    +1089       * @param subscriber The subscriber.
    +1090       * @param executor Executor to run callbacks with.
    +1091       */
    +1092      ListenableFutureSubscription(@Nonnull ListenableFuture<T> future,
    +1093                                   @Nonnull Subscriber<? super T> subscriber,
    +1094                                   @Nonnull Executor executor) {
    +1095        this.future = Objects.requireNonNull(future);
    +1096        this.subscriber = Objects.requireNonNull(subscriber);
    +1097        this.executor = Objects.requireNonNull(executor);
    +1098      }
    +1099
    +1100      /**
    +1101       * Request the specified number of items from the underlying {@link Subscription}. This must <b>always be
    +1102       * <pre>1</pre></b>.
    +1103       *
    +1104       * @param n Number of elements to request to the upstream (must always be <pre>1</pre>).
    +1105       * @throws IllegalArgumentException If any value other than <pre>1</pre> is passed in.
    +1106       */
    +1107      public synchronized void request(long n) {
    +1108        if (n == 1 && !completed.get()) {
    +1109          try {
    +1110            ListenableFuture<T> future = this.future;
    +1111            future.addListener(() -> {
    +1112              T val = null;
    +1113              Throwable err = null;
    +1114              try {
    +1115                val = this.future.get();
    +1116              } catch (Exception exc) {
    +1117                err = exc;
    +1118              }
    +1119
    +1120              if (completed.compareAndSet(false, true)) {
    +1121                if (err != null) {
    +1122                  subscriber.onError(err);
    +1123                } else {
    +1124                  if (val != null) {
    +1125                    subscriber.onNext(val);
    +1126                  }
    +1127                  subscriber.onComplete();
    +1128                }
    +1129              }
    +1130            }, this.executor);
    +1131          } catch (Exception e) {
    +1132            subscriber.onError(e);
    +1133          }
    +1134        } else if (n != 1) {
    +1135          IllegalArgumentException ex = new IllegalArgumentException(
    +1136            "Cannot request more or less than 1 item from a ReactiveFuture-wrapped publisher.");
    +1137          subscriber.onError(ex);
    +1138        }
    +1139      }
    +1140
    +1141      /**
    +1142       * Request the publisher to stop sending data and clean up resources.
    +1143       */
    +1144      public synchronized void cancel() {
    +1145        if (completed.compareAndSet(false, true)) {
    +1146          subscriber.onComplete();
    +1147          future.cancel(false);
    +1148        }
    +1149      }
    +1150    }
    +1151  }
    +1152}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/runtime/ReactiveFuture.PublisherListenableFuture.html b/docs/java/src-html/gust/backend/runtime/ReactiveFuture.PublisherListenableFuture.html new file mode 100644 index 000000000..a365627a5 --- /dev/null +++ b/docs/java/src-html/gust/backend/runtime/ReactiveFuture.PublisherListenableFuture.html @@ -0,0 +1,1226 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.runtime;
    +014
    +015import com.google.api.core.ApiFuture;
    +016import com.google.api.core.ApiFutureToListenableFuture;
    +017import com.google.common.util.concurrent.Futures;
    +018import com.google.common.util.concurrent.ListenableFuture;
    +019import com.google.common.util.concurrent.MoreExecutors;
    +020import com.google.common.util.concurrent.SettableFuture;
    +021import org.reactivestreams.Publisher;
    +022import org.reactivestreams.Subscriber;
    +023import org.reactivestreams.Subscription;
    +024
    +025import javax.annotation.Nonnull;
    +026import javax.annotation.Nullable;
    +027import javax.annotation.concurrent.Immutable;
    +028import javax.annotation.concurrent.ThreadSafe;
    +029import java.util.*;
    +030import java.util.concurrent.*;
    +031import java.util.concurrent.atomic.AtomicBoolean;
    +032import java.util.function.BiConsumer;
    +033import java.util.function.BiFunction;
    +034import java.util.function.Consumer;
    +035import java.util.function.Function;
    +036
    +037
    +038/**
    +039 * Adapts future/async value containers from different frameworks (namely, Reactive Java, Guava, and the JDK).
    +040 *
    +041 * <p>Create a new {@link ReactiveFuture} by using any of the {@code wrap)} factory methods. The resulting object is
    +042 * usable as a {@link Publisher}, {@link ListenableFuture}, or {@link ApiFuture}. This object simply wraps whatever
    +043 * inner object is provided, and as such instances are lightweight; there is no default functionality after immediate
    +044 * construction in most cases.</p>
    +045 *
    +046 * <p><b>Caveat:</b> when using a {@link Publisher} as a {@link ListenableFuture} (i.e. wrapping a {@link Publisher} and
    +047 * then using any of the typical future methods, like {@link ListenableFuture#addListener(Runnable, Executor)}), the
    +048 * underlying publisher may not publish more than one value. This is to prevent dropping intermediate values on the
    +049 * floor, silently, before dispatching the future's callbacks, which generally only accept one value. Other than this,
    +050 * things should work "as expected" whether you're looking at them from a Guava, JDK, or Reactive perspective.</p>
    +051 *
    +052 * @see Publisher Reactive Java type adapted by this object.
    +053 * @see ListenableFuture Guava's extension of the JDK's basic {@link Future}, which adds listener support.
    +054 * @see ApiFuture Lightweight Guava-like future meant to avoid dependencies on Java in API libraries.
    +055 * @see #wrap(Publisher) To wrap a {@link Publisher}.
    +056 * @see #wrap(ListenableFuture, Executor) To wrap a {@link ListenableFuture}.
    +057 * @see #wrap(ApiFuture, Executor) To wrap an {@link ApiFuture}.
    +058 */
    +059@Immutable
    +060@ThreadSafe
    +061@SuppressWarnings("UnstableApiUsage")
    +062public final class ReactiveFuture<R> implements Publisher<R>, ListenableFuture<R>, ApiFuture<R> {
    +063  /** Inner future, if one is set. Otherwise {@link Optional#empty()}. */
    +064  private final @Nonnull Optional<ListenableFuture<R>> future;
    +065
    +066  /** If a `publisher` is present, this object adapts it to a `future`. */
    +067  private final @Nullable PublisherListenableFuture<R> publisherAdapter;
    +068
    +069  /** If a `future` is present, this object adapts it to a `publisher`. */
    +070  private final @Nullable ListenableFuturePublisher<R> futureAdapter;
    +071
    +072  /** If a `future` is present, this object adapts it to a `publisher`. */
    +073  private final @Nullable CompletableFuturePublisher<R> javaFutureAdapter;
    +074
    +075  /**
    +076   * Spawn a reactive/future adapter in a reactive context, from a {@link Publisher}. Constructing a reactive future in
    +077   * this manner causes the object to operate in a "publisher-backed" mode.
    +078   *
    +079   * @param publisher Publisher to work with.
    +080   */
    +081  private ReactiveFuture(@Nonnull Publisher<R> publisher) {
    +082    this.future = Optional.empty();
    +083    this.futureAdapter = null;
    +084    this.publisherAdapter = new PublisherListenableFuture<>(publisher);
    +085    this.javaFutureAdapter = null;
    +086  }
    +087
    +088  /**
    +089   * Spawn a reactive/future adapter in a future context, from a {@link ListenableFuture}. Constructing a reactive
    +090   * future in this manner causes the object to operate in a "future-backed" mode.
    +091   *
    +092   * @param future Future to work with.
    +093   * @param executor Executor to use when running callbacks.
    +094   */
    +095  private ReactiveFuture(@Nonnull ListenableFuture<R> future, @Nonnull Executor executor) {
    +096    this.future = Optional.of(future);
    +097    this.futureAdapter = new ListenableFuturePublisher<>(future, executor);
    +098    this.publisherAdapter = null;
    +099    this.javaFutureAdapter = null;
    +100  }
    +101
    +102  /**
    +103   * Spawn a reactive/future adapter in a future context, from a {@link CompletableFuture}. Constructing a reactive
    +104   * future in this manner causes the object to operate in a "future-backed" mode.
    +105   *
    +106   * @param future Future to work with.
    +107   * @param executor Executor to use when running callbacks.
    +108   */
    +109  private ReactiveFuture(@Nonnull CompletableFuture<R> future, @Nonnull Executor executor) {
    +110    this.future = Optional.empty();
    +111    this.futureAdapter = null;
    +112    this.publisherAdapter = null;
    +113    this.javaFutureAdapter = new CompletableFuturePublisher<>(future, executor);
    +114  }
    +115
    +116  /** @return Internal future representation. */
    +117  private @Nonnull ListenableFuture<R> resolveFuture() {
    +118    if (this.publisherAdapter != null)
    +119      return this.publisherAdapter;
    +120    else if (this.javaFutureAdapter != null)
    +121      return this.javaFutureAdapter;
    +122    //noinspection OptionalGetWithoutIsPresent
    +123    return this.future.get();
    +124  }
    +125
    +126  /** @return Internal publisher representation. */
    +127  private @Nonnull Publisher<R> resolvePublisher() {
    +128    if (this.futureAdapter != null)
    +129      return this.futureAdapter;
    +130    else if (this.javaFutureAdapter != null)
    +131      return this.javaFutureAdapter;
    +132    return Objects.requireNonNull(this.publisherAdapter);
    +133  }
    +134
    +135  // -- Public API -- //
    +136  /**
    +137   * Wrap a Reactive Java {@link Publisher} in a universal {@link ReactiveFuture}, such that it may be used with any
    +138   * interface requiring a supported async or future value.
    +139   *
    +140   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +141   * class docs for more information.</p>
    +142   *
    +143   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +144   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +145   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +146   *
    +147   * @see #wrap(ListenableFuture, Executor) Wraps a {@link ListenableFuture} from Guava.
    +148   * @param publisher Reactive publisher to wrap.
    +149   * @param <R> Return or emission type of the publisher.
    +150   * @return Wrapped reactive future object.
    +151   * @throws IllegalArgumentException If the passed `publisher` is `null`.
    +152   */
    +153  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull Publisher<R> publisher) {
    +154    //noinspection ConstantConditions
    +155    if (publisher == null) throw new IllegalArgumentException("Cannot wrap `null` publisher.");
    +156    return new ReactiveFuture<>(publisher);
    +157  }
    +158
    +159  /**
    +160   * Wrap a regular Java {@link CompletableFuture} in a universal {@link ReactiveFuture}, such that it may be used with
    +161   * any interface requiring support for that class.
    +162   *
    +163   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +164   * class docs for more information.</p>
    +165   *
    +166   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +167   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +168   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +169   *
    +170   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +171   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +172   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +173   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +174   *
    +175   * @param future Completable future to wrap.
    +176   * @param <R> Return or emission type of the future.
    +177   * @return Wrapped reactive future object.
    +178   */
    +179  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull CompletableFuture<R> future) {
    +180    //noinspection ConstantConditions
    +181    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` publisher.");
    +182    return wrap(future, MoreExecutors.directExecutor());
    +183  }
    +184
    +185  /**
    +186   * Wrap a regular Java {@link CompletableFuture} in a universal {@link ReactiveFuture}, such that it may be used with
    +187   * any interface requiring support for that class.
    +188   *
    +189   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +190   * class docs for more information.</p>
    +191   *
    +192   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +193   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +194   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +195   *
    +196   * @param future Completable future to wrap.
    +197   * @param executor Executor to use.
    +198   * @param <R> Return or emission type of the future.
    +199   * @return Wrapped reactive future object.
    +200   */
    +201  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull CompletableFuture<R> future, @Nonnull Executor executor) {
    +202    //noinspection ConstantConditions
    +203    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` future.");
    +204    //noinspection ConstantConditions
    +205    if (executor == null) throw new IllegalArgumentException("Cannot wrap future with `null` executor.");
    +206    return new ReactiveFuture<>(future, executor);
    +207  }
    +208
    +209  /**
    +210   * Wrap a Guava {@link ListenableFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +211   * interface requiring a supported async or future value.
    +212   *
    +213   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +214   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +215   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +216   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +217   *
    +218   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +219   * class docs for more information.</p>
    +220   *
    +221   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +222   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +223   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +224   *
    +225   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +226   * @param future Future value to wrap.
    +227   * @param <R> Return value type for the future.
    +228   * @return Wrapped reactive future object.
    +229   * @throws IllegalArgumentException If the passed `future` is `null`.
    +230   */
    +231  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ListenableFuture<R> future) {
    +232    return wrap(future, MoreExecutors.directExecutor());
    +233  }
    +234
    +235  /**
    +236   * Wrap a Guava {@link ListenableFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +237   * interface requiring a supported async or future value.
    +238   *
    +239   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +240   * class docs for more information.</p>
    +241   *
    +242   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +243   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +244   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +245   *
    +246   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +247   * @param future Future value to wrap.
    +248   * @param executor Executor to dispatch callbacks with.
    +249   * @param <R> Return value type for the future.
    +250   * @return Wrapped reactive future object.
    +251   * @throws IllegalArgumentException If the passed `future` is `null`.
    +252   */
    +253  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ListenableFuture<R> future, @Nonnull Executor executor) {
    +254    //noinspection ConstantConditions
    +255    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` future.");
    +256    //noinspection ConstantConditions
    +257    if (executor == null) throw new IllegalArgumentException("Cannot wrap future with `null` executor.");
    +258    return new ReactiveFuture<>(future, executor);
    +259  }
    +260
    +261  /**
    +262   * Wrap a Google APIs {@link ApiFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +263   * interface requiring a supported async or future value.
    +264   *
    +265   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +266   * class docs for more information.</p>
    +267   *
    +268   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +269   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +270   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +271   *
    +272   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +273   * @see #wrap(ListenableFuture, Executor) Wraps a regular Guava {@link ListenableFuture}.
    +274   * @param apiFuture API future to wrap.
    +275   * @param executor Executor to run callbacks with.
    +276   * @param <R> Return value type for the future.
    +277   * @return Wrapped reactive future object.
    +278   * @throws IllegalArgumentException If the passed `apiFuture` is `null`.
    +279   */
    +280  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ApiFuture<R> apiFuture, @Nonnull Executor executor) {
    +281    //noinspection ConstantConditions
    +282    if (apiFuture == null) throw new IllegalArgumentException("Cannot wrap `null` API future.");
    +283    return wrap(new ApiFutureToListenableFuture<>(apiFuture), executor);
    +284  }
    +285
    +286  /**
    +287   * Wrap a Google APIs {@link ApiFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +288   * interface requiring a supported async or future value.
    +289   *
    +290   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +291   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +292   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +293   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +294   *
    +295   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +296   * class docs for more information.</p>
    +297   *
    +298   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +299   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +300   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +301   *
    +302   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +303   * @see #wrap(ListenableFuture, Executor) Wraps a regular Guava {@link ListenableFuture}.
    +304   * @param apiFuture API future to wrap.
    +305   * @param <R> Return value type for the future.
    +306   * @return Wrapped reactive future object.
    +307   * @throws IllegalArgumentException If the passed `apiFuture` is `null`.
    +308   */
    +309  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ApiFuture<R> apiFuture) {
    +310    return wrap(apiFuture, MoreExecutors.directExecutor());
    +311  }
    +312
    +313  /**
    +314   * Create an already-resolved future, wrapping the provided value. The future will present as done as soon as it is
    +315   * returned from this method.
    +316   *
    +317   * <p>Under the hood, this is simply a {@link ReactiveFuture} wrapping a call to
    +318   * {@link Futures#immediateFuture(Object)}.</p>
    +319   *
    +320   * @param value Value to wrap in an already-completed future.
    +321   * @param <R> Return value generic type.
    +322   * @return Reactive future wrapping a finished value.
    +323   */
    +324  public static @Nonnull <R> ReactiveFuture<R> done(@Nonnull R value) {
    +325    return wrap(Futures.immediateFuture(value));
    +326  }
    +327
    +328  /**
    +329   * Create an already-failed future, wrapping the provided exception instance. The future will present as one as soon
    +330   * as it is returned from this method.
    +331   *
    +332   * <p>Calling {@link Future#get(long, TimeUnit)} or {@link Future#get()} on a failed future will surface the
    +333   * associated exception where invocation occurs. Under the hood, this is simply a {@link ReactiveFuture} wrapping a
    +334   * call to {@link Futures#immediateFailedFuture(Throwable)}.</p>
    +335   *
    +336   * @param error Error to wrap in an already-failed future.
    +337   * @param <R> Return value generic type.
    +338   * @return Reactive future wrapping a finished value.
    +339   */
    +340  public static @Nonnull <R> ReactiveFuture<R> failed(@Nonnull Throwable error) {
    +341    return wrap(Futures.immediateFailedFuture(error));
    +342  }
    +343
    +344  /**
    +345   * Create an already-cancelled future. The future will present as both done and cancelled as soon as it is returned
    +346   * from this method.
    +347   *
    +348   * <p>Under the hood, this is simply a {@link ReactiveFuture} wrapping a call to
    +349   * {@link Futures#immediateCancelledFuture()}.</p>
    +350   *
    +351   * @param <R> Return value generic type.
    +352   * @return Reactive future wrapping a cancelled operation.
    +353   */
    +354  public static @Nonnull <R> ReactiveFuture<R> cancelled() {
    +355    return wrap(Futures.immediateCancelledFuture());
    +356  }
    +357
    +358  // -- Compliance: Publisher -- //
    +359  /**
    +360   * Request {@link Publisher} to start streaming data.
    +361   *
    +362   * <p>This is a "factory method" and can be called multiple times, each time starting a new {@link Subscription}. Each
    +363   * {@link Subscription} will work for only a single {@link Subscriber}. A {@link Subscriber} should only subscribe
    +364   * once to a single {@link Publisher}. If the {@link Publisher} rejects the subscription attempt or otherwise fails it
    +365   * will signal the error via {@link Subscriber#onError}.</p>
    +366   *
    +367   * @param subscriber the {@link Subscriber} that will consume signals from this {@link Publisher}.
    +368   */
    +369  @Override
    +370  public void subscribe(Subscriber<? super R> subscriber) {
    +371    resolvePublisher().subscribe(subscriber);
    +372  }
    +373
    +374  // -- Compliance: Listenable Future -- //
    +375  /**
    +376   * Registers a listener to be {@linkplain Executor#execute(Runnable) run} on the given executor. The listener will run
    +377   * when the {@code Future}'s computation is {@linkplain Future#isDone() complete} or, if the computation is already
    +378   * complete, immediately.
    +379   *
    +380   * <p>There is no guaranteed ordering of execution of listeners, but any listener added through this method is
    +381   * guaranteed to be called once the computation is complete.</p>
    +382   *
    +383   * <p>Exceptions thrown by a listener will be propagated up to the executor. Any exception thrown during
    +384   * {@code Executor.execute} (e.g., a {@code RejectedExecutionException} or an exception thrown by
    +385   * {@linkplain MoreExecutors#directExecutor direct execution}) will be caught and logged.</p>
    +386   *
    +387   * <p>Note: For fast, lightweight listeners that would be safe to execute in any thread, consider
    +388   * {@link MoreExecutors#directExecutor}. Otherwise, avoid it. Heavyweight {@code directExecutor} listeners can cause
    +389   * problems, and these problems can be difficult to reproduce because they depend on timing. For example:</p>
    +390   * <ul>
    +391   *   <li>The listener may be executed by the caller of {@code addListener}. That caller may be a
    +392   *       UI thread or other latency-sensitive thread. This can harm UI responsiveness.
    +393   *   <li>The listener may be executed by the thread that completes this {@code Future}. That
    +394   *       thread may be an internal system thread such as an RPC network thread. Blocking that
    +395   *       thread may stall progress of the whole system. It may even cause a deadlock.
    +396   *   <li>The listener may delay other listeners, even listeners that are not themselves {@code
    +397   *       directExecutor} listeners.
    +398   * </ul>
    +399   *
    +400   * <p>This is the most general listener interface. For common operations performed using listeners, see
    +401   * {@link Futures}. For a simplified but general listener interface, see
    +402   * {@link Futures#addCallback addCallback()}.</p>
    +403   *
    +404   * <p>Memory consistency effects: Actions in a thread prior to adding a listener <a
    +405   * href="https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5"><i>happen-before</i></a> its
    +406   * execution begins, perhaps in another thread.</p>
    +407   *
    +408   * <p>Guava implementations of {@code ListenableFuture} promptly release references to listeners after executing
    +409   * them.</p>
    +410   *
    +411   * @param listener the listener to run when the computation is complete.
    +412   * @param executor the executor to run the listener in
    +413   * @throws RejectedExecutionException if we tried to execute the listener immediately but the executor rejecte it.
    +414   */
    +415  @Override
    +416  public void addListener(@Nonnull Runnable listener, @Nonnull Executor executor) throws RejectedExecutionException {
    +417    resolveFuture().addListener(listener, executor);
    +418  }
    +419
    +420  /**
    +421   * Attempts to cancel execution of this task.  This attempt will fail if the task has already completed, has already
    +422   * been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when
    +423   * {@code cancel} is called, this task should never run.  If the task has already started, then the
    +424   * {@code mayInterruptIfRunning} parameter determines whether the thread executing this task should be interrupted in
    +425   * an attempt to stop the task.
    +426   *
    +427   * <p>After this method returns, subsequent calls to {@link #isDone} will always return {@code true}.  Subsequent
    +428   * calls to {@link #isCancelled} will always return {@code true} if this method returned {@code true}.
    +429   *
    +430   * @param mayInterruptIfRunning {@code true} if the thread executing this task should be interrupted; otherwise,
    +431   *                              in-progress tasks are allowed to complete
    +432   * @return {@code false} if the task could not be cancelled, typically because it has already completed normally;
    +433   *         {@code true} otherwise.
    +434   */
    +435  @Override
    +436  public boolean cancel(boolean mayInterruptIfRunning) {
    +437    return resolveFuture().cancel(mayInterruptIfRunning);
    +438  }
    +439
    +440  /**
    +441   * Returns {@code true} if this task was cancelled before it completed normally. This defers to the underlying future,
    +442   * or a wrapped object if using a {@link Publisher}.
    +443   *
    +444   * @return {@code true} if this task was cancelled before it completed
    +445   */
    +446  @Override
    +447  public boolean isCancelled() {
    +448    return resolveFuture().isCancelled();
    +449  }
    +450
    +451  /**
    +452   * Returns {@code true} if this task completed. This defers to the underlying future, or a wrapped object if using a
    +453   * Reactive Java {@link Publisher}.
    +454   *
    +455   * Completion may be due to normal termination, an exception, or cancellation -- in all of these cases, this method
    +456   * will return {@code true}.
    +457   *
    +458   * @return {@code true} if this task completed.
    +459   */
    +460  @Override
    +461  public boolean isDone() {
    +462    return resolveFuture().isDone();
    +463  }
    +464
    +465  /**
    +466   * Waits if necessary for the computation to complete, and then retrieves its result.
    +467   *
    +468   * <p>It is generally recommended to use the variant of this method which specifies a timeout - one must handle the
    +469   * additional {@link TimeoutException}, but on the other hand the computation can never infinitely block if an async
    +470   * value does not materialize.</p>
    +471   *
    +472   * @see #get(long, TimeUnit) For a safer version of this method, which allows specifying a timeout.
    +473   * @return the computed result.
    +474   * @throws CancellationException if the computation was cancelled
    +475   * @throws ExecutionException    if the computation threw an exception
    +476   * @throws InterruptedException  if the current thread was interrupted while waiting
    +477   */
    +478  @Override
    +479  public R get() throws InterruptedException, ExecutionException {
    +480    return resolveFuture().get();
    +481  }
    +482
    +483  /**
    +484   * Waits if necessary for at most the given time for the computation to complete, and then retrieves its result, if
    +485   * available.
    +486   *
    +487   * @param timeout the maximum time to wait
    +488   * @param unit    the time unit of the timeout argument
    +489   * @return the computed result
    +490   * @throws CancellationException if the computation was cancelled
    +491   * @throws ExecutionException    if the computation threw an exception
    +492   * @throws InterruptedException  if the current thread was interrupted while waiting
    +493   * @throws TimeoutException      if the wait timed out
    +494   */
    +495  @Override
    +496  public R get(long timeout, @Nonnull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
    +497    return resolveFuture().get(timeout, unit);
    +498  }
    +499
    +500  /**
    +501   * Structure that adapts a {@link Publisher} to a {@link ListenableFuture} interface. We accomplish this by
    +502   * immediately subscribing to the publisher with a callback that dispatches a {@link SettableFuture}.
    +503   *
    +504   * <p>This object is used in the specific circumstance of wrapping a {@link Publisher}, and then using the wrapped
    +505   * object as a {@link ListenableFuture} (or any descendent or compliant implementation thereof).</p>
    +506   *
    +507   * @param <T> Generic type returned by the future.
    +508   */
    +509  @Immutable
    +510  @ThreadSafe
    +511  public final static class PublisherListenableFuture<T> implements ListenableFuture<T>, Publisher<T> {
    +512    /** Whether we have received a value. */
    +513    private final @Nonnull AtomicBoolean received = new AtomicBoolean(false);
    +514
    +515    /** Whether we have completed acquiring a value. */
    +516    private final @Nonnull AtomicBoolean completed = new AtomicBoolean(false);
    +517
    +518    /** Whether we have been cancelled. */
    +519    private final @Nonnull AtomicBoolean cancelled = new AtomicBoolean(false);
    +520
    +521    /** Describes the list of proxied subscribers. */
    +522    private final @Nonnull Map<String, Subscriber<? super T>> subscribers = new ConcurrentHashMap<>();
    +523
    +524    /** Converted/pass-through future value. */
    +525    private final @Nonnull SettableFuture<T> future;
    +526
    +527    /** Subscription, so we can propagate cancellation. */
    +528    private volatile Subscription subscription;
    +529
    +530    /**
    +531     * Private constructor.
    +532     *
    +533     * @param publisher Publisher to wrap.
    +534     */
    +535    private PublisherListenableFuture(@Nonnull Publisher<T> publisher) {
    +536      this.future = SettableFuture.create();
    +537      publisher.subscribe(new Subscriber<T>() {
    +538        @Override
    +539        public void onSubscribe(Subscription s) {
    +540          PublisherListenableFuture.this.subscription = s;
    +541        }
    +542
    +543        @Override
    +544        public void onNext(T t) {
    +545          if (received.compareAndSet(false, true)) {
    +546            PublisherListenableFuture.this.proxyExecute((sub) -> sub.onNext(t));
    +547            future.set(t);
    +548            return;
    +549          }
    +550          this.onError(new IllegalStateException(
    +551            "Cannot publish multiple items through `ReactiveFuture`."));
    +552        }
    +553
    +554        @Override
    +555        public void onError(Throwable t) {
    +556          if (!completed.get()) {
    +557            PublisherListenableFuture.this.proxyExecute((sub) -> sub.onError(t));
    +558            future.setException(t);
    +559          }
    +560        }
    +561
    +562        @Override
    +563        public void onComplete() {
    +564          if (completed.compareAndSet(false, true)) {
    +565            PublisherListenableFuture.this.proxyExecute(Subscriber::onComplete);
    +566            PublisherListenableFuture.this.clear();
    +567          }
    +568        }
    +569      });
    +570    }
    +571
    +572    /**
    +573     * Call something on each proxied publisher subscription, if any.
    +574     *
    +575     * @param operation Operation to execute. Called for each subscriber.
    +576     */
    +577    private void proxyExecute(@Nonnull Consumer<Subscriber<? super T>> operation) {
    +578      if (!this.subscribers.isEmpty()) {
    +579        this.subscribers.values().forEach(operation);
    +580      }
    +581    }
    +582
    +583    /**
    +584     * Remove all subscribers and clear references to futures/publishers/listeners.
    +585     */
    +586    private void clear() {
    +587      this.subscribers.clear();
    +588      this.subscription = null;
    +589    }
    +590
    +591    /**
    +592     * Drop a subscription (after proxied {@link Subscription#cancel()} is called).
    +593     *
    +594     * @param id ID of the subscription to drop.
    +595     */
    +596    private void dropSubscription(@Nonnull String id) {
    +597      this.subscribers.get(id).onComplete();
    +598      this.subscribers.remove(id);
    +599    }
    +600
    +601    // -- Interface Compliance: Publisher -- //
    +602
    +603    @Override
    +604    public void subscribe(Subscriber<? super T> s) {
    +605      final String id = String.valueOf(this.subscribers.size());
    +606      Subscription sub = new Subscription() {
    +607        @Override
    +608        public void request(long n) {
    +609          PublisherListenableFuture.this.subscription.request(n);
    +610        }
    +611
    +612        @Override
    +613        public void cancel() {
    +614          // kill self
    +615          PublisherListenableFuture.this.dropSubscription(id);
    +616        }
    +617      };
    +618      this.subscribers.put(id, s);
    +619      s.onSubscribe(sub);
    +620    }
    +621
    +622    // -- Interface Compliance: Listenable Future -- //
    +623
    +624    @Override
    +625    public void addListener(@Nonnull Runnable runnable, @Nonnull Executor executor) {
    +626      this.future.addListener(runnable, executor);
    +627    }
    +628
    +629    @Override
    +630    public boolean cancel(boolean mayInterruptIfRunning) {
    +631      boolean cancelled = false;
    +632      if (!this.completed.get() && this.cancelled.compareAndSet(false, true)) {
    +633        this.proxyExecute(Subscriber::onComplete);  // dispatch `onComplete` for any subscribers
    +634        this.subscription.cancel();  // cancel upwards
    +635        cancelled = this.future.cancel(mayInterruptIfRunning);  // cancel future
    +636        this.clear();  // clear references
    +637      }
    +638      return cancelled;
    +639    }
    +640
    +641    @Override
    +642    public boolean isCancelled() {
    +643      return this.cancelled.get();
    +644    }
    +645
    +646    @Override
    +647    public boolean isDone() {
    +648      return this.completed.get() || this.cancelled.get();
    +649    }
    +650
    +651    @Override
    +652    public T get() throws InterruptedException, ExecutionException {
    +653      return this.future.get();
    +654    }
    +655
    +656    @Override
    +657    public T get(long timeout, @Nonnull TimeUnit unit)
    +658        throws InterruptedException, ExecutionException, TimeoutException {
    +659      return this.future.get(timeout, unit);
    +660    }
    +661  }
    +662
    +663  /**
    +664   * Structure that adapts Java's {@link CompletableFuture} to a Reactive Java {@link Publisher}, which publishes one
    +665   * item - either the result of the computation, or an error.
    +666   *
    +667   * <p>This object is used in the specific circumstance that a {@link CompletableFuture} is wrapped by a
    +668   * {@link ReactiveFuture}, and then used within the Reactive Java or Guava ecosystems as a {@link Publisher} or a
    +669   * {@link ListenableFuture} (or {@link ApiFuture}), or a descendent thereof. As in {@link ListenableFuturePublisher},
    +670   * we simply set the callback for the future value, upon item-request (one cycle is allowed), and propagate any events
    +671   * received to the publisher.</p>
    +672   *
    +673   * @param <T> Emit type for this adapter. Matches the future it wraps.
    +674   */
    +675  public final static class CompletableFuturePublisher<T>
    +676      implements Publisher<T>, ListenableFuture<T>, CompletionStage<T> {
    +677    private final @Nonnull CompletableFuture<T> future;
    +678    private final @Nonnull CompletionStage<T> stage;
    +679    private final @Nonnull Executor callbackExecutor;
    +680
    +681    /**
    +682     * Construct an adapter that propagates signals from a {@link CompletableFuture} to a {@link Publisher}.
    +683     *
    +684     * @param future Completable future to wrap.
    +685     * @param callbackExecutor Callback executor to use.
    +686     */
    +687    private CompletableFuturePublisher(@Nonnull CompletableFuture<T> future,
    +688                                       @Nonnull Executor callbackExecutor) {
    +689      this.future = future;
    +690      this.stage = future;
    +691      this.callbackExecutor = callbackExecutor;
    +692    }
    +693
    +694    /* == `Future`/`ListenableFuture` Interface Compliance == */
    +695
    +696    /** @inheritDoc */
    +697    @Override public final void subscribe(Subscriber<? super T> subscriber) {
    +698      Objects.requireNonNull(subscriber, "Subscriber cannot be null");
    +699      subscriber.onSubscribe(new CompletableFutureSubscription(this.future, subscriber, this.callbackExecutor));
    +700    }
    +701
    +702    /** @inheritDoc */
    +703    @Override public void addListener(Runnable runnable, Executor executor) {
    +704      future.thenRunAsync(runnable, executor);
    +705    }
    +706
    +707    /** @inheritDoc */
    +708    @Override public boolean cancel(boolean mayInterruptIfRunning) {
    +709      return future.cancel(mayInterruptIfRunning);
    +710    }
    +711
    +712    /** @inheritDoc */
    +713    @Override public boolean isCancelled() {
    +714      return future.isCancelled();
    +715    }
    +716
    +717    /** @inheritDoc */
    +718    @Override public boolean isDone() {
    +719      return future.isDone();
    +720    }
    +721
    +722    /** @inheritDoc */
    +723    @Override public T get() throws InterruptedException, ExecutionException {
    +724      return future.get();
    +725    }
    +726
    +727    /** @inheritDoc */
    +728    @Override
    +729    public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
    +730      return future.get(timeout, unit);
    +731    }
    +732
    +733    /* == `CompletionStage` Interface Compliance == */
    +734
    +735    /** @inheritDoc */
    +736    @Override public <U> CompletionStage<U> thenApply(Function<? super T, ? extends U> fn) {
    +737      return stage.thenApply(fn);
    +738    }
    +739
    +740    /** @inheritDoc */
    +741    @Override public <U> CompletionStage<U> thenApplyAsync(Function<? super T, ? extends U> fn) {
    +742      return stage.thenApplyAsync(fn);
    +743    }
    +744
    +745    /** @inheritDoc */
    +746    @Override public <U> CompletionStage<U> thenApplyAsync(Function<? super T, ? extends U> fn, Executor executor) {
    +747      return stage.thenApplyAsync(fn, executor);
    +748    }
    +749
    +750    /** @inheritDoc */
    +751    @Override public CompletionStage<Void> thenAccept(Consumer<? super T> action) {
    +752      return stage.thenAccept(action);
    +753    }
    +754
    +755    /** @inheritDoc */
    +756    @Override public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action) {
    +757      return stage.thenAcceptAsync(action);
    +758    }
    +759
    +760    /** @inheritDoc */
    +761    @Override public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor) {
    +762      return stage.thenAcceptAsync(action, executor);
    +763    }
    +764
    +765    /** @inheritDoc */
    +766    @Override public CompletionStage<Void> thenRun(Runnable action) {
    +767      return stage.thenRun(action);
    +768    }
    +769
    +770    /** @inheritDoc */
    +771    @Override public CompletionStage<Void> thenRunAsync(Runnable action) {
    +772      return stage.thenRunAsync(action);
    +773    }
    +774
    +775    /** @inheritDoc */
    +776    @Override public CompletionStage<Void> thenRunAsync(Runnable action, Executor executor) {
    +777      return stage.thenRunAsync(action, executor);
    +778    }
    +779
    +780    /** @inheritDoc */
    +781    @Override public <U, V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,
    +782                                                           BiFunction<? super T, ? super U, ? extends V> fn) {
    +783      return stage.thenCombine(other, fn);
    +784    }
    +785
    +786    /** @inheritDoc */
    +787    @Override public <U, V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,
    +788                                                                BiFunction<? super T, ? super U, ? extends V> fn) {
    +789      return stage.thenCombineAsync(other, fn);
    +790    }
    +791
    +792    /** @inheritDoc */
    +793    @Override public <U, V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,
    +794                                                                BiFunction<? super T, ? super U, ? extends V> fn,
    +795                                                                Executor executor) {
    +796      return stage.thenCombineAsync(other, fn, executor);
    +797    }
    +798
    +799    /** @inheritDoc */
    +800    @Override public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,
    +801                                                              BiConsumer<? super T, ? super U> action) {
    +802      return stage.thenAcceptBoth(other, action);
    +803    }
    +804
    +805    /** @inheritDoc */
    +806    @Override public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
    +807                                                                   BiConsumer<? super T, ? super U> action) {
    +808      return stage.thenAcceptBothAsync(other, action);
    +809    }
    +810
    +811    /** @inheritDoc */
    +812    @Override
    +813    public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
    +814                                                         BiConsumer<? super T, ? super U> action,
    +815                                                         Executor executor) {
    +816      return stage.thenAcceptBothAsync(other, action, executor);
    +817    }
    +818
    +819    /** @inheritDoc */
    +820    @Override
    +821    public CompletionStage<Void> runAfterBoth(CompletionStage<?> other, Runnable action) {
    +822      return stage.runAfterBoth(other, action);
    +823    }
    +824
    +825    /** @inheritDoc */
    +826    @Override
    +827    public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action) {
    +828      return stage.runAfterBothAsync(other, action);
    +829    }
    +830
    +831    /** @inheritDoc */
    +832    @Override
    +833    public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action, Executor executor) {
    +834      return stage.runAfterBothAsync(other, action, executor);
    +835    }
    +836
    +837    /** @inheritDoc */
    +838    @Override
    +839    public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn) {
    +840      return stage.applyToEither(other, fn);
    +841    }
    +842
    +843    /** @inheritDoc */
    +844    @Override
    +845    public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn) {
    +846      return stage.applyToEitherAsync(other, fn);
    +847    }
    +848
    +849    /** @inheritDoc */
    +850    @Override
    +851    public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,
    +852                                                     Function<? super T, U> fn,
    +853                                                     Executor executor) {
    +854      return stage.applyToEitherAsync(other, fn, executor);
    +855    }
    +856
    +857    /** @inheritDoc */
    +858    @Override public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,
    +859                                                        Consumer<? super T> action) {
    +860      return stage.acceptEither(other, action);
    +861    }
    +862
    +863    /** @inheritDoc */
    +864    @Override public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,
    +865                                                             Consumer<? super T> action) {
    +866      return stage.acceptEitherAsync(other, action);
    +867    }
    +868
    +869    /** @inheritDoc */
    +870    @Override public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,
    +871                                                             Consumer<? super T> action,
    +872                                                             Executor executor) {
    +873      return stage.acceptEitherAsync(other, action, executor);
    +874    }
    +875
    +876    /** @inheritDoc */
    +877    @Override public CompletionStage<Void> runAfterEither(CompletionStage<?> other, Runnable action) {
    +878      return stage.runAfterEither(other, action);
    +879    }
    +880
    +881    /** @inheritDoc */
    +882    @Override public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action) {
    +883      return stage.runAfterEitherAsync(other, action);
    +884    }
    +885
    +886    /** @inheritDoc */
    +887    @Override public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action, Executor executor) {
    +888      return stage.runAfterEitherAsync(other, action, executor);
    +889    }
    +890
    +891    /** @inheritDoc */
    +892    @Override public <U> CompletionStage<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) {
    +893      return stage.thenCompose(fn);
    +894    }
    +895
    +896    /** @inheritDoc */
    +897    @Override public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) {
    +898      return stage.thenComposeAsync(fn);
    +899    }
    +900
    +901    /** @inheritDoc */
    +902    @Override public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn,
    +903                                                             Executor executor) {
    +904      return stage.thenComposeAsync(fn, executor);
    +905    }
    +906
    +907    /** @inheritDoc */
    +908    @Override public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) {
    +909      return stage.handle(fn);
    +910    }
    +911
    +912    /** @inheritDoc */
    +913    @Override public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) {
    +914      return stage.handleAsync(fn);
    +915    }
    +916
    +917    /** @inheritDoc */
    +918    @Override public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,
    +919                                                        Executor executor) {
    +920      return stage.handleAsync(fn, executor);
    +921    }
    +922
    +923    /** @inheritDoc */
    +924    @Override public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> action) {
    +925      return stage.whenComplete(action);
    +926    }
    +927
    +928    /** @inheritDoc */
    +929    @Override public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action) {
    +930      return stage.whenCompleteAsync(action);
    +931    }
    +932
    +933    /** @inheritDoc */
    +934    @Override public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action,
    +935                                                          Executor executor) {
    +936      return stage.whenCompleteAsync(action, executor);
    +937    }
    +938
    +939    /** @inheritDoc */
    +940    @Override public CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn) {
    +941      return stage.exceptionally(fn);
    +942    }
    +943
    +944    /** @inheritDoc */
    +945    @Override public CompletableFuture<T> toCompletableFuture() {
    +946      return stage.toCompletableFuture();
    +947    }
    +948
    +949    /**
    +950     * Models a Reactive Java {@link Subscription}, which is responsible for propagating events from a
    +951     * Concurrent Java {@link CompletableFuture} to a {@link Subscriber}.
    +952     *
    +953     * <p>This object is generally used internally by the {@link CompletableFuturePublisher}, once a {@link Subscriber}
    +954     * attaches itself to a {@link Publisher} that is actually a wrapped {@link CompletableFuture}. Error (exception)
    +955     * events and value events are both propagated. Subscribers based on this wrapping will only ever receive a maximum
    +956     * of <b>one value</b> or <b>one error</b>.</p>
    +957     */
    +958    @Immutable
    +959    @ThreadSafe
    +960    public final class CompletableFutureSubscription implements Subscription {
    +961      private final AtomicBoolean completed = new AtomicBoolean(false);
    +962      private final @Nonnull Subscriber<? super T> subscriber;
    +963      private final @Nonnull CompletableFuture<T> future;
    +964      private final @Nonnull Executor executor;
    +965
    +966      /**
    +967       * Private constructor, meant for use by {@link CompletableFuturePublisher} only.
    +968       *
    +969       * @param future Future value to adapt.
    +970       * @param subscriber The subscriber.
    +971       * @param executor Executor to run callbacks with.
    +972       */
    +973      CompletableFutureSubscription(@Nonnull CompletableFuture<T> future,
    +974                                    @Nonnull Subscriber<? super T> subscriber,
    +975                                    @Nonnull Executor executor) {
    +976        this.future = Objects.requireNonNull(future);
    +977        this.subscriber = Objects.requireNonNull(subscriber);
    +978        this.executor = Objects.requireNonNull(executor);
    +979      }
    +980
    +981      /**
    +982       * Request the specified number of items from the underlying {@link Subscription}. This must <b>always be
    +983       * <pre>1</pre></b>.
    +984       *
    +985       * @param n Number of elements to request to the upstream (must always be <pre>1</pre>).
    +986       * @throws IllegalArgumentException If any value other than <pre>1</pre> is passed in.
    +987       */
    +988      public synchronized void request(long n) {
    +989        if (n == 1 && !completed.get()) {
    +990          try {
    +991            CompletableFuture<T> future = this.future;
    +992            future.thenAcceptAsync(t -> {
    +993              T val = null;
    +994              Throwable err = null;
    +995              try {
    +996                val = future.get();
    +997              } catch (Exception exc) {
    +998                err = exc;
    +999              }
    +1000
    +1001              if (completed.compareAndSet(false, true)) {
    +1002                if (err != null) {
    +1003                  subscriber.onError(err);
    +1004                } else {
    +1005                  if (val != null) {
    +1006                    subscriber.onNext(val);
    +1007                  }
    +1008                  subscriber.onComplete();
    +1009                }
    +1010              }
    +1011            }, executor);
    +1012
    +1013          } catch (Exception e) {
    +1014            subscriber.onError(e);
    +1015          }
    +1016        } else if (n != 1) {
    +1017          IllegalArgumentException ex = new IllegalArgumentException(
    +1018              "Cannot request more or less than 1 item from a ReactiveFuture-wrapped publisher.");
    +1019          subscriber.onError(ex);
    +1020        }
    +1021      }
    +1022
    +1023      /**
    +1024       * Request the publisher to stop sending data and clean up resources.
    +1025       */
    +1026      public synchronized void cancel() {
    +1027        if (completed.compareAndSet(false, true)) {
    +1028          subscriber.onComplete();
    +1029          future.cancel(false);
    +1030        }
    +1031      }
    +1032    }
    +1033  }
    +1034
    +1035  /**
    +1036   * Structure that adapts Guava's {@link ListenableFuture} to a Reactive Java {@link Publisher}, which publishes one
    +1037   * item - either the result of the computation, or an error.
    +1038   *
    +1039   * <p>This object is used in the specific circumstance that a {@link ListenableFuture} is wrapped by a
    +1040   * {@link ReactiveFuture}, and then used within the Reactive Java ecosystem as a {@link Publisher}. We simply set a
    +1041   * callback for the future value, upon item-request (one cycle is allowed), and propagate any events received to the
    +1042   * publisher.</p>
    +1043   *
    +1044   * @param <T> Emit type for this adapter. Matches the publisher it wraps.
    +1045   */
    +1046  public final static class ListenableFuturePublisher<T> implements Publisher<T> {
    +1047    private final @Nonnull ListenableFuture<T> future;
    +1048    private final @Nonnull Executor callbackExecutor;
    +1049
    +1050    /**
    +1051     * Wrap a {@link ListenableFuture}. Private constructor for use by {@link ReactiveFuture} only.
    +1052     *
    +1053     * @param future The future to convert or wait on.
    +1054     * @param callbackExecutor Executor to run the callback on.
    +1055     */
    +1056    private ListenableFuturePublisher(@Nonnull ListenableFuture<T> future,
    +1057                                      @Nonnull Executor callbackExecutor) {
    +1058      this.future = future;
    +1059      this.callbackExecutor = callbackExecutor;
    +1060    }
    +1061
    +1062    @Override
    +1063    public final void subscribe(Subscriber<? super T> subscriber) {
    +1064      Objects.requireNonNull(subscriber, "Subscriber cannot be null");
    +1065      subscriber.onSubscribe(new ListenableFutureSubscription(this.future, subscriber, this.callbackExecutor));
    +1066    }
    +1067
    +1068    /**
    +1069     * Models a Reactive Java {@link Subscription}, which is responsible for propagating events from a
    +1070     * {@link ListenableFuture} to a {@link Subscriber}.
    +1071     *
    +1072     * <p>This object is generally used internally by the {@link ListenableFuturePublisher}, once a {@link Subscriber}
    +1073     * attaches itself to a {@link Publisher} that is actually a wrapped {@link ListenableFuture}. Error (exception)
    +1074     * events and value events are both propagated. Subscribers based on this wrapping will only ever receive a maximum
    +1075     * of <b>one value</b> or <b>one error</b>.</p>
    +1076     */
    +1077    @Immutable
    +1078    @ThreadSafe
    +1079    public final class ListenableFutureSubscription implements Subscription {
    +1080      private final AtomicBoolean completed = new AtomicBoolean(false);
    +1081      private final @Nonnull Subscriber<? super T> subscriber;
    +1082      private final @Nonnull ListenableFuture<T> future; // to allow cancellation
    +1083      private final @Nonnull Executor executor;  // executor to use when dispatching the callback
    +1084
    +1085      /**
    +1086       * Private constructor, meant for use by {@link ListenableFuturePublisher} only.
    +1087       *
    +1088       * @param future Future value to adapt.
    +1089       * @param subscriber The subscriber.
    +1090       * @param executor Executor to run callbacks with.
    +1091       */
    +1092      ListenableFutureSubscription(@Nonnull ListenableFuture<T> future,
    +1093                                   @Nonnull Subscriber<? super T> subscriber,
    +1094                                   @Nonnull Executor executor) {
    +1095        this.future = Objects.requireNonNull(future);
    +1096        this.subscriber = Objects.requireNonNull(subscriber);
    +1097        this.executor = Objects.requireNonNull(executor);
    +1098      }
    +1099
    +1100      /**
    +1101       * Request the specified number of items from the underlying {@link Subscription}. This must <b>always be
    +1102       * <pre>1</pre></b>.
    +1103       *
    +1104       * @param n Number of elements to request to the upstream (must always be <pre>1</pre>).
    +1105       * @throws IllegalArgumentException If any value other than <pre>1</pre> is passed in.
    +1106       */
    +1107      public synchronized void request(long n) {
    +1108        if (n == 1 && !completed.get()) {
    +1109          try {
    +1110            ListenableFuture<T> future = this.future;
    +1111            future.addListener(() -> {
    +1112              T val = null;
    +1113              Throwable err = null;
    +1114              try {
    +1115                val = this.future.get();
    +1116              } catch (Exception exc) {
    +1117                err = exc;
    +1118              }
    +1119
    +1120              if (completed.compareAndSet(false, true)) {
    +1121                if (err != null) {
    +1122                  subscriber.onError(err);
    +1123                } else {
    +1124                  if (val != null) {
    +1125                    subscriber.onNext(val);
    +1126                  }
    +1127                  subscriber.onComplete();
    +1128                }
    +1129              }
    +1130            }, this.executor);
    +1131          } catch (Exception e) {
    +1132            subscriber.onError(e);
    +1133          }
    +1134        } else if (n != 1) {
    +1135          IllegalArgumentException ex = new IllegalArgumentException(
    +1136            "Cannot request more or less than 1 item from a ReactiveFuture-wrapped publisher.");
    +1137          subscriber.onError(ex);
    +1138        }
    +1139      }
    +1140
    +1141      /**
    +1142       * Request the publisher to stop sending data and clean up resources.
    +1143       */
    +1144      public synchronized void cancel() {
    +1145        if (completed.compareAndSet(false, true)) {
    +1146          subscriber.onComplete();
    +1147          future.cancel(false);
    +1148        }
    +1149      }
    +1150    }
    +1151  }
    +1152}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/runtime/ReactiveFuture.html b/docs/java/src-html/gust/backend/runtime/ReactiveFuture.html new file mode 100644 index 000000000..a365627a5 --- /dev/null +++ b/docs/java/src-html/gust/backend/runtime/ReactiveFuture.html @@ -0,0 +1,1226 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.runtime;
    +014
    +015import com.google.api.core.ApiFuture;
    +016import com.google.api.core.ApiFutureToListenableFuture;
    +017import com.google.common.util.concurrent.Futures;
    +018import com.google.common.util.concurrent.ListenableFuture;
    +019import com.google.common.util.concurrent.MoreExecutors;
    +020import com.google.common.util.concurrent.SettableFuture;
    +021import org.reactivestreams.Publisher;
    +022import org.reactivestreams.Subscriber;
    +023import org.reactivestreams.Subscription;
    +024
    +025import javax.annotation.Nonnull;
    +026import javax.annotation.Nullable;
    +027import javax.annotation.concurrent.Immutable;
    +028import javax.annotation.concurrent.ThreadSafe;
    +029import java.util.*;
    +030import java.util.concurrent.*;
    +031import java.util.concurrent.atomic.AtomicBoolean;
    +032import java.util.function.BiConsumer;
    +033import java.util.function.BiFunction;
    +034import java.util.function.Consumer;
    +035import java.util.function.Function;
    +036
    +037
    +038/**
    +039 * Adapts future/async value containers from different frameworks (namely, Reactive Java, Guava, and the JDK).
    +040 *
    +041 * <p>Create a new {@link ReactiveFuture} by using any of the {@code wrap)} factory methods. The resulting object is
    +042 * usable as a {@link Publisher}, {@link ListenableFuture}, or {@link ApiFuture}. This object simply wraps whatever
    +043 * inner object is provided, and as such instances are lightweight; there is no default functionality after immediate
    +044 * construction in most cases.</p>
    +045 *
    +046 * <p><b>Caveat:</b> when using a {@link Publisher} as a {@link ListenableFuture} (i.e. wrapping a {@link Publisher} and
    +047 * then using any of the typical future methods, like {@link ListenableFuture#addListener(Runnable, Executor)}), the
    +048 * underlying publisher may not publish more than one value. This is to prevent dropping intermediate values on the
    +049 * floor, silently, before dispatching the future's callbacks, which generally only accept one value. Other than this,
    +050 * things should work "as expected" whether you're looking at them from a Guava, JDK, or Reactive perspective.</p>
    +051 *
    +052 * @see Publisher Reactive Java type adapted by this object.
    +053 * @see ListenableFuture Guava's extension of the JDK's basic {@link Future}, which adds listener support.
    +054 * @see ApiFuture Lightweight Guava-like future meant to avoid dependencies on Java in API libraries.
    +055 * @see #wrap(Publisher) To wrap a {@link Publisher}.
    +056 * @see #wrap(ListenableFuture, Executor) To wrap a {@link ListenableFuture}.
    +057 * @see #wrap(ApiFuture, Executor) To wrap an {@link ApiFuture}.
    +058 */
    +059@Immutable
    +060@ThreadSafe
    +061@SuppressWarnings("UnstableApiUsage")
    +062public final class ReactiveFuture<R> implements Publisher<R>, ListenableFuture<R>, ApiFuture<R> {
    +063  /** Inner future, if one is set. Otherwise {@link Optional#empty()}. */
    +064  private final @Nonnull Optional<ListenableFuture<R>> future;
    +065
    +066  /** If a `publisher` is present, this object adapts it to a `future`. */
    +067  private final @Nullable PublisherListenableFuture<R> publisherAdapter;
    +068
    +069  /** If a `future` is present, this object adapts it to a `publisher`. */
    +070  private final @Nullable ListenableFuturePublisher<R> futureAdapter;
    +071
    +072  /** If a `future` is present, this object adapts it to a `publisher`. */
    +073  private final @Nullable CompletableFuturePublisher<R> javaFutureAdapter;
    +074
    +075  /**
    +076   * Spawn a reactive/future adapter in a reactive context, from a {@link Publisher}. Constructing a reactive future in
    +077   * this manner causes the object to operate in a "publisher-backed" mode.
    +078   *
    +079   * @param publisher Publisher to work with.
    +080   */
    +081  private ReactiveFuture(@Nonnull Publisher<R> publisher) {
    +082    this.future = Optional.empty();
    +083    this.futureAdapter = null;
    +084    this.publisherAdapter = new PublisherListenableFuture<>(publisher);
    +085    this.javaFutureAdapter = null;
    +086  }
    +087
    +088  /**
    +089   * Spawn a reactive/future adapter in a future context, from a {@link ListenableFuture}. Constructing a reactive
    +090   * future in this manner causes the object to operate in a "future-backed" mode.
    +091   *
    +092   * @param future Future to work with.
    +093   * @param executor Executor to use when running callbacks.
    +094   */
    +095  private ReactiveFuture(@Nonnull ListenableFuture<R> future, @Nonnull Executor executor) {
    +096    this.future = Optional.of(future);
    +097    this.futureAdapter = new ListenableFuturePublisher<>(future, executor);
    +098    this.publisherAdapter = null;
    +099    this.javaFutureAdapter = null;
    +100  }
    +101
    +102  /**
    +103   * Spawn a reactive/future adapter in a future context, from a {@link CompletableFuture}. Constructing a reactive
    +104   * future in this manner causes the object to operate in a "future-backed" mode.
    +105   *
    +106   * @param future Future to work with.
    +107   * @param executor Executor to use when running callbacks.
    +108   */
    +109  private ReactiveFuture(@Nonnull CompletableFuture<R> future, @Nonnull Executor executor) {
    +110    this.future = Optional.empty();
    +111    this.futureAdapter = null;
    +112    this.publisherAdapter = null;
    +113    this.javaFutureAdapter = new CompletableFuturePublisher<>(future, executor);
    +114  }
    +115
    +116  /** @return Internal future representation. */
    +117  private @Nonnull ListenableFuture<R> resolveFuture() {
    +118    if (this.publisherAdapter != null)
    +119      return this.publisherAdapter;
    +120    else if (this.javaFutureAdapter != null)
    +121      return this.javaFutureAdapter;
    +122    //noinspection OptionalGetWithoutIsPresent
    +123    return this.future.get();
    +124  }
    +125
    +126  /** @return Internal publisher representation. */
    +127  private @Nonnull Publisher<R> resolvePublisher() {
    +128    if (this.futureAdapter != null)
    +129      return this.futureAdapter;
    +130    else if (this.javaFutureAdapter != null)
    +131      return this.javaFutureAdapter;
    +132    return Objects.requireNonNull(this.publisherAdapter);
    +133  }
    +134
    +135  // -- Public API -- //
    +136  /**
    +137   * Wrap a Reactive Java {@link Publisher} in a universal {@link ReactiveFuture}, such that it may be used with any
    +138   * interface requiring a supported async or future value.
    +139   *
    +140   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +141   * class docs for more information.</p>
    +142   *
    +143   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +144   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +145   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +146   *
    +147   * @see #wrap(ListenableFuture, Executor) Wraps a {@link ListenableFuture} from Guava.
    +148   * @param publisher Reactive publisher to wrap.
    +149   * @param <R> Return or emission type of the publisher.
    +150   * @return Wrapped reactive future object.
    +151   * @throws IllegalArgumentException If the passed `publisher` is `null`.
    +152   */
    +153  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull Publisher<R> publisher) {
    +154    //noinspection ConstantConditions
    +155    if (publisher == null) throw new IllegalArgumentException("Cannot wrap `null` publisher.");
    +156    return new ReactiveFuture<>(publisher);
    +157  }
    +158
    +159  /**
    +160   * Wrap a regular Java {@link CompletableFuture} in a universal {@link ReactiveFuture}, such that it may be used with
    +161   * any interface requiring support for that class.
    +162   *
    +163   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +164   * class docs for more information.</p>
    +165   *
    +166   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +167   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +168   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +169   *
    +170   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +171   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +172   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +173   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +174   *
    +175   * @param future Completable future to wrap.
    +176   * @param <R> Return or emission type of the future.
    +177   * @return Wrapped reactive future object.
    +178   */
    +179  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull CompletableFuture<R> future) {
    +180    //noinspection ConstantConditions
    +181    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` publisher.");
    +182    return wrap(future, MoreExecutors.directExecutor());
    +183  }
    +184
    +185  /**
    +186   * Wrap a regular Java {@link CompletableFuture} in a universal {@link ReactiveFuture}, such that it may be used with
    +187   * any interface requiring support for that class.
    +188   *
    +189   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +190   * class docs for more information.</p>
    +191   *
    +192   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +193   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +194   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +195   *
    +196   * @param future Completable future to wrap.
    +197   * @param executor Executor to use.
    +198   * @param <R> Return or emission type of the future.
    +199   * @return Wrapped reactive future object.
    +200   */
    +201  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull CompletableFuture<R> future, @Nonnull Executor executor) {
    +202    //noinspection ConstantConditions
    +203    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` future.");
    +204    //noinspection ConstantConditions
    +205    if (executor == null) throw new IllegalArgumentException("Cannot wrap future with `null` executor.");
    +206    return new ReactiveFuture<>(future, executor);
    +207  }
    +208
    +209  /**
    +210   * Wrap a Guava {@link ListenableFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +211   * interface requiring a supported async or future value.
    +212   *
    +213   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +214   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +215   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +216   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +217   *
    +218   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +219   * class docs for more information.</p>
    +220   *
    +221   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +222   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +223   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +224   *
    +225   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +226   * @param future Future value to wrap.
    +227   * @param <R> Return value type for the future.
    +228   * @return Wrapped reactive future object.
    +229   * @throws IllegalArgumentException If the passed `future` is `null`.
    +230   */
    +231  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ListenableFuture<R> future) {
    +232    return wrap(future, MoreExecutors.directExecutor());
    +233  }
    +234
    +235  /**
    +236   * Wrap a Guava {@link ListenableFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +237   * interface requiring a supported async or future value.
    +238   *
    +239   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +240   * class docs for more information.</p>
    +241   *
    +242   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +243   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +244   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +245   *
    +246   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +247   * @param future Future value to wrap.
    +248   * @param executor Executor to dispatch callbacks with.
    +249   * @param <R> Return value type for the future.
    +250   * @return Wrapped reactive future object.
    +251   * @throws IllegalArgumentException If the passed `future` is `null`.
    +252   */
    +253  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ListenableFuture<R> future, @Nonnull Executor executor) {
    +254    //noinspection ConstantConditions
    +255    if (future == null) throw new IllegalArgumentException("Cannot wrap `null` future.");
    +256    //noinspection ConstantConditions
    +257    if (executor == null) throw new IllegalArgumentException("Cannot wrap future with `null` executor.");
    +258    return new ReactiveFuture<>(future, executor);
    +259  }
    +260
    +261  /**
    +262   * Wrap a Google APIs {@link ApiFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +263   * interface requiring a supported async or future value.
    +264   *
    +265   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +266   * class docs for more information.</p>
    +267   *
    +268   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +269   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +270   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +271   *
    +272   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +273   * @see #wrap(ListenableFuture, Executor) Wraps a regular Guava {@link ListenableFuture}.
    +274   * @param apiFuture API future to wrap.
    +275   * @param executor Executor to run callbacks with.
    +276   * @param <R> Return value type for the future.
    +277   * @return Wrapped reactive future object.
    +278   * @throws IllegalArgumentException If the passed `apiFuture` is `null`.
    +279   */
    +280  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ApiFuture<R> apiFuture, @Nonnull Executor executor) {
    +281    //noinspection ConstantConditions
    +282    if (apiFuture == null) throw new IllegalArgumentException("Cannot wrap `null` API future.");
    +283    return wrap(new ApiFutureToListenableFuture<>(apiFuture), executor);
    +284  }
    +285
    +286  /**
    +287   * Wrap a Google APIs {@link ApiFuture} in a universal {@link ReactiveFuture}, such that it may be used with any
    +288   * interface requiring a supported async or future value.
    +289   *
    +290   * <p><b>Warning:</b> this method uses {@link MoreExecutors#directExecutor()} for callback execution. You should only
    +291   * do this if the callbacks associated with your future are lightweight and exit quickly. Otherwise, it is heavily
    +292   * recommended to use the variants of {@code wrap} that accept an {@link Executor}. For instance, the corresponding
    +293   * method to this one is {@link #wrap(ListenableFuture, Executor)}.</p>
    +294   *
    +295   * <p>The resulting object is usable as any of {@link ListenableFuture}, {@link Publisher}, or {@link ApiFuture}. See
    +296   * class docs for more information.</p>
    +297   *
    +298   * <p><b>Note:</b> to use a {@link Publisher} as a {@link Future} (or any descendent thereof), the {@link Publisher}
    +299   * may only emit one value, and no more. Emitting multiple items is considered an error when wrapped in this class and
    +300   * accessed as a {@link Future}, to prevent silently dropping intermediate values on the floor.</p>
    +301   *
    +302   * @see #wrap(Publisher) Wraps a Reactive Java {@link Publisher}.
    +303   * @see #wrap(ListenableFuture, Executor) Wraps a regular Guava {@link ListenableFuture}.
    +304   * @param apiFuture API future to wrap.
    +305   * @param <R> Return value type for the future.
    +306   * @return Wrapped reactive future object.
    +307   * @throws IllegalArgumentException If the passed `apiFuture` is `null`.
    +308   */
    +309  public static @Nonnull <R> ReactiveFuture<R> wrap(@Nonnull ApiFuture<R> apiFuture) {
    +310    return wrap(apiFuture, MoreExecutors.directExecutor());
    +311  }
    +312
    +313  /**
    +314   * Create an already-resolved future, wrapping the provided value. The future will present as done as soon as it is
    +315   * returned from this method.
    +316   *
    +317   * <p>Under the hood, this is simply a {@link ReactiveFuture} wrapping a call to
    +318   * {@link Futures#immediateFuture(Object)}.</p>
    +319   *
    +320   * @param value Value to wrap in an already-completed future.
    +321   * @param <R> Return value generic type.
    +322   * @return Reactive future wrapping a finished value.
    +323   */
    +324  public static @Nonnull <R> ReactiveFuture<R> done(@Nonnull R value) {
    +325    return wrap(Futures.immediateFuture(value));
    +326  }
    +327
    +328  /**
    +329   * Create an already-failed future, wrapping the provided exception instance. The future will present as one as soon
    +330   * as it is returned from this method.
    +331   *
    +332   * <p>Calling {@link Future#get(long, TimeUnit)} or {@link Future#get()} on a failed future will surface the
    +333   * associated exception where invocation occurs. Under the hood, this is simply a {@link ReactiveFuture} wrapping a
    +334   * call to {@link Futures#immediateFailedFuture(Throwable)}.</p>
    +335   *
    +336   * @param error Error to wrap in an already-failed future.
    +337   * @param <R> Return value generic type.
    +338   * @return Reactive future wrapping a finished value.
    +339   */
    +340  public static @Nonnull <R> ReactiveFuture<R> failed(@Nonnull Throwable error) {
    +341    return wrap(Futures.immediateFailedFuture(error));
    +342  }
    +343
    +344  /**
    +345   * Create an already-cancelled future. The future will present as both done and cancelled as soon as it is returned
    +346   * from this method.
    +347   *
    +348   * <p>Under the hood, this is simply a {@link ReactiveFuture} wrapping a call to
    +349   * {@link Futures#immediateCancelledFuture()}.</p>
    +350   *
    +351   * @param <R> Return value generic type.
    +352   * @return Reactive future wrapping a cancelled operation.
    +353   */
    +354  public static @Nonnull <R> ReactiveFuture<R> cancelled() {
    +355    return wrap(Futures.immediateCancelledFuture());
    +356  }
    +357
    +358  // -- Compliance: Publisher -- //
    +359  /**
    +360   * Request {@link Publisher} to start streaming data.
    +361   *
    +362   * <p>This is a "factory method" and can be called multiple times, each time starting a new {@link Subscription}. Each
    +363   * {@link Subscription} will work for only a single {@link Subscriber}. A {@link Subscriber} should only subscribe
    +364   * once to a single {@link Publisher}. If the {@link Publisher} rejects the subscription attempt or otherwise fails it
    +365   * will signal the error via {@link Subscriber#onError}.</p>
    +366   *
    +367   * @param subscriber the {@link Subscriber} that will consume signals from this {@link Publisher}.
    +368   */
    +369  @Override
    +370  public void subscribe(Subscriber<? super R> subscriber) {
    +371    resolvePublisher().subscribe(subscriber);
    +372  }
    +373
    +374  // -- Compliance: Listenable Future -- //
    +375  /**
    +376   * Registers a listener to be {@linkplain Executor#execute(Runnable) run} on the given executor. The listener will run
    +377   * when the {@code Future}'s computation is {@linkplain Future#isDone() complete} or, if the computation is already
    +378   * complete, immediately.
    +379   *
    +380   * <p>There is no guaranteed ordering of execution of listeners, but any listener added through this method is
    +381   * guaranteed to be called once the computation is complete.</p>
    +382   *
    +383   * <p>Exceptions thrown by a listener will be propagated up to the executor. Any exception thrown during
    +384   * {@code Executor.execute} (e.g., a {@code RejectedExecutionException} or an exception thrown by
    +385   * {@linkplain MoreExecutors#directExecutor direct execution}) will be caught and logged.</p>
    +386   *
    +387   * <p>Note: For fast, lightweight listeners that would be safe to execute in any thread, consider
    +388   * {@link MoreExecutors#directExecutor}. Otherwise, avoid it. Heavyweight {@code directExecutor} listeners can cause
    +389   * problems, and these problems can be difficult to reproduce because they depend on timing. For example:</p>
    +390   * <ul>
    +391   *   <li>The listener may be executed by the caller of {@code addListener}. That caller may be a
    +392   *       UI thread or other latency-sensitive thread. This can harm UI responsiveness.
    +393   *   <li>The listener may be executed by the thread that completes this {@code Future}. That
    +394   *       thread may be an internal system thread such as an RPC network thread. Blocking that
    +395   *       thread may stall progress of the whole system. It may even cause a deadlock.
    +396   *   <li>The listener may delay other listeners, even listeners that are not themselves {@code
    +397   *       directExecutor} listeners.
    +398   * </ul>
    +399   *
    +400   * <p>This is the most general listener interface. For common operations performed using listeners, see
    +401   * {@link Futures}. For a simplified but general listener interface, see
    +402   * {@link Futures#addCallback addCallback()}.</p>
    +403   *
    +404   * <p>Memory consistency effects: Actions in a thread prior to adding a listener <a
    +405   * href="https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5"><i>happen-before</i></a> its
    +406   * execution begins, perhaps in another thread.</p>
    +407   *
    +408   * <p>Guava implementations of {@code ListenableFuture} promptly release references to listeners after executing
    +409   * them.</p>
    +410   *
    +411   * @param listener the listener to run when the computation is complete.
    +412   * @param executor the executor to run the listener in
    +413   * @throws RejectedExecutionException if we tried to execute the listener immediately but the executor rejecte it.
    +414   */
    +415  @Override
    +416  public void addListener(@Nonnull Runnable listener, @Nonnull Executor executor) throws RejectedExecutionException {
    +417    resolveFuture().addListener(listener, executor);
    +418  }
    +419
    +420  /**
    +421   * Attempts to cancel execution of this task.  This attempt will fail if the task has already completed, has already
    +422   * been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when
    +423   * {@code cancel} is called, this task should never run.  If the task has already started, then the
    +424   * {@code mayInterruptIfRunning} parameter determines whether the thread executing this task should be interrupted in
    +425   * an attempt to stop the task.
    +426   *
    +427   * <p>After this method returns, subsequent calls to {@link #isDone} will always return {@code true}.  Subsequent
    +428   * calls to {@link #isCancelled} will always return {@code true} if this method returned {@code true}.
    +429   *
    +430   * @param mayInterruptIfRunning {@code true} if the thread executing this task should be interrupted; otherwise,
    +431   *                              in-progress tasks are allowed to complete
    +432   * @return {@code false} if the task could not be cancelled, typically because it has already completed normally;
    +433   *         {@code true} otherwise.
    +434   */
    +435  @Override
    +436  public boolean cancel(boolean mayInterruptIfRunning) {
    +437    return resolveFuture().cancel(mayInterruptIfRunning);
    +438  }
    +439
    +440  /**
    +441   * Returns {@code true} if this task was cancelled before it completed normally. This defers to the underlying future,
    +442   * or a wrapped object if using a {@link Publisher}.
    +443   *
    +444   * @return {@code true} if this task was cancelled before it completed
    +445   */
    +446  @Override
    +447  public boolean isCancelled() {
    +448    return resolveFuture().isCancelled();
    +449  }
    +450
    +451  /**
    +452   * Returns {@code true} if this task completed. This defers to the underlying future, or a wrapped object if using a
    +453   * Reactive Java {@link Publisher}.
    +454   *
    +455   * Completion may be due to normal termination, an exception, or cancellation -- in all of these cases, this method
    +456   * will return {@code true}.
    +457   *
    +458   * @return {@code true} if this task completed.
    +459   */
    +460  @Override
    +461  public boolean isDone() {
    +462    return resolveFuture().isDone();
    +463  }
    +464
    +465  /**
    +466   * Waits if necessary for the computation to complete, and then retrieves its result.
    +467   *
    +468   * <p>It is generally recommended to use the variant of this method which specifies a timeout - one must handle the
    +469   * additional {@link TimeoutException}, but on the other hand the computation can never infinitely block if an async
    +470   * value does not materialize.</p>
    +471   *
    +472   * @see #get(long, TimeUnit) For a safer version of this method, which allows specifying a timeout.
    +473   * @return the computed result.
    +474   * @throws CancellationException if the computation was cancelled
    +475   * @throws ExecutionException    if the computation threw an exception
    +476   * @throws InterruptedException  if the current thread was interrupted while waiting
    +477   */
    +478  @Override
    +479  public R get() throws InterruptedException, ExecutionException {
    +480    return resolveFuture().get();
    +481  }
    +482
    +483  /**
    +484   * Waits if necessary for at most the given time for the computation to complete, and then retrieves its result, if
    +485   * available.
    +486   *
    +487   * @param timeout the maximum time to wait
    +488   * @param unit    the time unit of the timeout argument
    +489   * @return the computed result
    +490   * @throws CancellationException if the computation was cancelled
    +491   * @throws ExecutionException    if the computation threw an exception
    +492   * @throws InterruptedException  if the current thread was interrupted while waiting
    +493   * @throws TimeoutException      if the wait timed out
    +494   */
    +495  @Override
    +496  public R get(long timeout, @Nonnull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
    +497    return resolveFuture().get(timeout, unit);
    +498  }
    +499
    +500  /**
    +501   * Structure that adapts a {@link Publisher} to a {@link ListenableFuture} interface. We accomplish this by
    +502   * immediately subscribing to the publisher with a callback that dispatches a {@link SettableFuture}.
    +503   *
    +504   * <p>This object is used in the specific circumstance of wrapping a {@link Publisher}, and then using the wrapped
    +505   * object as a {@link ListenableFuture} (or any descendent or compliant implementation thereof).</p>
    +506   *
    +507   * @param <T> Generic type returned by the future.
    +508   */
    +509  @Immutable
    +510  @ThreadSafe
    +511  public final static class PublisherListenableFuture<T> implements ListenableFuture<T>, Publisher<T> {
    +512    /** Whether we have received a value. */
    +513    private final @Nonnull AtomicBoolean received = new AtomicBoolean(false);
    +514
    +515    /** Whether we have completed acquiring a value. */
    +516    private final @Nonnull AtomicBoolean completed = new AtomicBoolean(false);
    +517
    +518    /** Whether we have been cancelled. */
    +519    private final @Nonnull AtomicBoolean cancelled = new AtomicBoolean(false);
    +520
    +521    /** Describes the list of proxied subscribers. */
    +522    private final @Nonnull Map<String, Subscriber<? super T>> subscribers = new ConcurrentHashMap<>();
    +523
    +524    /** Converted/pass-through future value. */
    +525    private final @Nonnull SettableFuture<T> future;
    +526
    +527    /** Subscription, so we can propagate cancellation. */
    +528    private volatile Subscription subscription;
    +529
    +530    /**
    +531     * Private constructor.
    +532     *
    +533     * @param publisher Publisher to wrap.
    +534     */
    +535    private PublisherListenableFuture(@Nonnull Publisher<T> publisher) {
    +536      this.future = SettableFuture.create();
    +537      publisher.subscribe(new Subscriber<T>() {
    +538        @Override
    +539        public void onSubscribe(Subscription s) {
    +540          PublisherListenableFuture.this.subscription = s;
    +541        }
    +542
    +543        @Override
    +544        public void onNext(T t) {
    +545          if (received.compareAndSet(false, true)) {
    +546            PublisherListenableFuture.this.proxyExecute((sub) -> sub.onNext(t));
    +547            future.set(t);
    +548            return;
    +549          }
    +550          this.onError(new IllegalStateException(
    +551            "Cannot publish multiple items through `ReactiveFuture`."));
    +552        }
    +553
    +554        @Override
    +555        public void onError(Throwable t) {
    +556          if (!completed.get()) {
    +557            PublisherListenableFuture.this.proxyExecute((sub) -> sub.onError(t));
    +558            future.setException(t);
    +559          }
    +560        }
    +561
    +562        @Override
    +563        public void onComplete() {
    +564          if (completed.compareAndSet(false, true)) {
    +565            PublisherListenableFuture.this.proxyExecute(Subscriber::onComplete);
    +566            PublisherListenableFuture.this.clear();
    +567          }
    +568        }
    +569      });
    +570    }
    +571
    +572    /**
    +573     * Call something on each proxied publisher subscription, if any.
    +574     *
    +575     * @param operation Operation to execute. Called for each subscriber.
    +576     */
    +577    private void proxyExecute(@Nonnull Consumer<Subscriber<? super T>> operation) {
    +578      if (!this.subscribers.isEmpty()) {
    +579        this.subscribers.values().forEach(operation);
    +580      }
    +581    }
    +582
    +583    /**
    +584     * Remove all subscribers and clear references to futures/publishers/listeners.
    +585     */
    +586    private void clear() {
    +587      this.subscribers.clear();
    +588      this.subscription = null;
    +589    }
    +590
    +591    /**
    +592     * Drop a subscription (after proxied {@link Subscription#cancel()} is called).
    +593     *
    +594     * @param id ID of the subscription to drop.
    +595     */
    +596    private void dropSubscription(@Nonnull String id) {
    +597      this.subscribers.get(id).onComplete();
    +598      this.subscribers.remove(id);
    +599    }
    +600
    +601    // -- Interface Compliance: Publisher -- //
    +602
    +603    @Override
    +604    public void subscribe(Subscriber<? super T> s) {
    +605      final String id = String.valueOf(this.subscribers.size());
    +606      Subscription sub = new Subscription() {
    +607        @Override
    +608        public void request(long n) {
    +609          PublisherListenableFuture.this.subscription.request(n);
    +610        }
    +611
    +612        @Override
    +613        public void cancel() {
    +614          // kill self
    +615          PublisherListenableFuture.this.dropSubscription(id);
    +616        }
    +617      };
    +618      this.subscribers.put(id, s);
    +619      s.onSubscribe(sub);
    +620    }
    +621
    +622    // -- Interface Compliance: Listenable Future -- //
    +623
    +624    @Override
    +625    public void addListener(@Nonnull Runnable runnable, @Nonnull Executor executor) {
    +626      this.future.addListener(runnable, executor);
    +627    }
    +628
    +629    @Override
    +630    public boolean cancel(boolean mayInterruptIfRunning) {
    +631      boolean cancelled = false;
    +632      if (!this.completed.get() && this.cancelled.compareAndSet(false, true)) {
    +633        this.proxyExecute(Subscriber::onComplete);  // dispatch `onComplete` for any subscribers
    +634        this.subscription.cancel();  // cancel upwards
    +635        cancelled = this.future.cancel(mayInterruptIfRunning);  // cancel future
    +636        this.clear();  // clear references
    +637      }
    +638      return cancelled;
    +639    }
    +640
    +641    @Override
    +642    public boolean isCancelled() {
    +643      return this.cancelled.get();
    +644    }
    +645
    +646    @Override
    +647    public boolean isDone() {
    +648      return this.completed.get() || this.cancelled.get();
    +649    }
    +650
    +651    @Override
    +652    public T get() throws InterruptedException, ExecutionException {
    +653      return this.future.get();
    +654    }
    +655
    +656    @Override
    +657    public T get(long timeout, @Nonnull TimeUnit unit)
    +658        throws InterruptedException, ExecutionException, TimeoutException {
    +659      return this.future.get(timeout, unit);
    +660    }
    +661  }
    +662
    +663  /**
    +664   * Structure that adapts Java's {@link CompletableFuture} to a Reactive Java {@link Publisher}, which publishes one
    +665   * item - either the result of the computation, or an error.
    +666   *
    +667   * <p>This object is used in the specific circumstance that a {@link CompletableFuture} is wrapped by a
    +668   * {@link ReactiveFuture}, and then used within the Reactive Java or Guava ecosystems as a {@link Publisher} or a
    +669   * {@link ListenableFuture} (or {@link ApiFuture}), or a descendent thereof. As in {@link ListenableFuturePublisher},
    +670   * we simply set the callback for the future value, upon item-request (one cycle is allowed), and propagate any events
    +671   * received to the publisher.</p>
    +672   *
    +673   * @param <T> Emit type for this adapter. Matches the future it wraps.
    +674   */
    +675  public final static class CompletableFuturePublisher<T>
    +676      implements Publisher<T>, ListenableFuture<T>, CompletionStage<T> {
    +677    private final @Nonnull CompletableFuture<T> future;
    +678    private final @Nonnull CompletionStage<T> stage;
    +679    private final @Nonnull Executor callbackExecutor;
    +680
    +681    /**
    +682     * Construct an adapter that propagates signals from a {@link CompletableFuture} to a {@link Publisher}.
    +683     *
    +684     * @param future Completable future to wrap.
    +685     * @param callbackExecutor Callback executor to use.
    +686     */
    +687    private CompletableFuturePublisher(@Nonnull CompletableFuture<T> future,
    +688                                       @Nonnull Executor callbackExecutor) {
    +689      this.future = future;
    +690      this.stage = future;
    +691      this.callbackExecutor = callbackExecutor;
    +692    }
    +693
    +694    /* == `Future`/`ListenableFuture` Interface Compliance == */
    +695
    +696    /** @inheritDoc */
    +697    @Override public final void subscribe(Subscriber<? super T> subscriber) {
    +698      Objects.requireNonNull(subscriber, "Subscriber cannot be null");
    +699      subscriber.onSubscribe(new CompletableFutureSubscription(this.future, subscriber, this.callbackExecutor));
    +700    }
    +701
    +702    /** @inheritDoc */
    +703    @Override public void addListener(Runnable runnable, Executor executor) {
    +704      future.thenRunAsync(runnable, executor);
    +705    }
    +706
    +707    /** @inheritDoc */
    +708    @Override public boolean cancel(boolean mayInterruptIfRunning) {
    +709      return future.cancel(mayInterruptIfRunning);
    +710    }
    +711
    +712    /** @inheritDoc */
    +713    @Override public boolean isCancelled() {
    +714      return future.isCancelled();
    +715    }
    +716
    +717    /** @inheritDoc */
    +718    @Override public boolean isDone() {
    +719      return future.isDone();
    +720    }
    +721
    +722    /** @inheritDoc */
    +723    @Override public T get() throws InterruptedException, ExecutionException {
    +724      return future.get();
    +725    }
    +726
    +727    /** @inheritDoc */
    +728    @Override
    +729    public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
    +730      return future.get(timeout, unit);
    +731    }
    +732
    +733    /* == `CompletionStage` Interface Compliance == */
    +734
    +735    /** @inheritDoc */
    +736    @Override public <U> CompletionStage<U> thenApply(Function<? super T, ? extends U> fn) {
    +737      return stage.thenApply(fn);
    +738    }
    +739
    +740    /** @inheritDoc */
    +741    @Override public <U> CompletionStage<U> thenApplyAsync(Function<? super T, ? extends U> fn) {
    +742      return stage.thenApplyAsync(fn);
    +743    }
    +744
    +745    /** @inheritDoc */
    +746    @Override public <U> CompletionStage<U> thenApplyAsync(Function<? super T, ? extends U> fn, Executor executor) {
    +747      return stage.thenApplyAsync(fn, executor);
    +748    }
    +749
    +750    /** @inheritDoc */
    +751    @Override public CompletionStage<Void> thenAccept(Consumer<? super T> action) {
    +752      return stage.thenAccept(action);
    +753    }
    +754
    +755    /** @inheritDoc */
    +756    @Override public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action) {
    +757      return stage.thenAcceptAsync(action);
    +758    }
    +759
    +760    /** @inheritDoc */
    +761    @Override public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor) {
    +762      return stage.thenAcceptAsync(action, executor);
    +763    }
    +764
    +765    /** @inheritDoc */
    +766    @Override public CompletionStage<Void> thenRun(Runnable action) {
    +767      return stage.thenRun(action);
    +768    }
    +769
    +770    /** @inheritDoc */
    +771    @Override public CompletionStage<Void> thenRunAsync(Runnable action) {
    +772      return stage.thenRunAsync(action);
    +773    }
    +774
    +775    /** @inheritDoc */
    +776    @Override public CompletionStage<Void> thenRunAsync(Runnable action, Executor executor) {
    +777      return stage.thenRunAsync(action, executor);
    +778    }
    +779
    +780    /** @inheritDoc */
    +781    @Override public <U, V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,
    +782                                                           BiFunction<? super T, ? super U, ? extends V> fn) {
    +783      return stage.thenCombine(other, fn);
    +784    }
    +785
    +786    /** @inheritDoc */
    +787    @Override public <U, V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,
    +788                                                                BiFunction<? super T, ? super U, ? extends V> fn) {
    +789      return stage.thenCombineAsync(other, fn);
    +790    }
    +791
    +792    /** @inheritDoc */
    +793    @Override public <U, V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,
    +794                                                                BiFunction<? super T, ? super U, ? extends V> fn,
    +795                                                                Executor executor) {
    +796      return stage.thenCombineAsync(other, fn, executor);
    +797    }
    +798
    +799    /** @inheritDoc */
    +800    @Override public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,
    +801                                                              BiConsumer<? super T, ? super U> action) {
    +802      return stage.thenAcceptBoth(other, action);
    +803    }
    +804
    +805    /** @inheritDoc */
    +806    @Override public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
    +807                                                                   BiConsumer<? super T, ? super U> action) {
    +808      return stage.thenAcceptBothAsync(other, action);
    +809    }
    +810
    +811    /** @inheritDoc */
    +812    @Override
    +813    public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
    +814                                                         BiConsumer<? super T, ? super U> action,
    +815                                                         Executor executor) {
    +816      return stage.thenAcceptBothAsync(other, action, executor);
    +817    }
    +818
    +819    /** @inheritDoc */
    +820    @Override
    +821    public CompletionStage<Void> runAfterBoth(CompletionStage<?> other, Runnable action) {
    +822      return stage.runAfterBoth(other, action);
    +823    }
    +824
    +825    /** @inheritDoc */
    +826    @Override
    +827    public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action) {
    +828      return stage.runAfterBothAsync(other, action);
    +829    }
    +830
    +831    /** @inheritDoc */
    +832    @Override
    +833    public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action, Executor executor) {
    +834      return stage.runAfterBothAsync(other, action, executor);
    +835    }
    +836
    +837    /** @inheritDoc */
    +838    @Override
    +839    public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn) {
    +840      return stage.applyToEither(other, fn);
    +841    }
    +842
    +843    /** @inheritDoc */
    +844    @Override
    +845    public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn) {
    +846      return stage.applyToEitherAsync(other, fn);
    +847    }
    +848
    +849    /** @inheritDoc */
    +850    @Override
    +851    public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,
    +852                                                     Function<? super T, U> fn,
    +853                                                     Executor executor) {
    +854      return stage.applyToEitherAsync(other, fn, executor);
    +855    }
    +856
    +857    /** @inheritDoc */
    +858    @Override public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,
    +859                                                        Consumer<? super T> action) {
    +860      return stage.acceptEither(other, action);
    +861    }
    +862
    +863    /** @inheritDoc */
    +864    @Override public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,
    +865                                                             Consumer<? super T> action) {
    +866      return stage.acceptEitherAsync(other, action);
    +867    }
    +868
    +869    /** @inheritDoc */
    +870    @Override public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,
    +871                                                             Consumer<? super T> action,
    +872                                                             Executor executor) {
    +873      return stage.acceptEitherAsync(other, action, executor);
    +874    }
    +875
    +876    /** @inheritDoc */
    +877    @Override public CompletionStage<Void> runAfterEither(CompletionStage<?> other, Runnable action) {
    +878      return stage.runAfterEither(other, action);
    +879    }
    +880
    +881    /** @inheritDoc */
    +882    @Override public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action) {
    +883      return stage.runAfterEitherAsync(other, action);
    +884    }
    +885
    +886    /** @inheritDoc */
    +887    @Override public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action, Executor executor) {
    +888      return stage.runAfterEitherAsync(other, action, executor);
    +889    }
    +890
    +891    /** @inheritDoc */
    +892    @Override public <U> CompletionStage<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) {
    +893      return stage.thenCompose(fn);
    +894    }
    +895
    +896    /** @inheritDoc */
    +897    @Override public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) {
    +898      return stage.thenComposeAsync(fn);
    +899    }
    +900
    +901    /** @inheritDoc */
    +902    @Override public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn,
    +903                                                             Executor executor) {
    +904      return stage.thenComposeAsync(fn, executor);
    +905    }
    +906
    +907    /** @inheritDoc */
    +908    @Override public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) {
    +909      return stage.handle(fn);
    +910    }
    +911
    +912    /** @inheritDoc */
    +913    @Override public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) {
    +914      return stage.handleAsync(fn);
    +915    }
    +916
    +917    /** @inheritDoc */
    +918    @Override public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,
    +919                                                        Executor executor) {
    +920      return stage.handleAsync(fn, executor);
    +921    }
    +922
    +923    /** @inheritDoc */
    +924    @Override public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> action) {
    +925      return stage.whenComplete(action);
    +926    }
    +927
    +928    /** @inheritDoc */
    +929    @Override public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action) {
    +930      return stage.whenCompleteAsync(action);
    +931    }
    +932
    +933    /** @inheritDoc */
    +934    @Override public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action,
    +935                                                          Executor executor) {
    +936      return stage.whenCompleteAsync(action, executor);
    +937    }
    +938
    +939    /** @inheritDoc */
    +940    @Override public CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn) {
    +941      return stage.exceptionally(fn);
    +942    }
    +943
    +944    /** @inheritDoc */
    +945    @Override public CompletableFuture<T> toCompletableFuture() {
    +946      return stage.toCompletableFuture();
    +947    }
    +948
    +949    /**
    +950     * Models a Reactive Java {@link Subscription}, which is responsible for propagating events from a
    +951     * Concurrent Java {@link CompletableFuture} to a {@link Subscriber}.
    +952     *
    +953     * <p>This object is generally used internally by the {@link CompletableFuturePublisher}, once a {@link Subscriber}
    +954     * attaches itself to a {@link Publisher} that is actually a wrapped {@link CompletableFuture}. Error (exception)
    +955     * events and value events are both propagated. Subscribers based on this wrapping will only ever receive a maximum
    +956     * of <b>one value</b> or <b>one error</b>.</p>
    +957     */
    +958    @Immutable
    +959    @ThreadSafe
    +960    public final class CompletableFutureSubscription implements Subscription {
    +961      private final AtomicBoolean completed = new AtomicBoolean(false);
    +962      private final @Nonnull Subscriber<? super T> subscriber;
    +963      private final @Nonnull CompletableFuture<T> future;
    +964      private final @Nonnull Executor executor;
    +965
    +966      /**
    +967       * Private constructor, meant for use by {@link CompletableFuturePublisher} only.
    +968       *
    +969       * @param future Future value to adapt.
    +970       * @param subscriber The subscriber.
    +971       * @param executor Executor to run callbacks with.
    +972       */
    +973      CompletableFutureSubscription(@Nonnull CompletableFuture<T> future,
    +974                                    @Nonnull Subscriber<? super T> subscriber,
    +975                                    @Nonnull Executor executor) {
    +976        this.future = Objects.requireNonNull(future);
    +977        this.subscriber = Objects.requireNonNull(subscriber);
    +978        this.executor = Objects.requireNonNull(executor);
    +979      }
    +980
    +981      /**
    +982       * Request the specified number of items from the underlying {@link Subscription}. This must <b>always be
    +983       * <pre>1</pre></b>.
    +984       *
    +985       * @param n Number of elements to request to the upstream (must always be <pre>1</pre>).
    +986       * @throws IllegalArgumentException If any value other than <pre>1</pre> is passed in.
    +987       */
    +988      public synchronized void request(long n) {
    +989        if (n == 1 && !completed.get()) {
    +990          try {
    +991            CompletableFuture<T> future = this.future;
    +992            future.thenAcceptAsync(t -> {
    +993              T val = null;
    +994              Throwable err = null;
    +995              try {
    +996                val = future.get();
    +997              } catch (Exception exc) {
    +998                err = exc;
    +999              }
    +1000
    +1001              if (completed.compareAndSet(false, true)) {
    +1002                if (err != null) {
    +1003                  subscriber.onError(err);
    +1004                } else {
    +1005                  if (val != null) {
    +1006                    subscriber.onNext(val);
    +1007                  }
    +1008                  subscriber.onComplete();
    +1009                }
    +1010              }
    +1011            }, executor);
    +1012
    +1013          } catch (Exception e) {
    +1014            subscriber.onError(e);
    +1015          }
    +1016        } else if (n != 1) {
    +1017          IllegalArgumentException ex = new IllegalArgumentException(
    +1018              "Cannot request more or less than 1 item from a ReactiveFuture-wrapped publisher.");
    +1019          subscriber.onError(ex);
    +1020        }
    +1021      }
    +1022
    +1023      /**
    +1024       * Request the publisher to stop sending data and clean up resources.
    +1025       */
    +1026      public synchronized void cancel() {
    +1027        if (completed.compareAndSet(false, true)) {
    +1028          subscriber.onComplete();
    +1029          future.cancel(false);
    +1030        }
    +1031      }
    +1032    }
    +1033  }
    +1034
    +1035  /**
    +1036   * Structure that adapts Guava's {@link ListenableFuture} to a Reactive Java {@link Publisher}, which publishes one
    +1037   * item - either the result of the computation, or an error.
    +1038   *
    +1039   * <p>This object is used in the specific circumstance that a {@link ListenableFuture} is wrapped by a
    +1040   * {@link ReactiveFuture}, and then used within the Reactive Java ecosystem as a {@link Publisher}. We simply set a
    +1041   * callback for the future value, upon item-request (one cycle is allowed), and propagate any events received to the
    +1042   * publisher.</p>
    +1043   *
    +1044   * @param <T> Emit type for this adapter. Matches the publisher it wraps.
    +1045   */
    +1046  public final static class ListenableFuturePublisher<T> implements Publisher<T> {
    +1047    private final @Nonnull ListenableFuture<T> future;
    +1048    private final @Nonnull Executor callbackExecutor;
    +1049
    +1050    /**
    +1051     * Wrap a {@link ListenableFuture}. Private constructor for use by {@link ReactiveFuture} only.
    +1052     *
    +1053     * @param future The future to convert or wait on.
    +1054     * @param callbackExecutor Executor to run the callback on.
    +1055     */
    +1056    private ListenableFuturePublisher(@Nonnull ListenableFuture<T> future,
    +1057                                      @Nonnull Executor callbackExecutor) {
    +1058      this.future = future;
    +1059      this.callbackExecutor = callbackExecutor;
    +1060    }
    +1061
    +1062    @Override
    +1063    public final void subscribe(Subscriber<? super T> subscriber) {
    +1064      Objects.requireNonNull(subscriber, "Subscriber cannot be null");
    +1065      subscriber.onSubscribe(new ListenableFutureSubscription(this.future, subscriber, this.callbackExecutor));
    +1066    }
    +1067
    +1068    /**
    +1069     * Models a Reactive Java {@link Subscription}, which is responsible for propagating events from a
    +1070     * {@link ListenableFuture} to a {@link Subscriber}.
    +1071     *
    +1072     * <p>This object is generally used internally by the {@link ListenableFuturePublisher}, once a {@link Subscriber}
    +1073     * attaches itself to a {@link Publisher} that is actually a wrapped {@link ListenableFuture}. Error (exception)
    +1074     * events and value events are both propagated. Subscribers based on this wrapping will only ever receive a maximum
    +1075     * of <b>one value</b> or <b>one error</b>.</p>
    +1076     */
    +1077    @Immutable
    +1078    @ThreadSafe
    +1079    public final class ListenableFutureSubscription implements Subscription {
    +1080      private final AtomicBoolean completed = new AtomicBoolean(false);
    +1081      private final @Nonnull Subscriber<? super T> subscriber;
    +1082      private final @Nonnull ListenableFuture<T> future; // to allow cancellation
    +1083      private final @Nonnull Executor executor;  // executor to use when dispatching the callback
    +1084
    +1085      /**
    +1086       * Private constructor, meant for use by {@link ListenableFuturePublisher} only.
    +1087       *
    +1088       * @param future Future value to adapt.
    +1089       * @param subscriber The subscriber.
    +1090       * @param executor Executor to run callbacks with.
    +1091       */
    +1092      ListenableFutureSubscription(@Nonnull ListenableFuture<T> future,
    +1093                                   @Nonnull Subscriber<? super T> subscriber,
    +1094                                   @Nonnull Executor executor) {
    +1095        this.future = Objects.requireNonNull(future);
    +1096        this.subscriber = Objects.requireNonNull(subscriber);
    +1097        this.executor = Objects.requireNonNull(executor);
    +1098      }
    +1099
    +1100      /**
    +1101       * Request the specified number of items from the underlying {@link Subscription}. This must <b>always be
    +1102       * <pre>1</pre></b>.
    +1103       *
    +1104       * @param n Number of elements to request to the upstream (must always be <pre>1</pre>).
    +1105       * @throws IllegalArgumentException If any value other than <pre>1</pre> is passed in.
    +1106       */
    +1107      public synchronized void request(long n) {
    +1108        if (n == 1 && !completed.get()) {
    +1109          try {
    +1110            ListenableFuture<T> future = this.future;
    +1111            future.addListener(() -> {
    +1112              T val = null;
    +1113              Throwable err = null;
    +1114              try {
    +1115                val = this.future.get();
    +1116              } catch (Exception exc) {
    +1117                err = exc;
    +1118              }
    +1119
    +1120              if (completed.compareAndSet(false, true)) {
    +1121                if (err != null) {
    +1122                  subscriber.onError(err);
    +1123                } else {
    +1124                  if (val != null) {
    +1125                    subscriber.onNext(val);
    +1126                  }
    +1127                  subscriber.onComplete();
    +1128                }
    +1129              }
    +1130            }, this.executor);
    +1131          } catch (Exception e) {
    +1132            subscriber.onError(e);
    +1133          }
    +1134        } else if (n != 1) {
    +1135          IllegalArgumentException ex = new IllegalArgumentException(
    +1136            "Cannot request more or less than 1 item from a ReactiveFuture-wrapped publisher.");
    +1137          subscriber.onError(ex);
    +1138        }
    +1139      }
    +1140
    +1141      /**
    +1142       * Request the publisher to stop sending data and clean up resources.
    +1143       */
    +1144      public synchronized void cancel() {
    +1145        if (completed.compareAndSet(false, true)) {
    +1146          subscriber.onComplete();
    +1147          future.cancel(false);
    +1148        }
    +1149      }
    +1150    }
    +1151  }
    +1152}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/transport/GoogleAPIChannel.html b/docs/java/src-html/gust/backend/transport/GoogleAPIChannel.html new file mode 100644 index 000000000..22564e2d9 --- /dev/null +++ b/docs/java/src-html/gust/backend/transport/GoogleAPIChannel.html @@ -0,0 +1,126 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.transport;
    +014
    +015import io.micronaut.aop.Introduction;
    +016import io.micronaut.context.annotation.Bean;
    +017import io.micronaut.context.annotation.Type;
    +018
    +019import javax.annotation.Nonnull;
    +020import java.lang.annotation.*;
    +021
    +022
    +023/**
    +024 * Specifies an injection qualifier for a managed transport supporting the service specified by this annotation.
    +025 *
    +026 * <p>To use this annotation, decorate a {@link io.grpc.ManagedChannel} parameter with <code>@GoogleAPIChannel</code>,
    +027 * and pass in the enumerated service you wish to receive a connection for. The connection will be initialized and kept
    +028 * in a pool according to current settings and load.</p>
    +029 *
    +030 * <p>For more information about how connections are managed and integrated with Micronaut,
    +031 * see {@link GoogleTransportManager}.</p>
    +032 *
    +033 * @see GoogleTransportManager
    +034 **/
    +035@Bean
    +036@Documented
    +037@Introduction
    +038@Target(ElementType.PARAMETER)
    +039@Retention(RetentionPolicy.RUNTIME)
    +040@Type(GoogleTransportManager.class)
    +041public @interface GoogleAPIChannel {
    +042  /**
    +043   * Service for which this annotation is requesting a {@link io.grpc.ManagedChannel} instance.
    +044   *
    +045   * <p>If no connection to the requested service exists, one will be initialized before being handed back to the user.
    +046   * Otherwise, the user may get an existing singleton connection instance, or a connection instance from a pool of
    +047   * connections, depending on the implementing {@link TransportManager} (usually {@link GoogleTransportManager}).</p>
    +048   *
    +049   * @return Service to return a managed connection for.
    +050   */
    +051  @Nonnull GoogleService service();
    +052}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/transport/GoogleService.html b/docs/java/src-html/gust/backend/transport/GoogleService.html new file mode 100644 index 000000000..af80cc209 --- /dev/null +++ b/docs/java/src-html/gust/backend/transport/GoogleService.html @@ -0,0 +1,142 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.transport;
    +014
    +015import javax.annotation.Nonnull;
    +016import javax.annotation.Nullable;
    +017import java.util.Optional;
    +018
    +019
    +020/** Enumerates Google Cloud services with built-in managed transport support. */
    +021public enum GoogleService {
    +022  /** Google Cloud Pub-Sub (token: <pre>pubsub</pre>). */
    +023  PUBSUB("pubsub", null),
    +024
    +025  /** Google Cloud Storage (token: <pre>storage</pre>). */
    +026  STORAGE("storage", null),
    +027
    +028  /** Google Cloud Firestore (token: <pre>firestore</pre>) */
    +029  FIRESTORE("firestore", null),
    +030
    +031  /** Google Cloud Firestore (token: <pre>spanner</pre>) */
    +032  SPANNER("spanner", null);
    +033
    +034  /** Prefix at which the specified service may be configured. */
    +035  private final @Nonnull String token;
    +036
    +037  /** Config bindings class for the provided service. */
    +038  private final @Nullable Class<GoogleTransportConfig> configType;
    +039
    +040  /**
    +041   * Private constructor.
    +042   *
    +043   * @param token Configuration binding prefix.
    +044   * @param configType Configuration class type.
    +045   */
    +046  GoogleService(@Nonnull String token,
    +047                @Nullable Class<GoogleTransportConfig> configType) {
    +048    this.token = token;
    +049    this.configType = configType;
    +050  }
    +051
    +052  // -- Getters -- //
    +053  /**
    +054   * @return Prefix at which the specified service can be configured.
    +055   */
    +056  public @Nonnull String getToken() {
    +057    return token;
    +058  }
    +059
    +060  /**
    +061   * @return Configuration bindings class for the provided service, if supported.
    +062   */
    +063  public @Nonnull Optional<Class<GoogleTransportConfig>> getConfigType() {
    +064    if (configType == null)
    +065      return Optional.empty();
    +066    return Optional.of(configType);
    +067  }
    +068}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/transport/GoogleTransportConfig.html b/docs/java/src-html/gust/backend/transport/GoogleTransportConfig.html new file mode 100644 index 000000000..f305dd481 --- /dev/null +++ b/docs/java/src-html/gust/backend/transport/GoogleTransportConfig.html @@ -0,0 +1,131 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.transport;
    +014
    +015import com.google.api.gax.core.CredentialsProvider;
    +016import com.google.api.gax.core.GoogleCredentialsProvider;
    +017
    +018import javax.annotation.Nonnull;
    +019import java.util.Collections;
    +020import java.util.List;
    +021import java.util.Optional;
    +022
    +023
    +024/**
    +025 * Provides sensible defaults and additional configuration when applying transport settings specifically to Google-
    +026 * provided or hosted services. These generally do not need to be changed.
    +027 *
    +028 * <p>Namely, this enforces authentication by default, and resolves Application Default Credentials. This behavior can
    +029 * be overridden by specifying configuration properties in your <code>application.yml</code>. See
    +030 * {@link GoogleTransportManager} for more information.</p>
    +031 *
    +032 * @see GoogleTransportManager for information about transport credential settings.
    +033 */
    +034public interface GoogleTransportConfig extends GrpcTransportConfig {
    +035  /**
    +036   * @return Whether a transport requires credentials. This defaults to <code>true</code> for
    +037   *         {@link GoogleTransportConfig} and descendents.
    +038   */
    +039  @Override
    +040  default @Nonnull Boolean requiresCredentials() {
    +041    return true;
    +042  }
    +043
    +044  /**
    +045   * Resolve a credentials provider bound to the specified auth requirements.
    +046   *
    +047   * @param scopes Authorization scopes to request.
    +048   * @return Credentials provider that should be active for managed RPC channel calls. By default, for configurations
    +049   *         that inherit from {@link GoogleTransportConfig}, this will read and use Application Default Credentials.
    +050   */
    +051  @Override
    +052  default @Nonnull Optional<CredentialsProvider> credentialsProvider(@Nonnull Optional<List<String>> scopes) {
    +053    return Optional.of(GoogleCredentialsProvider.newBuilder()
    +054      .setScopesToApply(scopes.isPresent() ? scopes.get() : Collections.emptyList())
    +055      .build());
    +056  }
    +057}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/transport/GoogleTransportManager.html b/docs/java/src-html/gust/backend/transport/GoogleTransportManager.html new file mode 100644 index 000000000..1c2c0a226 --- /dev/null +++ b/docs/java/src-html/gust/backend/transport/GoogleTransportManager.html @@ -0,0 +1,529 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.transport;
    +014
    +015import com.google.api.gax.core.CredentialsProvider;
    +016import com.google.api.gax.core.ExecutorProvider;
    +017import com.google.api.gax.grpc.*;
    +018import gust.Core;
    +019import gust.backend.runtime.Logging;
    +020import io.grpc.Channel;
    +021import io.grpc.ClientInterceptor;
    +022import io.grpc.ManagedChannel;
    +023import io.micronaut.aop.MethodInvocationContext;
    +024import io.micronaut.context.annotation.ConfigurationProperties;
    +025import io.micronaut.context.annotation.Context;
    +026import io.micronaut.core.annotation.AnnotationValue;
    +027import io.micronaut.runtime.context.scope.Refreshable;
    +028import org.slf4j.Logger;
    +029import org.threeten.bp.Duration;
    +030
    +031import javax.annotation.Nonnull;
    +032import javax.annotation.Nullable;
    +033import javax.annotation.concurrent.Immutable;
    +034import javax.annotation.concurrent.ThreadSafe;
    +035import javax.inject.Inject;
    +036import javax.inject.Singleton;
    +037import java.io.IOException;
    +038import java.util.*;
    +039import java.util.concurrent.ConcurrentMap;
    +040import java.util.concurrent.ConcurrentSkipListMap;
    +041import java.util.concurrent.ScheduledExecutorService;
    +042
    +043
    +044/**
    +045 * Supplies a {@link TransportManager} implementation for dealing with Google Cloud APIs via gRPC and Protobuf. In this
    +046 * object, we make heavy use of the Google API Extensions for Java ("GAX"), in order to centrally manage channels, on-
    +047 * demand, for downstream service use.
    +048 *
    +049 * <p>Connection instances may be requested from this object like any other transport manager, but if there is an
    +050 * existing managed channel for the provided service ID, it will provide the existing instance (or one from a pool of
    +051 * existing instances) rather than creating a new one.</p>
    +052 *
    +053 * <p>To request a connection instance from this transport manager, annotate an injectable method parameter (or
    +054 * constructor parameter) of type {@link Channel} with {@link GoogleAPIChannel}. For example:
    +055 * <code>
    +056 *   public void doSomething(@GoogleAPIChannel(Service.PUBSUB) Channel pubsubChannel) {
    +057 *     // ...
    +058 *   }
    +059 * </code></p>
    +060 *
    +061 * <p><b>Configuration:</b> Each Google service has a specified <i>name</i>, included on the docs in
    +062 * {@link GoogleService}, at which that service may be configured in <pre>application.yml</pre>. For example, the
    +063 * following code configures the Cloud Pubsub client's keepalive and pool size settings:
    +064 * <code>
    +065 *   transport:
    +066 *     google:
    +067 *       pubsub:
    +068 *         poolSize: 3
    +069 *         keepAliveTime: 15s
    +070 *         keepAliveTimeout: 30s
    +071 *         keepAliveWithoutCalls: true
    +072 * </code></p>
    +073 */
    +074@Context
    +075@Singleton
    +076@Immutable
    +077@ThreadSafe
    +078@Refreshable
    +079@SuppressWarnings("unused")
    +080@ConfigurationProperties(GoogleTransportManager.CONFIG_PREFIX)  // `transport.google`
    +081public final class GoogleTransportManager implements TransportManager<GoogleAPIChannel, GoogleService, Channel> {
    +082  /** Prefix under which Google services may be configured. */
    +083  public final static String CONFIG_PREFIX = ROOT_CONFIG_PREFIX + ".google";
    +084
    +085  /** Logging pipe. */
    +086  private final static Logger logging = Logging.logger(GoogleTransportManager.class);
    +087
    +088  /** Maximum number of concurrent connections per service. */
    +089  private final static int DEFAULT_MAX_POOL_SIZE = 3;
    +090
    +091  /** Tag to append to the user-agent on outgoing calls. */
    +092  private final static String GUST_TAG = "gust/" + Core.getGustVersion();
    +093
    +094  /** Map of pooled connections, grouped per-service. */
    +095  private final ConcurrentMap<GoogleService, ManagedChannelPool> poolMap;
    +096
    +097  /** Maximum size of any one service connection pool. */
    +098  private volatile int maxPoolSize = DEFAULT_MAX_POOL_SIZE;
    +099
    +100  /** Executor service to use for RPC traffic. */
    +101  private volatile @Nonnull ScheduledExecutorService executorService;
    +102
    +103  /** Thrown when required transport configuration is missing. */
    +104  private static final class TransportConfigMissing extends TransportException {
    +105    /**
    +106     * Instantiate a new `TransportConfigMissing` exception.
    +107     *
    +108     * @param svc Service we are missing config for.
    +109     */
    +110    private TransportConfigMissing(@Nonnull GoogleService svc) {
    +111      super(String.format(
    +112        "Google transport configuration could not be resolved for service %s.", svc.getToken()));
    +113    }
    +114  }
    +115
    +116  /** Thrown when credentials are required, but missing. */
    +117  private static final class TransportCredentialsMissing extends TransportException {
    +118    /**
    +119     * Instantiate a new `TransportConfigMissing` exception.
    +120     *
    +121     * @param svc Service we are missing config for.
    +122     */
    +123    private TransportCredentialsMissing(@Nonnull GoogleService svc, @Nullable IOException ioe) {
    +124      super(String.format(
    +125        "Google transport credentials could not be resolved for service %s.", svc.getToken()), ioe);
    +126    }
    +127  }
    +128
    +129  /** Exception thrown when a channel could not be established. */
    +130  private static final class ChannelEstablishFailed extends TransportException {
    +131    /**
    +132     * Instantiate a new `ChannelEstablishException` exception.
    +133     *
    +134     * @param svc Service that failed to establish a connection.
    +135     * @param ioe Wrapped IO exception.
    +136     */
    +137    private ChannelEstablishFailed(@Nonnull GoogleService svc, @Nonnull IOException ioe) {
    +138      super(String.format(
    +139        "Failed to establish a connection to Google service '%s'.", svc.getToken()), ioe);
    +140    }
    +141  }
    +142
    +143  /** Client header interceptor for injecting the framework `User-Agent` header. */
    +144  @Immutable
    +145  private static final class UAInterceptor extends GrpcHeaderInterceptor {
    +146    /** Singleton interceptor instance. */
    +147    private static final UAInterceptor INSTANCE = new UAInterceptor();
    +148
    +149    private UAInterceptor() {
    +150      super(Collections.singletonMap("user-agent", GUST_TAG));
    +151    }
    +152  }
    +153
    +154  /** Wrapper object that manages a set of pooled connections. */
    +155  @Immutable
    +156  private static final class ManagedChannelPool {
    +157    /** Known service we will be managing connections for. */
    +158    private final GoogleService service;
    +159
    +160    /** Holds the set of channels managed by this pool. */
    +161    private final InstantiatingGrpcChannelProvider provider;
    +162
    +163    /**
    +164     * Initialize a new managed connection pool for the provided service configuration.
    +165     *
    +166     * @param maxPoolSize Maximum per-service pool size.
    +167     * @param executorService Executor service to use for spawning work.
    +168     * @param svc Service which we will be managing connections for.
    +169     * @param config Configuration by which to initialize gRPC channels.
    +170     */
    +171    private ManagedChannelPool(int maxPoolSize,
    +172                               @Nonnull ScheduledExecutorService executorService,
    +173                               @Nonnull GoogleService svc,
    +174                               @Nonnull GoogleTransportConfig config) {
    +175      this.service = svc;
    +176      this.provider = buildProvider(maxPoolSize, svc, executorService, config);
    +177    }
    +178
    +179    /**
    +180     * Acquire a managed channel from this pool, potentially blocking until one is ready or otherwise free. If all
    +181     * channels are busy, and the maximum number of channels has not yet been reached, one may be established fresh for
    +182     * the invoking caller, which may also incur delays.
    +183     *
    +184     * @return Managed channel acquired for the service configured with this pool.
    +185     * @throws ChannelEstablishFailed If an I/O error occurs establishing or resolving the requested channel.
    +186     */
    +187    @Nonnull GrpcTransportChannel acquire() throws ChannelEstablishFailed {
    +188      try {
    +189        if (logging.isDebugEnabled())
    +190          logging.debug(String.format("Acquiring connection for service '%s'...", this.service.getToken()));
    +191
    +192        // acquire the channel
    +193        GrpcTransportChannel channel = (GrpcTransportChannel)this.provider.getTransportChannel();
    +194        if (channel == null || channel.isShutdown())
    +195          throw new IllegalStateException("Failed to acquire gRPC channel (or was initially shut down).");
    +196        else if (logging.isDebugEnabled())
    +197          logging.debug(String.format("gRPC channel acquired ('%s').", channel.getChannel().authority()));
    +198        return channel;
    +199
    +200      } catch (IOException ioe) {
    +201        logging.error(String.format("Failed to establish managed channel (error: '%s').", ioe.getMessage()));
    +202        throw new ChannelEstablishFailed(this.service, ioe);
    +203      }
    +204    }
    +205  }
    +206
    +207  /**
    +208   * Build the provided {@link GoogleTransportConfig} into a gRPC channel provider, which applies the configuration when
    +209   * instantiating channels according to needs invoked through the active {@link TransportManager}.
    +210   *
    +211   * @param maxPoolSize Maximum size of the channel pool.
    +212   * @param service Service for which we are building a channel provider.
    +213   * @param executorService Executor service to make use of when executing RPCs.
    +214   * @param config gRPC configuration to apply when instantiating new channels for this service.
    +215   * @return Pre-fabricated provider instance to use when instantiating new channels.
    +216   */
    +217  private static InstantiatingGrpcChannelProvider buildProvider(int maxPoolSize,
    +218                                                                @Nonnull GoogleService service,
    +219                                                                @Nonnull ScheduledExecutorService executorService,
    +220                                                                @Nonnull GoogleTransportConfig config) {
    +221    if (logging.isTraceEnabled())
    +222      logging.trace("Setting up new gRPC channel provider...");
    +223    // begin setting up provider
    +224    InstantiatingGrpcChannelProvider.Builder builder = InstantiatingGrpcChannelProvider.newBuilder()
    +225      // target
    +226      .setEndpoint(Objects.requireNonNull(config.endpoint(),
    +227        "Managed channel endpoint cannot be null."))
    +228
    +229      // message sizes
    +230      .setMaxInboundMessageSize(Objects.requireNonNull(config.maxInboundMessageSize(),
    +231        "Maximum inbound message size cannot be null."))
    +232      .setMaxInboundMetadataSize(Objects.requireNonNull(config.maxInboundMetadataSize(),
    +233        "Maximum inbound metadata size cannot be null."))
    +234
    +235      // pooling & execution
    +236      .setPoolSize(Math.min(
    +237        Objects.requireNonNull(config.getPoolSize(),
    +238          "Cannot set `null` for max pool size."),
    +239        maxPoolSize))
    +240
    +241      .setExecutorProvider(new ExecutorProvider() {
    +242        @Override
    +243        public boolean shouldAutoClose() {
    +244          return false; /* this executor is shared, so don't auto-close per-service. */
    +245        }
    +246
    +247        @Override
    +248        public ScheduledExecutorService getExecutor() {
    +249          return executorService;
    +250        }
    +251      });
    +252
    +253    // interceptors
    +254    if (logging.isTraceEnabled()) logging.trace("Applying extra interceptors...");
    +255    Optional<List<ClientInterceptor>> extraInterceptors = config.getExtraInterceptors();
    +256    if (extraInterceptors.isPresent()) {
    +257      List<ClientInterceptor> extraInterceptorsList = extraInterceptors.get();
    +258      final ArrayList<ClientInterceptor> composedInterceptors = new ArrayList<>(
    +259        extraInterceptorsList.size() + 1);
    +260      composedInterceptors.add(UAInterceptor.INSTANCE);
    +261      composedInterceptors.addAll(extraInterceptorsList);
    +262      builder.setInterceptorProvider(() -> Collections.unmodifiableList(composedInterceptors));
    +263      if (logging.isDebugEnabled())
    +264        logging.debug(String.format(
    +265          "Custom interceptors added (%s), along with `UAInterceptor`.", extraInterceptorsList.size()));
    +266    } else {
    +267      builder.setInterceptorProvider(() -> Collections.singletonList(UAInterceptor.INSTANCE));
    +268      if (logging.isDebugEnabled())
    +269        logging.debug("No custom interceptors detected. Added `UAInterceptor`.");
    +270    }
    +271
    +272    // keepalive
    +273    if (logging.isTraceEnabled()) logging.trace("Applying keepalive settings...");
    +274    if (Objects.requireNonNull(config.getKeepaliveEnabled())) {
    +275      builder
    +276        .setKeepAliveTime(Duration.ofSeconds(config.getKeepaliveTime().getSeconds()))
    +277        .setKeepAliveTimeout(Duration.ofSeconds(config.getKeepaliveTimeout().getSeconds()))
    +278        .setKeepAliveWithoutCalls(config.getKeepAliveNoActivity());
    +279      if (logging.isDebugEnabled())
    +280        logging.debug(String.format(
    +281          "Managed channel keepalive is ENABLED with (%s) time, (%s) timeout.",
    +282          config.getKeepaliveTime().toString(),
    +283          config.getKeepaliveTimeout().toString()));
    +284    } else {
    +285      if (logging.isDebugEnabled())
    +286        logging.debug("Managed channel keepalive is disabled.");
    +287    }
    +288
    +289    // credentials
    +290    if (logging.isTraceEnabled()) logging.trace("Applying credential settings...");
    +291    Optional<CredentialsProvider> maybeProvider = config.credentialsProvider();
    +292    if (Objects.requireNonNull(config.requiresCredentials()) && !maybeProvider.isPresent()) {
    +293      logging.error(String.format(
    +294        "Failed to initialize gRPC service '%s': credentials were required, but could not be obtained.",
    +295        service.getToken()));
    +296      throw new TransportCredentialsMissing(service, null);
    +297    }
    +298    if (maybeProvider.isPresent()) {
    +299      if (logging.isTraceEnabled()) logging.trace("Found credential provider. Resolving...");
    +300      try {
    +301        builder.setCredentials(maybeProvider.get().getCredentials());
    +302        if (logging.isDebugEnabled()) logging.debug("Credentials were resolved and attached.");
    +303      } catch (IOException ioe) {
    +304        logging.error(String.format(
    +305          "Failed to initialize gRPC service '%s': credentials were specified, but failed to load.",
    +306          service.getToken()));
    +307        throw new TransportCredentialsMissing(service, ioe);
    +308      }
    +309    }
    +310
    +311    // connection priming
    +312    if (Objects.requireNonNull(config.enablePrimer())) {
    +313      if (logging.isDebugEnabled()) logging.debug("Connection priming ENABLED.");
    +314      builder.setChannelPrimer(managedChannel -> primeManagedChannel(service, config, managedChannel));
    +315    } else if (logging.isDebugEnabled()) {
    +316      logging.debug("Connection priming DISABLED.");
    +317    }
    +318    return builder.build();
    +319  }
    +320
    +321  /**
    +322   * Prime a managed gRPC channel, once it has been established by the underlying GAX tooling.
    +323   *
    +324   * @param service Service which we are instantiating a channel for.
    +325   * @param config Configuration for our gRPC service channel.
    +326   * @param channel Instantiated/established connection and higher-order RPC channel.
    +327   */
    +328  private static void primeManagedChannel(@Nonnull GoogleService service,
    +329                                          @Nonnull GoogleTransportConfig config,
    +330                                          @Nonnull ManagedChannel channel) {
    +331    throw new IllegalStateException("channel priming is not yet supported");
    +332  }
    +333
    +334  /**
    +335   * Initialize a new Google Transport Manager.
    +336   *
    +337   * @param executorService Scheduled executor against which to execute RPC traffic.
    +338   */
    +339  @Inject
    +340  GoogleTransportManager(@Nonnull ScheduledExecutorService executorService) {
    +341    if (logging.isTraceEnabled())
    +342      logging.trace(String.format("Initializing `GoogleTransportManager` (version tag: '%s').", GUST_TAG));
    +343    if (logging.isDebugEnabled())
    +344      logging.debug(String.format("`GoogleTransportManager` executor: '%s'.", executorService.getClass().getName()));
    +345    this.poolMap = new ConcurrentSkipListMap<>();  // initialize pool map
    +346    this.executorService = executorService;
    +347  }
    +348
    +349  /**
    +350   * Generate or otherwise resolve a transport-layer configuration for the provided <pre>service</pre>. This includes
    +351   * stuff like the actual endpoint to connect to, keepalive configuration, retries, pooling, and so on.
    +352   *
    +353   * @param service Service for which we should generate or acquire a transport config.
    +354   * @return Transport configuration for the specified service.
    +355   * @throws TransportConfigMissing If configuration cannot be resolved.
    +356   */
    +357  private static @Nonnull GoogleTransportConfig configForService(@Nonnull GoogleService service)
    +358      throws TransportConfigMissing {
    +359    if (logging.isTraceEnabled())
    +360      logging.trace(String.format("Resolving configuration type for Google service '%s'...", service.getToken()));
    +361    Optional<Class<GoogleTransportConfig>> cfgType = service.getConfigType();
    +362    if (cfgType.isPresent()) {
    +363      Class<GoogleTransportConfig> cfgTypeClass = cfgType.get();
    +364      if (logging.isDebugEnabled())
    +365        logging.debug(String.format("Configuration type resolved as '%s'.", cfgType.get().getName()));
    +366      // create config instance
    +367      try {
    +368        return cfgTypeClass.newInstance();
    +369      } catch (InstantiationException | IllegalAccessException err) {
    +370        logging.error(
    +371          String.format("Failed to resolve configuration for Google service '%s'.", service.getToken()));
    +372        throw new TransportConfigMissing(service);
    +373      }
    +374    }
    +375    throw new IllegalStateException(
    +376      "Failed to resolve configuration type for service '%s'. It may not be implemented.");
    +377  }
    +378
    +379  /**
    +380   * Resolve a managed channel for the provided Google API service, according to the provided transport configuration,
    +381   * which contains transport-layer settings to apply when establishing new connections for this service.
    +382   *
    +383   * @param service Service to establish or otherwise resolve a connection for.
    +384   * @param config Configuration to apply when establishing connections for this service.
    +385   * @return Managed channel, established for the provided service.
    +386   * @throws ChannelEstablishFailed If a channel cannot be established.
    +387   */
    +388  private @Nonnull GrpcTransportChannel resolveChannel(@Nonnull GoogleService service,
    +389                                                       @Nonnull GoogleTransportConfig config) throws TransportException {
    +390    if (logging.isTraceEnabled())
    +391      logging.trace(String.format("Resolving managed gRPC channel for Google service '%s'.", service.getToken()));
    +392
    +393    final @Nonnull ManagedChannelPool pool;
    +394    if (!poolMap.containsKey(service)) {
    +395      if (logging.isDebugEnabled()) logging.debug(String.format(
    +396          "Connection pool not found for service '%s'. Establishing...", service.getToken()));
    +397
    +398      // establish initial pool of connections
    +399      pool = new ManagedChannelPool(maxPoolSize, executorService, service, config);
    +400      poolMap.put(service, pool);
    +401    } else {
    +402      if (logging.isTraceEnabled()) logging.trace(String.format(
    +403        "Connection pool found for service '%s'. Using existing.", service.getToken()));
    +404      pool = Objects.requireNonNull(poolMap.get(service));
    +405    }
    +406    return pool.acquire();
    +407  }
    +408
    +409  // -- Getters & Setters -- //
    +410
    +411  /** @return Maximum size of any one service connection pool. */
    +412  public int getMaxPoolSize() {
    +413    return maxPoolSize;
    +414  }
    +415
    +416  // -- Public API -- //
    +417  /**
    +418   * Acquire a connection from this transport manager. The connection provided may or may not be freshly-created,
    +419   * depending on the underlying implementation, but it should never be <pre>null</pre> (exceptions are raised instead).
    +420   *
    +421   * @param type Type of connection to acquire. Defined by the implementation.
    +422   * @return Connection instance for the desired service, potentially fresh, potentially re-used.
    +423   * @throws TransportException If the connection could not be acquired.
    +424   */
    +425  @Override
    +426  public @Nonnull Channel acquire(@Nonnull GoogleService type) throws TransportException {
    +427    //noinspection ConstantConditions
    +428    if (type == null) throw new IllegalArgumentException("Cannot resolve connection for `null` service.");
    +429    return resolveChannel(type, configForService(type)).getChannel();
    +430  }
    +431
    +432  /**
    +433   * Provide acquired connections via injection-annotated {@link Channel} method or constructor parameters. This
    +434   * essentially proxies to {@link #acquire(GoogleService)}, passing in the service specified in the context of the
    +435   * {@link GoogleAPIChannel} annotation.
    +436   *
    +437   * @param context Intercepted method execution context.
    +438   * @return Resulting object.
    +439   * @throws TransportException If the connection could not be acquired.
    +440   */
    +441  @Override
    +442  public Channel intercept(MethodInvocationContext<Object, Channel> context) {
    +443    if (!context.hasAnnotation(GoogleAPIChannel.class)) {
    +444      throw new IllegalArgumentException(
    +445        "Must annotate method with @GoogleAPIChannel to inject from GoogleTransportManager.");
    +446    }
    +447    // fetch annotation and resolve desired service
    +448    @Nonnull AnnotationValue<GoogleAPIChannel> anno = Objects.requireNonNull(
    +449      context.getAnnotation(GoogleAPIChannel.class));
    +450    Optional<GoogleService> desiredService = anno.enumValue("service", GoogleService.class);
    +451    if (!desiredService.isPresent())
    +452      throw new IllegalArgumentException("Must provide desired service to GoogleTransportManager.");
    +453    return acquire(desiredService.get());
    +454  }
    +455}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/transport/GrpcTransportConfig.html b/docs/java/src-html/gust/backend/transport/GrpcTransportConfig.html new file mode 100644 index 000000000..5ae075688 --- /dev/null +++ b/docs/java/src-html/gust/backend/transport/GrpcTransportConfig.html @@ -0,0 +1,146 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.transport;
    +014
    +015import io.grpc.ClientInterceptor;
    +016
    +017import javax.annotation.Nonnull;
    +018import java.util.List;
    +019import java.util.Optional;
    +020
    +021
    +022/**
    +023 * Specifies transport configuration properties specific to gRPC {@link io.grpc.ManagedChannel} objects.
    +024 */
    +025public interface GrpcTransportConfig extends PooledTransportConfig, GrpcTransportCredentials {
    +026  /** Default maximum inbound message size, if no other value is specified. */
    +027  Integer DEFAULT_MAX_INBOUND_MESSAGE_SIZE = 1;
    +028
    +029  /** Default maximum inbound metadata size, if no other value is specified. */
    +030  Integer DEFAULT_MAX_INBOUND_METADATA_SIZE = 1;
    +031
    +032  /** Whether to prime managed gRPC connections by default. */
    +033  Boolean DEFAULT_PRIME_CONNECTIONS = true;
    +034
    +035  /**
    +036   * @return gRPC endpoint at which to connect to the target service.
    +037   */
    +038  @Nonnull String endpoint();
    +039
    +040  /**
    +041   * @return Whether to keep-alive even when there is no activity.
    +042   */
    +043  @Nonnull Boolean getKeepAliveNoActivity();
    +044
    +045  /**
    +046   * @return Max inbound message size, in bytes.
    +047   */
    +048  default @Nonnull Integer maxInboundMessageSize() {
    +049    return DEFAULT_MAX_INBOUND_MESSAGE_SIZE;
    +050  }
    +051
    +052  /**
    +053   * @return Max inbound metadata stanza size, in bytes.
    +054   */
    +055  default @Nonnull Integer maxInboundMetadataSize() {
    +056    return DEFAULT_MAX_INBOUND_METADATA_SIZE;
    +057  }
    +058
    +059  /**
    +060   * @return Additional client-side call interceptors to install.
    +061   */
    +062  default @Nonnull Optional<List<ClientInterceptor>> getExtraInterceptors() {
    +063    return Optional.empty();
    +064  }
    +065
    +066  /**
    +067   * @return Whether to prime managed gRPC connections.
    +068   */
    +069  default @Nonnull Boolean enablePrimer() {
    +070    return DEFAULT_PRIME_CONNECTIONS;
    +071  }
    +072}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/transport/GrpcTransportCredentials.html b/docs/java/src-html/gust/backend/transport/GrpcTransportCredentials.html new file mode 100644 index 000000000..9eb0c1433 --- /dev/null +++ b/docs/java/src-html/gust/backend/transport/GrpcTransportCredentials.html @@ -0,0 +1,114 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.transport;
    +014
    +015import com.google.api.gax.core.CredentialsProvider;
    +016
    +017import javax.annotation.Nonnull;
    +018import java.util.List;
    +019import java.util.Optional;
    +020
    +021
    +022/**
    +023 * Transport-layer credentials configuration for use with managed gRPC connections. Specifies a credential provider for
    +024 * use with auto-initialized and pooled RPC channels.
    +025 */
    +026public interface GrpcTransportCredentials extends TransportCredentials {
    +027  /**
    +028   * @return Credentials provider that should be active for managed RPC channel calls, with an empty set of scopes.
    +029   */
    +030  default @Nonnull Optional<CredentialsProvider> credentialsProvider() {
    +031    return credentialsProvider(Optional.empty());
    +032  }
    +033
    +034  /**
    +035   * @return Credentials provider that should be active for managed RPC channel calls.
    +036   */
    +037  default @Nonnull Optional<CredentialsProvider> credentialsProvider(@Nonnull Optional<List<String>> scopes) {
    +038    return Optional.empty();
    +039  }
    +040}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/transport/PooledTransportConfig.html b/docs/java/src-html/gust/backend/transport/PooledTransportConfig.html new file mode 100644 index 000000000..273419745 --- /dev/null +++ b/docs/java/src-html/gust/backend/transport/PooledTransportConfig.html @@ -0,0 +1,98 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.transport;
    +014
    +015import javax.annotation.Nonnull;
    +016
    +017
    +018/** Specifies configuration properties related to pooling of managed transport connections. */
    +019public interface PooledTransportConfig extends TransportConfig {
    +020  /**
    +021   * @return Retrieve the desired connection pool size.
    +022   */
    +023  @Nonnull Integer getPoolSize();
    +024}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/transport/TransportConfig.html b/docs/java/src-html/gust/backend/transport/TransportConfig.html new file mode 100644 index 000000000..e00c479c8 --- /dev/null +++ b/docs/java/src-html/gust/backend/transport/TransportConfig.html @@ -0,0 +1,106 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.transport;
    +014
    +015import javax.annotation.Nonnull;
    +016import java.time.Duration;
    +017
    +018
    +019/**
    +020 * Specifies base configuration properties generally supported by all transport configurations. This includes properties
    +021 * like keepalive timeouts and timings, pooling settings, and so on.
    +022 */
    +023public interface TransportConfig {
    +024  /** @return Whether to enable keepalive features. */
    +025  @Nonnull Boolean getKeepaliveEnabled();
    +026
    +027  /** @return Keep-alive time. */
    +028  @Nonnull Duration getKeepaliveTime();
    +029
    +030  /** @return Keep-alive timeout. */
    +031  @Nonnull Duration getKeepaliveTimeout();
    +032}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/transport/TransportCredentials.html b/docs/java/src-html/gust/backend/transport/TransportCredentials.html new file mode 100644 index 000000000..b8ed06b10 --- /dev/null +++ b/docs/java/src-html/gust/backend/transport/TransportCredentials.html @@ -0,0 +1,103 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.transport;
    +014
    +015import javax.annotation.Nonnull;
    +016
    +017
    +018/**
    +019 * Specifies the generic notion of "transport credentials," as configuration or logic. Credentials of this nature are
    +020 * generally used during external connection establishment via {@link TransportManager} implementations.
    +021 */
    +022public interface TransportCredentials {
    +023  /**
    +024   * @return Whether a transport requires credentials. This defaults to <code>false</code>.
    +025   */
    +026  default @Nonnull Boolean requiresCredentials() {
    +027    return false;
    +028  }
    +029}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/transport/TransportException.html b/docs/java/src-html/gust/backend/transport/TransportException.html new file mode 100644 index 000000000..2488a5307 --- /dev/null +++ b/docs/java/src-html/gust/backend/transport/TransportException.html @@ -0,0 +1,116 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.transport;
    +014
    +015import javax.annotation.Nonnull;
    +016import javax.annotation.Nullable;
    +017
    +018
    +019/**
    +020 * Defines an error case that was encountered while dealing with managed transport logic. This could include connection
    +021 * acquisition, name resolution failures, and so on.
    +022 */
    +023public abstract class TransportException extends RuntimeException {
    +024  /**
    +025   * Package-private constructor for a regular exception.
    +026   *
    +027   * @param message Error message.
    +028   */
    +029  TransportException(@Nonnull String message) {
    +030    super(message);
    +031  }
    +032
    +033  /**
    +034   * Package-private constructor for a wrapped exception.
    +035   *
    +036   * @param message Error message.
    +037   * @param thr Wrapped throwable.
    +038   */
    +039  TransportException(@Nonnull String message, @Nullable Throwable thr) {
    +040    super(message, thr);
    +041  }
    +042}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/backend/transport/TransportManager.html b/docs/java/src-html/gust/backend/transport/TransportManager.html new file mode 100644 index 000000000..25def7437 --- /dev/null +++ b/docs/java/src-html/gust/backend/transport/TransportManager.html @@ -0,0 +1,119 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.backend.transport;
    +014
    +015import io.micronaut.aop.MethodInterceptor;
    +016
    +017import javax.annotation.Nonnull;
    +018import java.lang.annotation.Annotation;
    +019
    +020
    +021/**
    +022 * Defines the interface by which "transport manager" objects must comply. These objects are used to construct and
    +023 * manage connections to other machines or systems, usually via higher-order service layers like gRPC.
    +024 *
    +025 * @param <A> Annotation qualifier type, which is responsible for marking types that need injection from a given
    +026 *           implementing manager.
    +027 * @param <E> Enumerated connection type, or service type. An instance of this enumerated type is required when
    +028 *           acquiring a connection.
    +029 * @param <C> Connection implementation. Should match the object that a given manager hands back when a connection is
    +030 *           acquired for use.
    +031 */
    +032public interface TransportManager<A extends Annotation, E extends Enum<E>, C> extends MethodInterceptor<Object, C> {
    +033  /** Config prefix under which transport settings are specified. */
    +034  String ROOT_CONFIG_PREFIX = "transport";
    +035
    +036  /**
    +037   * Acquire a connection from this transport manager. The connection provided may or may not be freshly-created,
    +038   * depending on the underlying implementation, but it should never be <pre>null</pre> (exceptions are raised instead).
    +039   *
    +040   * @param type Type of connection to acquire. Defined by the implementation.
    +041   * @return Connection instance.
    +042   * @throws TransportException If the connection could not be acquired.
    +043   */
    +044  @Nonnull C acquire(@Nonnull E type) throws TransportException;
    +045}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/Hex.html b/docs/java/src-html/gust/util/Hex.html new file mode 100644 index 000000000..18786d1b6 --- /dev/null +++ b/docs/java/src-html/gust/util/Hex.html @@ -0,0 +1,125 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
    +003 *
    +004 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
    +005 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
    +006 * this code in object or source form requires and implies consent and agreement to that license in principle and
    +007 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
    +008 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
    +009 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
    +010 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
    +011 * is strictly forbidden except in adherence with assigned license requirements.
    +012 */
    +013package gust.util;
    +014
    +015import javax.annotation.Nonnull;
    +016
    +017
    +018/** Provides utilities for encoding values into hex, or decoding values from hex. */
    +019public final class Hex {
    +020  /** Array of hex-allowable characters. */
    +021  private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
    +022
    +023  /**
    +024   * Convert a byte array to hex.
    +025   *
    +026   * @param bytes Raw bytes to encode.
    +027   * @return Resulting hex-encoded string.
    +028   */
    +029  public static @Nonnull String bytesToHex(byte[] bytes) {
    +030    return bytesToHex(bytes, -1);
    +031  }
    +032
    +033  /**
    +034   * Convert a byte array to hex, optionally limiting the number of characters returned and cycles performed.
    +035   *
    +036   * @param bytes Raw bytes to encode.
    +037   * @param maxChars Max number of output characters to care about. Pass `-1` to encode the whole thing.
    +038   * @return Resulting hex-encoded string.
    +039   */
    +040  public static @Nonnull String bytesToHex(byte[] bytes, int maxChars) {
    +041    char[] hexChars = new char[bytes.length * 2];
    +042    for (int j = 0; j < (maxChars == -1 ? bytes.length : Math.min(bytes.length, maxChars / 2)); j++) {
    +043      int v = bytes[j] & 0xFF;
    +044      hexChars[j * 2] = HEX_ARRAY[v >>> 4];
    +045      hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
    +046    }
    +047    return new String(hexChars).trim().replace("\000", "");
    +048  }
    +049
    +050  private Hex() { /* Disallow instantiation. */ }
    +051}
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/InstantFactory.html b/docs/java/src-html/gust/util/InstantFactory.html new file mode 100644 index 000000000..bab4fe485 --- /dev/null +++ b/docs/java/src-html/gust/util/InstantFactory.html @@ -0,0 +1,97 @@ + + + +Source code + + + +
    + +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.Builder.html b/docs/java/src-html/gust/util/MessageDifferencer.Builder.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.Builder.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.DefaultFieldComparator.html b/docs/java/src-html/gust/util/MessageDifferencer.DefaultFieldComparator.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.DefaultFieldComparator.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.FieldComparator.ComparisonResult.html b/docs/java/src-html/gust/util/MessageDifferencer.FieldComparator.ComparisonResult.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.FieldComparator.ComparisonResult.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.FieldComparator.html b/docs/java/src-html/gust/util/MessageDifferencer.FieldComparator.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.FieldComparator.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.FloatComparison.html b/docs/java/src-html/gust/util/MessageDifferencer.FloatComparison.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.FloatComparison.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.IgnoreCriteria.html b/docs/java/src-html/gust/util/MessageDifferencer.IgnoreCriteria.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.IgnoreCriteria.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.MapKeyComparator.html b/docs/java/src-html/gust/util/MessageDifferencer.MapKeyComparator.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.MapKeyComparator.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.MessageFieldComparison.html b/docs/java/src-html/gust/util/MessageDifferencer.MessageFieldComparison.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.MessageFieldComparison.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.RepeatedFieldComparison.html b/docs/java/src-html/gust/util/MessageDifferencer.RepeatedFieldComparison.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.RepeatedFieldComparison.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.ReportType.html b/docs/java/src-html/gust/util/MessageDifferencer.ReportType.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.ReportType.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.Reporter.html b/docs/java/src-html/gust/util/MessageDifferencer.Reporter.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.Reporter.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.Scope.html b/docs/java/src-html/gust/util/MessageDifferencer.Scope.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.Scope.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.SpecificField.html b/docs/java/src-html/gust/util/MessageDifferencer.SpecificField.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.SpecificField.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.StreamReporter.StreamException.html b/docs/java/src-html/gust/util/MessageDifferencer.StreamReporter.StreamException.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.StreamReporter.StreamException.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.StreamReporter.html b/docs/java/src-html/gust/util/MessageDifferencer.StreamReporter.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.StreamReporter.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.UnknownDescriptor.html b/docs/java/src-html/gust/util/MessageDifferencer.UnknownDescriptor.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.UnknownDescriptor.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.UnknownFieldType.html b/docs/java/src-html/gust/util/MessageDifferencer.UnknownFieldType.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.UnknownFieldType.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/MessageDifferencer.html b/docs/java/src-html/gust/util/MessageDifferencer.html new file mode 100644 index 000000000..7b996adf0 --- /dev/null +++ b/docs/java/src-html/gust/util/MessageDifferencer.html @@ -0,0 +1,1678 @@ + + + +Source code + + + +
    +
    +
    001/*
    +002 * Copyright 2018 The StartupOS Authors.
    +003 *
    +004 * Licensed under the Apache License, Version 2.0 (the "License");
    +005 * you may not use this file except in compliance with the License.
    +006 * You may obtain a copy of the License at
    +007 *
    +008 *    https://www.apache.org/licenses/LICENSE-2.0
    +009 *
    +010 * Unless required by applicable law or agreed to in writing, software
    +011 * distributed under the License is distributed on an "AS IS" BASIS,
    +012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +013 * See the License for the specific language governing permissions and
    +014 * limitations under the License.
    +015 */
    +016package gust.util;
    +017
    +018import com.google.auto.value.AutoValue;
    +019import com.google.common.annotations.VisibleForTesting;
    +020import com.google.common.base.Preconditions;
    +021import com.google.common.collect.ImmutableCollection;
    +022import com.google.common.collect.ImmutableList;
    +023import com.google.common.collect.ImmutableMap;
    +024import com.google.common.collect.ImmutableSet;
    +025import com.google.common.collect.Iterables;
    +026import com.google.common.collect.Lists;
    +027import com.google.common.collect.Maps;
    +028import com.google.common.collect.Ordering;
    +029import com.google.common.collect.Sets;
    +030import com.google.protobuf.Descriptors.FieldDescriptor;
    +031import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
    +032import com.google.protobuf.Message;
    +033import com.google.protobuf.TextFormat;
    +034import com.google.protobuf.UnknownFieldSet;
    +035import com.google.protobuf.WireFormat;
    +036import java.io.IOException;
    +037import java.util.Arrays;
    +038import java.util.Collections;
    +039import java.util.Iterator;
    +040import java.util.LinkedList;
    +041import java.util.List;
    +042import java.util.Map;
    +043import java.util.Objects;
    +044import java.util.Set;
    +045import javax.annotation.Nullable;
    +046import javax.annotation.concurrent.Immutable;
    +047
    +048/**
    +049 * Static methods and classes for comparing Protocol Messages.
    +050 *
    +051 * <p>Taken from: com.google.common.truth.extensions.proto.MessageDifferencer
    +052 */
    +053@Immutable
    +054public final class MessageDifferencer {
    +055
    +056    /**
    +057     * MapKeyComparator is used to determine if two elements have the same key when comparing elements
    +058     * of a repeated field as a map.
    +059     */
    +060    public interface MapKeyComparator {
    +061        /**
    +062         * Decides whether the given messages match with respect to the keys of the map entries they
    +063         * represent.
    +064         *
    +065         * @param parentFields the stack of SpecificFields corresponding to the proto path to the given
    +066         *     messages.
    +067         */
    +068        public boolean isMatch(
    +069                MessageDifferencer messageDifferencer,
    +070                Message message1,
    +071                Message message2,
    +072                List<SpecificField> parentFields);
    +073    }
    +074
    +075    private static class ProtoMapKeyComparator implements MapKeyComparator {
    +076        @Override
    +077        public boolean isMatch(
    +078                MessageDifferencer messageDifferencer,
    +079                Message message1,
    +080                Message message2,
    +081                List<SpecificField> parentFields) {
    +082            FieldDescriptor keyField = message1.getDescriptorForType().findFieldByName("key");
    +083            return messageDifferencer.compareFieldValueUsingParentFields(
    +084                    message1,
    +085                    message2,
    +086                    // -1 indices because there is no way to declare a map key as repeated.
    +087                    keyField,
    +088                    -1,
    +089                    -1,
    +090                    null,
    +091                    parentFields);
    +092        }
    +093    }
    +094
    +095    private static final ProtoMapKeyComparator PROTO_MAP_KEY_COMPARATOR = new ProtoMapKeyComparator();
    +096
    +097    /**
    +098     * When comparing a repeated field as map, MultipleFieldMapKeyComparator can be used to specify
    +099     * multiple fields as key for key comparison. Two elements of a repeated field will be regarded as
    +100     * having the same key iff they have the same value for every specified key field. Note that you
    +101     * can also specify only one field as key.
    +102     */
    +103    private static class MultipleFieldsMapKeyComparator implements MapKeyComparator {
    +104        private final List<FieldDescriptor> keyFields;
    +105
    +106        public MultipleFieldsMapKeyComparator(List<FieldDescriptor> key) {
    +107            this.keyFields = key;
    +108        }
    +109
    +110        public MultipleFieldsMapKeyComparator(FieldDescriptor fieldDescriptor) {
    +111            keyFields = new LinkedList<>();
    +112            keyFields.add(fieldDescriptor);
    +113        }
    +114
    +115        @Override
    +116        public boolean isMatch(
    +117                MessageDifferencer messageDifferencer,
    +118                Message message1,
    +119                Message message2,
    +120                List<SpecificField> parentFields) {
    +121            for (int i = 0; i < keyFields.size(); ++i) {
    +122                FieldDescriptor field = keyFields.get(i);
    +123                if (field.isRepeated()) {
    +124                    if (!messageDifferencer.compareRepeatedField(
    +125                            message1, message2, field, null, parentFields)) {
    +126                        return false;
    +127                    }
    +128                } else {
    +129                    if (!messageDifferencer.compareFieldValueUsingParentFields(
    +130                            message1, message2, field, -1, -1, null, parentFields)) {
    +131                        return false;
    +132                    }
    +133                }
    +134            }
    +135            return true;
    +136        }
    +137    }
    +138
    +139    /** Creates a new builder. */
    +140    public static Builder newBuilder() {
    +141        return new Builder();
    +142    }
    +143
    +144    /** Builder object for {@link MessageDifferencer}. */
    +145    public static final class Builder {
    +146        private final Set<FieldDescriptor> setFields = Sets.newHashSet();
    +147        private final Set<FieldDescriptor> ignoreFields = Sets.newHashSet();
    +148        private final Map<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap = Maps.newHashMap();
    +149        private MessageFieldComparison messageFieldComparison = MessageFieldComparison.EQUAL;
    +150        private Scope scope = Scope.FULL;
    +151        private FloatComparison floatComparison = FloatComparison.EXACT;
    +152        private RepeatedFieldComparison repeatedFieldComparison = RepeatedFieldComparison.AS_LIST;
    +153        private boolean reportMatches;
    +154        private FieldComparator fieldComparator;
    +155        private final List<IgnoreCriteria> ignoreCriterias = Lists.newArrayList();
    +156
    +157        private Builder() {}
    +158
    +159        /**
    +160         * The elements of the given repeated field will be treated as a set for diffing purposes, so
    +161         * different orderings of the same elements will be considered equal. Elements which are present
    +162         * on both sides of the comparison but which have changed position will be reported with {@link
    +163         * ReportType#MOVED}. Elements which only exist on one side or the other are reported with
    +164         * {@link ReportType#ADDED} and {@link ReportType#DELETED} regardless of their positions. {@link
    +165         * ReportType#MODIFIED} is never used for this repeated field. If the only differences between
    +166         * the compared messages is that some fields have been moved, then {@link #compare} will return
    +167         * true.
    +168         *
    +169         * <p>If the scope of comparison is set to {@link Scope#PARTIAL}, extra values added to repeated
    +170         * fields of the second message will not cause {@link #compare} to return false.
    +171         *
    +172         * @throws IllegalArgumentException if the field is not repeated or is is already being as a map
    +173         *     for comparison
    +174         */
    +175        public Builder treatAsSet(FieldDescriptor field) {
    +176            Preconditions.checkArgument(
    +177                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +178            Preconditions.checkArgument(
    +179                    !mapKeyComparatorMap.containsKey(field),
    +180                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +181                    field.getFullName());
    +182            setFields.add(field);
    +183            return this;
    +184        }
    +185
    +186        /**
    +187         * The elements of the given repeated field will be treated as a map for diffing purposes, with
    +188         * {@code key} being the map key. Thus, elements with the same key will be compared even if they
    +189         * do not appear at the same index. Differences are reported similarly to {@link #treatAsSet},
    +190         * except that {@link ReportType#MODIFIED} is used to report elements with the same key but
    +191         * different values. Note that if an element is both moved and modified, only {@link
    +192         * ReportType#MODIFIED} will be used. As with {@link #treatAsSet}, if the only differences
    +193         * between the compared messages is that some fields have been moved, then {@link #compare} will
    +194         * return true.
    +195         *
    +196         * @throws IllegalArgumentException if the field is not repeated, is not a message, is already
    +197         *     being as a set for comparison, or is not a containing type of the key
    +198         */
    +199        public Builder treatAsMap(FieldDescriptor field, FieldDescriptor key) {
    +200            Preconditions.checkArgument(
    +201                    field.isRepeated(), "Field must be repeated: %s", field.getFullName());
    +202            Preconditions.checkArgument(
    +203                    field.getJavaType() == JavaType.MESSAGE,
    +204                    "Field has to be message type: %s",
    +205                    field.getFullName());
    +206            Preconditions.checkArgument(
    +207                    key.getContainingType().equals(field.getMessageType()),
    +208                    "%s must be a direct subfield within the repeated field: %s",
    +209                    key.getFullName(),
    +210                    field.getFullName());
    +211            Preconditions.checkArgument(
    +212                    !setFields.contains(field),
    +213                    "Cannot treat this repeated field as both Map and Set for comparison: %s",
    +214                    key.getFullName());
    +215            MultipleFieldsMapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(key);
    +216            mapKeyComparatorMap.put(field, keyComparator);
    +217            return this;
    +218        }
    +219
    +220        public Builder treatAsMapWithMultipleFieldsAsKey(
    +221                FieldDescriptor field, List<FieldDescriptor> keyFields) {
    +222            Preconditions.checkArgument(
    +223                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +224            Preconditions.checkArgument(
    +225                    JavaType.MESSAGE.equals(field.getJavaType()),
    +226                    "Field has to be message type.  Field name is: " + field.getFullName());
    +227            for (int i = 0; i < keyFields.size(); ++i) {
    +228                FieldDescriptor key = keyFields.get(i);
    +229                Preconditions.checkArgument(
    +230                        key.getContainingType().equals(field.getMessageType()),
    +231                        key.getFullName()
    +232                                + " must be a direct subfield within the repeated field: "
    +233                                + field.getFullName());
    +234            }
    +235            Preconditions.checkArgument(
    +236                    !setFields.contains(field),
    +237                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +238            MapKeyComparator keyComparator = new MultipleFieldsMapKeyComparator(keyFields);
    +239            mapKeyComparatorMap.put(field, keyComparator);
    +240            return this;
    +241        }
    +242
    +243        public Builder treatAsMapUsingKeyComparator(
    +244                FieldDescriptor field, MapKeyComparator keyComparator) {
    +245            Preconditions.checkArgument(
    +246                    field.isRepeated(), "Field must be repeated " + field.getFullName());
    +247            Preconditions.checkArgument(
    +248                    JavaType.MESSAGE.equals(field.getJavaType()),
    +249                    "Field has to be message type.  Field name is: " + field.getFullName());
    +250            Preconditions.checkArgument(
    +251                    !setFields.contains(field),
    +252                    "Cannot treat this repeated field as both Map and Set for comparison.");
    +253            mapKeyComparatorMap.put(field, keyComparator);
    +254            return this;
    +255        }
    +256
    +257        /**
    +258         * Indicates that any field with the given descriptor should be ignored for the purposes of
    +259         * comparing two messages. This applies to fields nested in the message structure as well as top
    +260         * level ones. When the MessageDifferencer encounters an ignored field, it is reported with
    +261         * {@link ReportType#IGNORED}.
    +262         *
    +263         * <p>The only place where the field's 'ignored' status is not applied is when it is being used
    +264         * as a key in a field passed to TreatAsMap or is one of the fields passed to
    +265         * TreatAsMapWithMultipleFieldsAsKey. In this case it is compared in key matching but after that
    +266         * it's ignored in value comparison.
    +267         */
    +268        public Builder ignoreField(FieldDescriptor field) {
    +269            ignoreFields.add(field);
    +270            return this;
    +271        }
    +272
    +273        public Builder addIgnoreCriteria(IgnoreCriteria criterion) {
    +274            this.ignoreCriterias.add(criterion);
    +275            return this;
    +276        }
    +277
    +278        /**
    +279         * Sets the type of comparison that is used by the differencer when determining how to compare
    +280         * fields in messages.
    +281         */
    +282        public Builder setMessageFieldComparison(MessageFieldComparison comparison) {
    +283            messageFieldComparison = comparison;
    +284            return this;
    +285        }
    +286
    +287        /** Tells the differencer whether or not to report matches. Defaults to false. */
    +288        public Builder setReportMatches(boolean reportMatches) {
    +289            this.reportMatches = reportMatches;
    +290            return this;
    +291        }
    +292
    +293        /**
    +294         * Sets the scope of the comparison that is used by the differencer when determining which
    +295         * fields to compare between the messages. Defaults to {@link Scope#FULL}.
    +296         */
    +297        public Builder setScope(Scope scope) {
    +298            this.scope = scope;
    +299            return this;
    +300        }
    +301
    +302        /**
    +303         * Sets the type of comparison that is used by the differencer when comparing float (and double)
    +304         * fields in messages. Defaults to {@link FloatComparison#EXACT}.
    +305         *
    +306         * <p>If you use {@link Builder#setFieldComparator(FieldComparator)}, this operation will be
    +307         * ignored
    +308         */
    +309        public Builder setFloatComparison(FloatComparison comparison) {
    +310            floatComparison =
    +311                    Preconditions.checkNotNull(comparison, "FloatComparison should not be null.");
    +312            return this;
    +313        }
    +314
    +315        /**
    +316         * Sets the {@link FieldComparator} used to determine differences between protocol buffer
    +317         * fields. By default it's set to a {@link DefaultFieldComparator} instance. Note that this
    +318         * method must be called before Compare for the comparator to be used.
    +319         */
    +320        public Builder setFieldComparator(FieldComparator fieldComparator) {
    +321            this.fieldComparator = fieldComparator;
    +322            return this;
    +323        }
    +324
    +325        /**
    +326         * Sets the type of comparison for repeated field that is used by this differencer when compare
    +327         * repeated fields in messages. Defaults to {@link RepeatedFieldComparison#AS_LIST}.
    +328         */
    +329        public Builder setRepeatedFieldComparison(RepeatedFieldComparison comparison) {
    +330            repeatedFieldComparison = comparison;
    +331            return this;
    +332        }
    +333
    +334        IgnoreCriteria getMergedIgnoreCriteria() {
    +335            if (!ignoreFields.isEmpty()) {
    +336                IgnoreCriteria criterion = ignoringFields(ImmutableSet.copyOf(ignoreFields));
    +337                return mergeCriteria(Iterables.concat(ignoreCriterias, Collections.singleton(criterion)));
    +338            } else {
    +339                return mergeCriteria(ignoreCriterias);
    +340            }
    +341        }
    +342
    +343        /** Creates a new immutable differencer instance from this builder. */
    +344        public MessageDifferencer build() {
    +345            return new MessageDifferencer(this);
    +346        }
    +347    }
    +348
    +349    private final ImmutableSet<FieldDescriptor> setFields;
    +350    private final IgnoreCriteria ignoreCriteria;
    +351    private final ImmutableMap<FieldDescriptor, MapKeyComparator> mapKeyComparatorMap;
    +352    private final MessageFieldComparison messageFieldComparison;
    +353    private final Scope scope;
    +354    private final FloatComparison floatComparison;
    +355    private final RepeatedFieldComparison repeatedFieldComparison;
    +356    private final boolean reportMatches;
    +357    private final FieldComparator fieldComparator;
    +358
    +359    private MessageDifferencer(Builder builder) {
    +360        setFields = ImmutableSet.copyOf(builder.setFields);
    +361        ignoreCriteria = builder.getMergedIgnoreCriteria();
    +362        mapKeyComparatorMap = ImmutableMap.copyOf(builder.mapKeyComparatorMap);
    +363        messageFieldComparison = builder.messageFieldComparison;
    +364        scope = builder.scope;
    +365        floatComparison = builder.floatComparison;
    +366        repeatedFieldComparison = builder.repeatedFieldComparison;
    +367        reportMatches = builder.reportMatches;
    +368        fieldComparator =
    +369                builder.fieldComparator == null
    +370                        ? new DefaultFieldComparator(floatComparison)
    +371                        : builder.fieldComparator;
    +372    }
    +373
    +374    /**
    +375     * Determines whether the supplied messages are equal. Equality is defined as all fields within
    +376     * the two messages being set to the same value. Primitive fields and strings are compared by
    +377     * value while embedded messages/groups are compared as if via a recursive call.
    +378     *
    +379     * @throws IllegalArgumentException if the messages have different descriptors
    +380     */
    +381    public static boolean equals(Message message1, Message message2) {
    +382        return newBuilder().build().compare(message1, message2);
    +383    }
    +384
    +385    /**
    +386     * Determines whether the supplied messages are equivalent. Equivalency is defined as all fields
    +387     * within the two messages having the same value. This differs from the {@link #equals(Message,
    +388     * Message)} method above in that fields with default values are considered set to said value
    +389     * automatically. This method also ignores unknown fields.
    +390     *
    +391     * @throws IllegalArgumentException if the messages have different descriptors
    +392     */
    +393    public static boolean equivalent(Message message1, Message message2) {
    +394        return newBuilder()
    +395                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +396                .build()
    +397                .compare(message1, message2);
    +398    }
    +399
    +400    /**
    +401     * Determines whether the supplied messages are approximately equal. Approximate equality is
    +402     * defined as all fields within the two messages being approximately equal. Primitive (non-float)
    +403     * fields and strings are compared by value, floats are compared using an equivalent of C++ {@code
    +404     * MathUtil::AlmostEquals} and embedded messages/groups are compared as if via a recursive call.
    +405     *
    +406     * @throws IllegalArgumentException if the messages have different descriptors
    +407     */
    +408    public static boolean approximatelyEquals(Message message1, Message message2) {
    +409        return newBuilder()
    +410                .setFloatComparison(FloatComparison.APPROXIMATE)
    +411                .build()
    +412                .compare(message1, message2);
    +413    }
    +414
    +415    /**
    +416     * Determines whether the supplied messages are approximately equivalent. Approximate equivalency
    +417     * is defined as all fields within the two messages being approximately equivalent. As in {@link
    +418     * #approximatelyEquals}, primitive (non-float) fields and strings are compared by value, floats
    +419     * are compared using an equivalent of C++ {@code MathUtil::AlmostEquals} and embedded
    +420     * messages/groups are compared as if via a recursive call. However, fields with default values
    +421     * are considered set to said value, as per {@link #equivalent}.
    +422     *
    +423     * @throws IllegalArgumentException if the messages have different descriptors
    +424     */
    +425    public static boolean approximatelyEquivalent(Message message1, Message message2) {
    +426        return newBuilder()
    +427                .setMessageFieldComparison(MessageFieldComparison.EQUIVALENT)
    +428                .setFloatComparison(FloatComparison.APPROXIMATE)
    +429                .build()
    +430                .compare(message1, message2);
    +431    }
    +432
    +433    /**
    +434     * IgnoreCriteria are registered with addIgnoreCriteria. For each compared field isIgnored is
    +435     * called on each criterion until one returns true or all return false. isIgnored is called for
    +436     * fields where at least one side has a value.
    +437     */
    +438    public interface IgnoreCriteria {
    +439
    +440        /**
    +441         * Should this field be ignored during the comparison.
    +442         *
    +443         * @param message1 the message containing the field being compared
    +444         * @param message2 the message containing the field being compared
    +445         * @param fieldDescriptor the field being compared (null for unknown fields). More details about
    +446         *     unknown field is available in the last entry of fieldPath.
    +447         * @param fieldPath an unmodifiable view of the path from the root message to this field
    +448         * @return whether this field should be ignored in the comparison.
    +449         */
    +450        boolean isIgnored(
    +451                Message message1,
    +452                Message message2,
    +453                @Nullable FieldDescriptor fieldDescriptor,
    +454                List<SpecificField> fieldPath);
    +455    }
    +456
    +457    private static IgnoreCriteria ignoringFields(
    +458            final ImmutableCollection<FieldDescriptor> fieldDescriptors) {
    +459        return (message1, message2, fieldDescriptor, fieldPath) ->
    +460                fieldDescriptors.contains(fieldDescriptor);
    +461    }
    +462
    +463    static IgnoreCriteria mergeCriteria(final Iterable<IgnoreCriteria> criteria) {
    +464        return (message1, message2, fieldDescriptor, fieldPath) -> {
    +465            for (IgnoreCriteria criterion : criteria) {
    +466                if (criterion.isIgnored(message1, message2, fieldDescriptor, fieldPath)) {
    +467                    return true;
    +468                }
    +469            }
    +470            return false;
    +471        };
    +472    }
    +473
    +474    /** Identifies an individual field in a message instance. */
    +475    @AutoValue
    +476    @Immutable
    +477    public abstract static class SpecificField {
    +478
    +479        private static SpecificField forField(FieldDescriptor field) {
    +480            Preconditions.checkNotNull(field);
    +481            return new AutoValue_MessageDifferencer_SpecificField(field, null, -1, -1);
    +482        }
    +483
    +484        private static SpecificField forRepeatedField(FieldDescriptor field, int index) {
    +485            Preconditions.checkNotNull(field);
    +486            Preconditions.checkArgument(index >= 0);
    +487            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, index);
    +488        }
    +489
    +490        private static SpecificField forRepeatedField(FieldDescriptor field, int index, int newIndex) {
    +491            Preconditions.checkNotNull(field);
    +492            Preconditions.checkArgument(index >= 0);
    +493            Preconditions.checkArgument(newIndex >= 0);
    +494            return new AutoValue_MessageDifferencer_SpecificField(field, null, index, newIndex);
    +495        }
    +496
    +497        private static SpecificField forUnknownDescriptor(UnknownDescriptor unknown, int index) {
    +498            Preconditions.checkNotNull(unknown);
    +499            return new AutoValue_MessageDifferencer_SpecificField(null, unknown, index, index);
    +500        }
    +501
    +502        /** Returns the descriptor for known fields, or null for unknown fields. */
    +503        @Nullable
    +504        public abstract FieldDescriptor getField();
    +505
    +506        /** Returns the descriptor for unknown fields, or null for known fields. */
    +507        @Nullable
    +508        public abstract UnknownDescriptor getUnknown();
    +509
    +510        /**
    +511         * Returns the field index. If this a repeated field, this is the index within it. For unknown
    +512         * fields, this is the index of the field among all unknown fields of the same field number and
    +513         * type. For other fields, returns -1.
    +514         */
    +515        public abstract int getIndex();
    +516
    +517        /**
    +518         * Returns the new field index. If this field is a repeated field which is being treated as a
    +519         * map or a set, this indicates the position to which the element has been moved. This only
    +520         * applies to {@link ReportType#MOVED}, and (in the case of {@link Builder#treatAsMap}) {@link
    +521         * ReportType#MODIFIED}.
    +522         */
    +523        public abstract int getNewIndex();
    +524    }
    +525
    +526    /** Unknown field information. */
    +527    @AutoValue
    +528    @Immutable
    +529    public abstract static class UnknownDescriptor {
    +530
    +531        private static UnknownDescriptor create(int fieldNumber, UnknownFieldType fieldType) {
    +532            return new AutoValue_MessageDifferencer_UnknownDescriptor(fieldNumber, fieldType);
    +533        }
    +534
    +535        /** Returns the field number. */
    +536        public abstract int getFieldNumber();
    +537
    +538        /** Returns the field type. */
    +539        public abstract UnknownFieldType getFieldType();
    +540    }
    +541
    +542    /**
    +543     * Interface for comparing protocol buffer fields. Regular users should consider using {@link
    +544     * DefaultFieldComparator} rather than this interface. Currently, this does not support comparing
    +545     * unknown fields.
    +546     */
    +547    public interface FieldComparator {
    +548        /** Comparison result for {@link FieldComparator#compare}. */
    +549        public enum ComparisonResult {
    +550            /**
    +551             * Compared fields are equal. In case of comparing submessages, user should not recursively
    +552             * compare their contents.
    +553             */
    +554            SAME,
    +555
    +556            /**
    +557             * Compared fields are different. In case of comparing submessages, user should not
    +558             * recursively compare their contents.
    +559             */
    +560            DIFFERENT,
    +561
    +562            /**
    +563             * Compared submessages need to be compared recursively. FieldComparator does not specify the
    +564             * semantics of recursive comparison. This value should not be returned for simple values.
    +565             */
    +566            RECURSE;
    +567
    +568            /**
    +569             * Return {@link ComparisonResult} from a boolean value.
    +570             *
    +571             * @return {@link ComparisonResult#SAME} if result is true, {@link ComparisonResult#DIFFERENT}
    +572             *     if result is false.
    +573             */
    +574            public static ComparisonResult of(boolean result) {
    +575                return result ? SAME : DIFFERENT;
    +576            }
    +577        }
    +578
    +579        /**
    +580         * Compares the values of a field in two protocol buffer messages.
    +581         *
    +582         * @param message1 the first message.
    +583         * @param message2 the second message.
    +584         * @param field field descriptor of the field where need to be compared.
    +585         * @param index1 the index of first message. In case the given FieldDescriptor points to a
    +586         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +587         * @param index2 the index of second message. In case the given FieldDescriptor points to a
    +588         *     repeated field, the indices need to be valid. Otherwise they should be ignored.
    +589         * @param parentFields an immutable list of fields that was taken to find the current field (not
    +590         *     include current field).
    +591         * @return Returns SAME or DIFFERENT for simple values, and SAME, DIFFERENT or RECURSE for
    +592         *     submessages. Returning RECURSE for fields not being submessages is illegal.
    +593         */
    +594        ComparisonResult compare(
    +595                Message message1,
    +596                Message message2,
    +597                FieldDescriptor field,
    +598                int index1,
    +599                int index2,
    +600                ImmutableList<SpecificField> parentFields);
    +601    }
    +602
    +603    /** Interface by which callers can receive information about each difference. */
    +604    public interface Reporter {
    +605        /**
    +606         * Reports information about a specific field.
    +607         *
    +608         * @param type the type of difference
    +609         * @param message1 the first message
    +610         * @param message2 the second message
    +611         * @param fieldPath an immutable list of fields that was taken to find the current field. For
    +612         *     example, for a field found in an embedded message, the list will contain two field
    +613         *     descriptors. The first will be the field of the embedded message itself and the second
    +614         *     will be the actual field in the embedded message that was added/deleted/modified.
    +615         */
    +616        void report(
    +617                ReportType type,
    +618                Message message1,
    +619                Message message2,
    +620                ImmutableList<SpecificField> fieldPath);
    +621    }
    +622
    +623    /** The type of the reported difference. */
    +624    public enum ReportType {
    +625        /** A field has been added to {@code message2}. */
    +626        ADDED,
    +627
    +628        /** A field has been deleted in {@code message2}. */
    +629        DELETED,
    +630
    +631        IGNORED,
    +632
    +633        /** A field has been modified. */
    +634        MODIFIED,
    +635
    +636        /**
    +637         * A repeated field has been moved to another location. This only applies when using {@link
    +638         * Builder#treatAsSet} or {@link Builder#treatAsMap}. Also note that for any given field, {@link
    +639         * #MODIFIED} and {@link #MOVED} are mutually exclusive. If a field has been both moved and
    +640         * modified, then only {@link #MODIFIED} will be used.
    +641         */
    +642        MOVED,
    +643
    +644        /**
    +645         * Reports that two fields match. Useful for doing side-by-side diffs. This is mutually
    +646         * exclusive with {@link #MODIFIED} and {@link #MOVED}. Matches must be enabled using {@link
    +647         * Builder#setReportMatches}.
    +648         */
    +649        MATCHED
    +650    }
    +651
    +652    /**
    +653     * The type of comparison that is used by the differencer when determining how to compare fields
    +654     * in messages.
    +655     */
    +656    public enum MessageFieldComparison {
    +657        /** Fields must be present in both messages for the messages to be considered the same. */
    +658        EQUAL,
    +659
    +660        /**
    +661         * Fields with default values are considered set for comparison purposes even if not explicitly
    +662         * set in the messages themselves. Unknown fields are ignored.
    +663         */
    +664        EQUIVALENT
    +665    }
    +666
    +667    /** Which fields to consider when comparing messages. */
    +668    public enum Scope {
    +669        /** All fields of both messages are considered in the comparison. */
    +670        FULL,
    +671
    +672        /**
    +673         * Only fields present in the first message are considered; fields set only in the second
    +674         * message will be skipped during comparison.
    +675         */
    +676        PARTIAL
    +677    }
    +678
    +679    /** How float and double fields in messages are compared. */
    +680    public enum FloatComparison {
    +681        /** Floats and doubles are compared exactly. */
    +682        EXACT,
    +683
    +684        /** Floats and doubles are compared using an equivalent of C++ {@code MathUtil::AlmostEqual}. */
    +685        APPROXIMATE
    +686    }
    +687
    +688    /** How to compare repeated fields. */
    +689    public enum RepeatedFieldComparison {
    +690        /**
    +691         * Repeated fields are compared in order. Differing values at the same index are reported using
    +692         * ReportModified(). If the repeated fields have different numbers of elements, the unpaired
    +693         * elements are reported using {@link ReportType#ADDED} or {@link ReportType#DELETED}.
    +694         */
    +695        AS_LIST,
    +696        /** Treat all the repeated fields as sets by default. See {@link Builder#treatAsSet}. */
    +697        AS_SET
    +698    }
    +699
    +700    /** The wire type of unknown fields. */
    +701    public enum UnknownFieldType {
    +702        /** Varint. */
    +703        VARINT(WireFormat.WIRETYPE_VARINT) {
    +704            @Override
    +705            public List<?> getValues(UnknownFieldSet.Field field) {
    +706                return field.getVarintList();
    +707            }
    +708        },
    +709
    +710        /** Fixed32. */
    +711        FIXED32(WireFormat.WIRETYPE_FIXED32) {
    +712            @Override
    +713            public List<?> getValues(UnknownFieldSet.Field field) {
    +714                return field.getFixed32List();
    +715            }
    +716        },
    +717
    +718        /** Fixed64. */
    +719        FIXED64(WireFormat.WIRETYPE_FIXED64) {
    +720            @Override
    +721            public List<?> getValues(UnknownFieldSet.Field field) {
    +722                return field.getFixed64List();
    +723            }
    +724        },
    +725
    +726        /** Length delimited. */
    +727        LENGTH_DELIMITED(WireFormat.WIRETYPE_LENGTH_DELIMITED) {
    +728            @Override
    +729            public List<?> getValues(UnknownFieldSet.Field field) {
    +730                return field.getLengthDelimitedList();
    +731            }
    +732        },
    +733
    +734        /** Group. */
    +735        GROUP(WireFormat.WIRETYPE_START_GROUP) {
    +736            @Override
    +737            public List<?> getValues(UnknownFieldSet.Field field) {
    +738                return field.getGroupList();
    +739            }
    +740        };
    +741
    +742        final int wireFormat;
    +743
    +744        UnknownFieldType(int wireFormat) {
    +745            this.wireFormat = wireFormat;
    +746        }
    +747
    +748        /** Returns the wire format for this unknown field type. */
    +749        public int getWireFormat() {
    +750            return wireFormat;
    +751        }
    +752
    +753        // TODO(chrisn): Genericize UnknownFieldType based on value type?
    +754        /** Returns the corresponding values from the given field. */
    +755        public abstract List<?> getValues(UnknownFieldSet.Field field);
    +756    }
    +757
    +758    /**
    +759     * Compares the two specified messages, returning true if they are the same.
    +760     *
    +761     * @throws IllegalArgumentException if the messages have different descriptors
    +762     */
    +763    public boolean compare(Message message1, Message message2) {
    +764        return compare(message1, message2, null);
    +765    }
    +766
    +767    /**
    +768     * Compares the two specified messages, returning true if they are the same. Reports differences
    +769     * to the reporter if it is non-null.
    +770     *
    +771     * @throws IllegalArgumentException if the messages have different descriptors
    +772     */
    +773    public boolean compare(Message message1, Message message2, @Nullable Reporter reporter) {
    +774        List<SpecificField> stack = Lists.newArrayList();
    +775        return compare(message1, message2, reporter, stack);
    +776    }
    +777
    +778    private boolean compare(
    +779            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +780        checkSameDescriptor(message1, message2);
    +781        if ((message1 == message2) && ((reporter == null) || !reportMatches)) {
    +782            return true;
    +783        }
    +784        boolean unknownCompareResult = true;
    +785        if (!compareUnknownFields(message1, message2, reporter, stack)) {
    +786            if (reporter == null) {
    +787                return false;
    +788            }
    +789            unknownCompareResult = false;
    +790        }
    +791        Set<FieldDescriptor> message1Fields = message1.getAllFields().keySet();
    +792        Set<FieldDescriptor> message2Fields = message2.getAllFields().keySet();
    +793        return compareRequestedFields(
    +794                message1, message2, message1Fields, message2Fields, reporter, stack)
    +795                && unknownCompareResult;
    +796    }
    +797
    +798    /**
    +799     * Same as above, except comparing only the given sets of field descriptors, using only the given
    +800     * message fields.
    +801     *
    +802     * @throws IllegalArgumentException if the messages have different descriptors
    +803     */
    +804    public boolean compareWithFields(
    +805            Message message1,
    +806            Message message2,
    +807            Set<FieldDescriptor> message1Fields,
    +808            Set<FieldDescriptor> message2Fields) {
    +809        return compareWithFields(message1, message2, message1Fields, message2Fields, null);
    +810    }
    +811
    +812    /**
    +813     * Compares the two specified messages, returning true if they are the same, using only the given
    +814     * message fields. Reports differences to the reporter if it is non-null.
    +815     *
    +816     * @throws IllegalArgumentException if the messages have different descriptors
    +817     */
    +818    public boolean compareWithFields(
    +819            Message message1,
    +820            Message message2,
    +821            Set<FieldDescriptor> message1Fields,
    +822            Set<FieldDescriptor> message2Fields,
    +823            @Nullable Reporter reporter) {
    +824        checkSameDescriptor(message1, message2);
    +825        // Ensure fields are sorted.
    +826        message1Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message1Fields));
    +827        message2Fields = ImmutableSet.copyOf(Ordering.natural().sortedCopy(message2Fields));
    +828        List<SpecificField> stack = Lists.newArrayList();
    +829        return compareRequestedFields(
    +830                message1, message2, message1Fields, message2Fields, reporter, stack);
    +831    }
    +832
    +833    private void checkSameDescriptor(Message message1, Message message2) {
    +834        Preconditions.checkArgument(
    +835                message1.getDescriptorForType().equals(message2.getDescriptorForType()),
    +836                "Comparison between two messages with different descriptors: %s and %s",
    +837                message1.getClass(),
    +838                message2.getClass());
    +839    }
    +840
    +841    private boolean compareUnknownFields(
    +842            Message message1, Message message2, @Nullable Reporter reporter, List<SpecificField> stack) {
    +843        UnknownFieldSet unknownFieldSet1 = message1.getUnknownFields();
    +844        UnknownFieldSet unknownFieldSet2 = message2.getUnknownFields();
    +845        return compareUnknownFields(
    +846                message1, message2, unknownFieldSet1, unknownFieldSet2, reporter, stack);
    +847    }
    +848
    +849    private boolean compareUnknownFields(
    +850            Message message1,
    +851            Message message2,
    +852            UnknownFieldSet unknownFieldSet1,
    +853            UnknownFieldSet unknownFieldSet2,
    +854            @Nullable Reporter reporter,
    +855            List<SpecificField> stack) {
    +856        if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +857            return true;
    +858        }
    +859        boolean identical = unknownFieldSet1.equals(unknownFieldSet2);
    +860        if (identical && ((reporter == null) || !reportMatches)) {
    +861            return true;
    +862        }
    +863        Set<Integer> numbers1 = unknownFieldSet1.asMap().keySet();
    +864        Set<Integer> numbers2 = unknownFieldSet2.asMap().keySet();
    +865        if (numbers1.isEmpty() && numbers2.isEmpty()) {
    +866            return true;
    +867        }
    +868
    +869        boolean match = true;
    +870        // Use TreeSet to visit the fields in tag order.
    +871        for (Integer number : Sets.newTreeSet(Sets.union(numbers1, numbers2))) {
    +872            for (UnknownFieldType fieldType : UnknownFieldType.values()) {
    +873                List<?> values1 = fieldType.getValues(unknownFieldSet1.getField(number));
    +874                List<?> values2 = fieldType.getValues(unknownFieldSet2.getField(number));
    +875                if (values1.equals(values2)) {
    +876                    continue;
    +877                }
    +878                if (values1.isEmpty()) {
    +879                    if (scope == Scope.PARTIAL) {
    +880                        continue;
    +881                    }
    +882                }
    +883                UnknownDescriptor unknownDesc = UnknownDescriptor.create(number, fieldType);
    +884                for (int i = 0, count = Math.max(values1.size(), values2.size()); i < count; i++) {
    +885                    Object value1 = (i < values1.size()) ? values1.get(i) : null;
    +886                    Object value2 = (i < values2.size()) ? values2.get(i) : null;
    +887
    +888                    ReportType reportType = ReportType.MATCHED;
    +889                    SpecificField unknownField = SpecificField.forUnknownDescriptor(unknownDesc, i);
    +890                    if (ignoreCriteria.isIgnored(message1, message2, null, immutable(stack, unknownField))) {
    +891                        if ((reporter == null) || !reportMatches) {
    +892                            continue;
    +893                        }
    +894                        reportType = ReportType.IGNORED;
    +895                    } else if (value1 == null) {
    +896                        reportType = ReportType.ADDED;
    +897                        match = false;
    +898                    } else if (value2 == null) {
    +899                        reportType = ReportType.DELETED;
    +900                        match = false;
    +901                    } else if (fieldType == UnknownFieldType.GROUP) {
    +902                        stack.add(unknownField);
    +903                        if (!compareUnknownFields(
    +904                                message1,
    +905                                message2,
    +906                                (UnknownFieldSet) value1,
    +907                                (UnknownFieldSet) value2,
    +908                                reporter,
    +909                                stack)) {
    +910                            reportType = ReportType.MODIFIED;
    +911                            match = false;
    +912                        }
    +913                        pop(stack);
    +914                    } else if (!Objects.equals(value1, value2)) {
    +915                        reportType = ReportType.MODIFIED;
    +916                        match = false;
    +917                    }
    +918
    +919                    if (reporter != null) {
    +920                        if ((reportType != ReportType.MATCHED) || reportMatches) {
    +921                            reporter.report(reportType, message1, message2, immutable(stack, unknownField));
    +922                        }
    +923                    } else if (!match) {
    +924                        return false;
    +925                    }
    +926                }
    +927            }
    +928        }
    +929        return match;
    +930    }
    +931
    +932    private boolean compareRequestedFields(
    +933            Message message1,
    +934            Message message2,
    +935            Set<FieldDescriptor> message1Fields,
    +936            Set<FieldDescriptor> message2Fields,
    +937            @Nullable Reporter reporter,
    +938            List<SpecificField> stack) {
    +939        if (scope == Scope.FULL) {
    +940            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +941                // We need to merge the field lists of both messages (i.e.
    +942                // we are merely checking for a difference in field values,
    +943                // rather than the addition or deletion of fields).
    +944                Set<FieldDescriptor> fieldsUnion = Sets.union(message1Fields, message2Fields);
    +945                return compareWithFieldsInternal(
    +946                        message1, message2, fieldsUnion, fieldsUnion, reporter, stack);
    +947            } else {
    +948                // Simple equality comparison, use the unaltered field lists.
    +949                return compareWithFieldsInternal(
    +950                        message1, message2, message1Fields, message2Fields, reporter, stack);
    +951            }
    +952        } else {
    +953            if (messageFieldComparison == MessageFieldComparison.EQUIVALENT) {
    +954                // We use the list of fields for message1 for both messages when
    +955                // comparing.  This way, extra fields in message2 are ignored,
    +956                // and missing fields in message2 use their default value.
    +957                return compareWithFieldsInternal(
    +958                        message1, message2, message1Fields, message1Fields, reporter, stack);
    +959            } else {
    +960                // We need to consider the full list of fields for message1
    +961                // but only the intersection for message2.  This way, any fields
    +962                // only present in message2 will be ignored, but any fields only
    +963                // present in message1 will be marked as a difference.
    +964                Set<FieldDescriptor> fieldsIntersection = Sets.intersection(message1Fields, message2Fields);
    +965                return compareWithFieldsInternal(
    +966                        message1, message2, message1Fields, fieldsIntersection, reporter, stack);
    +967            }
    +968        }
    +969    }
    +970
    +971    private static final Set<FieldDescriptor> SENTINEL = Collections.singleton(null);
    +972
    +973    private boolean compareWithFieldsInternal(
    +974            Message message1,
    +975            Message message2,
    +976            Set<FieldDescriptor> message1Fields,
    +977            Set<FieldDescriptor> message2Fields,
    +978            @Nullable Reporter reporter,
    +979            List<SpecificField> stack) {
    +980
    +981        boolean isDifferent = false;
    +982        Iterator<FieldDescriptor> it1 = Iterables.concat(message1Fields, SENTINEL).iterator();
    +983        Iterator<FieldDescriptor> it2 = Iterables.concat(message2Fields, SENTINEL).iterator();
    +984
    +985        // Loop while there are any fields in either message.
    +986        FieldDescriptor field1 = it1.next();
    +987        FieldDescriptor field2 = it2.next();
    +988        while ((field1 != null) || (field2 != null)) {
    +989            // Check for differences in the field itself.
    +990            if (fieldBefore(field1, field2)) {
    +991                // Field 1 is not in the field list for message 2.
    +992                if (ignoreCriteria.isIgnored(
    +993                        message1, message2, field1, Collections.unmodifiableList(stack))) {
    +994                    // We are ignoring field1. Report the ignore and move on to the next field in message1.
    +995                    if (reporter != null) {
    +996                        report(ReportType.IGNORED, message1, message2, field1, message1, reporter, stack);
    +997                    }
    +998                    field1 = it1.next();
    +999                    continue;
    +1000                }
    +1001                if (reporter == null) {
    +1002                    return false;
    +1003                } else {
    +1004                    report(ReportType.DELETED, message1, message2, field1, message1, reporter, stack);
    +1005                    isDifferent = true;
    +1006                }
    +1007                field1 = it1.next();
    +1008                continue;
    +1009            } else if (fieldBefore(field2, field1)) {
    +1010                // Field 2 is not in the field list for message 1.
    +1011                if (ignoreCriteria.isIgnored(
    +1012                        message1, message2, field2, Collections.unmodifiableList(stack))) {
    +1013                    // We are ignoring field2. Report the ignore and move on to the next field in message2.
    +1014                    if (reporter != null) {
    +1015                        report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1016                    }
    +1017                    field2 = it2.next();
    +1018                    continue;
    +1019                }
    +1020                if (reporter == null) {
    +1021                    return false;
    +1022                } else {
    +1023                    report(ReportType.ADDED, message1, message2, field2, message2, reporter, stack);
    +1024                    isDifferent = true;
    +1025                }
    +1026                field2 = it2.next();
    +1027                continue;
    +1028            }
    +1029
    +1030            // By this point, field1 and field2 are guaranteed to point to the same
    +1031            // field, so we can now compare the values.
    +1032            boolean fieldDifferent;
    +1033            if (ignoreCriteria.isIgnored(
    +1034                    message1, message2, field1, Collections.unmodifiableList(stack))) {
    +1035                if (reporter != null) {
    +1036                    report(ReportType.IGNORED, message1, message2, field2, message2, reporter, stack);
    +1037                }
    +1038            } else if (field1.isRepeated()) {
    +1039                fieldDifferent = !compareRepeatedField(message1, message2, field1, reporter, stack);
    +1040                if (fieldDifferent) {
    +1041                    if (reporter == null) {
    +1042                        return false;
    +1043                    }
    +1044                    isDifferent = true;
    +1045                }
    +1046            } else {
    +1047                SpecificField specificField = SpecificField.forField(field1);
    +1048                fieldDifferent =
    +1049                        !compareFieldValueUsingParentFields(
    +1050                                message1, message2, field1, -1, -1, reporter, stack);
    +1051                // If we have found differences, either report them or terminate if
    +1052                // no reporter is present.
    +1053                if (fieldDifferent) {
    +1054                    if (reporter == null) {
    +1055                        return false;
    +1056                    }
    +1057                    reporter.report(ReportType.MODIFIED, message1, message2, immutable(stack, specificField));
    +1058                    // If the field was at any point found to be different, mark to
    +1059                    // return this difference once the loop has completed.
    +1060                    isDifferent = true;
    +1061                } else if (reportMatches && (reporter != null)) {
    +1062                    reporter.report(ReportType.MATCHED, message1, message2, immutable(stack, specificField));
    +1063                }
    +1064            }
    +1065            field1 = it1.next();
    +1066            field2 = it2.next();
    +1067        }
    +1068        return !isDifferent;
    +1069    }
    +1070
    +1071    boolean compareFieldValueUsingParentFields(
    +1072            Message message1,
    +1073            Message message2,
    +1074            FieldDescriptor field,
    +1075            int index1,
    +1076            int index2,
    +1077            @Nullable Reporter reporter,
    +1078            List<SpecificField> stack) {
    +1079        FieldComparator.ComparisonResult result =
    +1080                fieldComparator.compare(
    +1081                        message1, message2, field, index1, index2, ImmutableList.copyOf(stack));
    +1082        if (result == FieldComparator.ComparisonResult.RECURSE) {
    +1083            Preconditions.checkArgument(
    +1084                    field.getJavaType() == JavaType.MESSAGE,
    +1085                    "FieldComparator should not return RECURSE for fields not being submessages!");
    +1086            // Get the nested messages and compare them using one of the
    +1087            // methods.
    +1088            Message nextMessage1 =
    +1089                    field.isRepeated()
    +1090                            ? (Message) message1.getRepeatedField(field, index1)
    +1091                            : (Message) message1.getField(field);
    +1092            Message nextMessage2 =
    +1093                    field.isRepeated()
    +1094                            ? (Message) message2.getRepeatedField(field, index2)
    +1095                            : (Message) message2.getField(field);
    +1096
    +1097            stack.add(
    +1098                    field.isRepeated()
    +1099                            ? SpecificField.forRepeatedField(field, index1, index2)
    +1100                            : SpecificField.forField(field));
    +1101            boolean isSame = compare(nextMessage1, nextMessage2, reporter, stack);
    +1102            pop(stack);
    +1103            return isSame;
    +1104        }
    +1105
    +1106        return result == FieldComparator.ComparisonResult.SAME;
    +1107    }
    +1108
    +1109    private void report(
    +1110            ReportType reportType,
    +1111            Message message1,
    +1112            Message message2,
    +1113            FieldDescriptor field,
    +1114            Message first,
    +1115            Reporter reporter,
    +1116            List<SpecificField> stack) {
    +1117        if (field.isRepeated()) {
    +1118            int count = first.getRepeatedFieldCount(field);
    +1119            for (int i = 0; i < count; i++) {
    +1120                reporter.report(
    +1121                        reportType,
    +1122                        message1,
    +1123                        message2,
    +1124                        immutable(stack, SpecificField.forRepeatedField(field, i)));
    +1125            }
    +1126        } else {
    +1127            reporter.report(
    +1128                    reportType, message1, message2, immutable(stack, SpecificField.forField(field)));
    +1129        }
    +1130    }
    +1131
    +1132    private boolean fieldBefore(FieldDescriptor field1, FieldDescriptor field2) {
    +1133        if (field1 == null) {
    +1134            return false;
    +1135        }
    +1136        if (field2 == null) {
    +1137            return true;
    +1138        }
    +1139        return field1.getNumber() < field2.getNumber();
    +1140    }
    +1141
    +1142    boolean compareRepeatedField(
    +1143            Message message1,
    +1144            Message message2,
    +1145            FieldDescriptor repeatedField,
    +1146            @Nullable Reporter reporter,
    +1147            List<SpecificField> stack) {
    +1148        int count1 = message1.getRepeatedFieldCount(repeatedField);
    +1149        int count2 = message2.getRepeatedFieldCount(repeatedField);
    +1150        boolean treatedAsSubset = isTreatedAsSubset(repeatedField);
    +1151
    +1152        // If the field is not treated as subset and no detailed reports is needed,
    +1153        // we do a quick check on the number of the elements to avoid unnecessary
    +1154        // comparison.
    +1155        if ((count1 != count2) && (reporter == null) && !treatedAsSubset) {
    +1156            return false;
    +1157        }
    +1158
    +1159        // These two arrays are used for store the index of the correspondent
    +1160        // element in peer repeated field.
    +1161        int[] matchList1 = new int[count1];
    +1162        int[] matchList2 = new int[count2];
    +1163
    +1164        // Try to match indices of the repeated fields. Return false if match fails
    +1165        // and there's no detailed report needed.
    +1166        if (!matchRepeatedFieldIndices(message1, message2, repeatedField, matchList1, matchList2, stack)
    +1167                && (reporter == null)) {
    +1168            return false;
    +1169        }
    +1170
    +1171        boolean fieldDifferent = false;
    +1172        // At this point, we have already matched pairs of fields (with the reporting
    +1173        // to be done later). Now to check if the paired elements are different.
    +1174        for (int i = 0; i < count1; i++) {
    +1175            if (matchList1[i] == -1) {
    +1176                continue;
    +1177            }
    +1178            int newIndex = matchList1[i];
    +1179            SpecificField specificField = SpecificField.forRepeatedField(repeatedField, i, newIndex);
    +1180            boolean result =
    +1181                    compareFieldValueUsingParentFields(
    +1182                            message1, message2, repeatedField, i, newIndex, reporter, stack);
    +1183
    +1184            // If we have found differences, either report them or terminate if
    +1185            // no reporter is present. Note that ReportModified, ReportMoved, and
    +1186            // ReportMatched are all mutually exclusive.
    +1187            if (!result) {
    +1188                if (reporter == null) {
    +1189                    return false;
    +1190                }
    +1191                fieldDifferent = true;
    +1192            }
    +1193
    +1194            if (reporter == null) {
    +1195                continue;
    +1196            }
    +1197
    +1198            ReportType reportType = null;
    +1199            if (!result) {
    +1200                reportType = ReportType.MODIFIED;
    +1201            } else if (i != newIndex) {
    +1202                reportType = ReportType.MOVED;
    +1203            } else if (reportMatches) {
    +1204                reportType = ReportType.MATCHED;
    +1205            }
    +1206            if (reportType != null) {
    +1207                reporter.report(reportType, message1, message2, immutable(stack, specificField));
    +1208            }
    +1209        }
    +1210
    +1211        // Report any remaining additions or deletions.
    +1212        for (int i = 0; i < count2; i++) {
    +1213            if (matchList2[i] != -1) {
    +1214                continue;
    +1215            }
    +1216            if (!treatedAsSubset) {
    +1217                fieldDifferent = true;
    +1218            }
    +1219            if (reporter != null) {
    +1220                reporter.report(
    +1221                        ReportType.ADDED,
    +1222                        message1,
    +1223                        message2,
    +1224                        immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1225            }
    +1226        }
    +1227
    +1228        for (int i = 0; i < count1; i++) {
    +1229            if (matchList1[i] != -1) {
    +1230                continue;
    +1231            }
    +1232            // We would have exited earlier if reporter was null.
    +1233            reporter.report(
    +1234                    ReportType.DELETED,
    +1235                    message1,
    +1236                    message2,
    +1237                    immutable(stack, SpecificField.forRepeatedField(repeatedField, i)));
    +1238            fieldDifferent = true;
    +1239        }
    +1240        return !fieldDifferent;
    +1241    }
    +1242
    +1243    private boolean matchRepeatedFieldIndices(
    +1244            Message message1,
    +1245            Message message2,
    +1246            FieldDescriptor repeatedField,
    +1247            int[] matchList1,
    +1248            int[] matchList2,
    +1249            List<SpecificField> stack) {
    +1250        MapKeyComparator keyComparator = mapKeyComparatorMap.get(repeatedField);
    +1251        if (repeatedField.isMapField() && (keyComparator == null)) {
    +1252            keyComparator = PROTO_MAP_KEY_COMPARATOR;
    +1253        }
    +1254        int count1 = matchList1.length;
    +1255        int count2 = matchList2.length;
    +1256        Arrays.fill(matchList1, -1);
    +1257        Arrays.fill(matchList2, -1);
    +1258
    +1259        boolean success = true;
    +1260        // Find potential match if this is a special repeated field.
    +1261        if ((keyComparator != null) || isTreatedAsSet(repeatedField)) {
    +1262            for (int i = 0; i < count1; i++) {
    +1263                // Indicates any matched elements for this repeated field.
    +1264                boolean match = false;
    +1265                int newIndex = i;
    +1266                for (int j = 0; j < count2; j++) {
    +1267                    if (matchList2[j] != -1) {
    +1268                        continue;
    +1269                    }
    +1270                    newIndex = j;
    +1271                    match = isMatch(repeatedField, keyComparator, message1, message2, i, j, stack);
    +1272                    if (match) {
    +1273                        matchList1[i] = newIndex;
    +1274                        matchList2[newIndex] = i;
    +1275                        break;
    +1276                    }
    +1277                }
    +1278                success = success && match;
    +1279            }
    +1280        } else {
    +1281            // If this field should be treated as list, just label the match_list.
    +1282            for (int i = 0; (i < count1) && (i < count2); i++) {
    +1283                matchList1[i] = matchList2[i] = i;
    +1284            }
    +1285        }
    +1286        return success;
    +1287    }
    +1288
    +1289    private boolean isMatch(
    +1290            FieldDescriptor repeatedField,
    +1291            @Nullable MapKeyComparator keyComparator,
    +1292            Message message1,
    +1293            Message message2,
    +1294            int index1,
    +1295            int index2,
    +1296            List<SpecificField> stack) {
    +1297        boolean isSame;
    +1298
    +1299        if (keyComparator == null) {
    +1300            return compareFieldValueUsingParentFields(
    +1301                    message1, message2, repeatedField, index1, index2, null, stack);
    +1302        } else {
    +1303            Message m1 = (Message) message1.getRepeatedField(repeatedField, index1);
    +1304            Message m2 = (Message) message2.getRepeatedField(repeatedField, index2);
    +1305            stack.add(SpecificField.forRepeatedField(repeatedField, index1, index2));
    +1306            isSame = keyComparator.isMatch(this, m1, m2, stack);
    +1307        }
    +1308        pop(stack);
    +1309
    +1310        return isSame;
    +1311    }
    +1312
    +1313    private boolean isTreatedAsSubset(FieldDescriptor field) {
    +1314        return isTreatedAsSet(field) && (scope == Scope.PARTIAL);
    +1315    }
    +1316
    +1317    private boolean isTreatedAsSet(FieldDescriptor field) {
    +1318        if (repeatedFieldComparison == RepeatedFieldComparison.AS_SET) {
    +1319            return true;
    +1320        }
    +1321        return setFields.contains(field);
    +1322    }
    +1323
    +1324    // Returns an immutable list copy of the stack with an extra element appended.
    +1325    private static <T> ImmutableList<T> immutable(Iterable<T> stack, T extraElement) {
    +1326        return ImmutableList.<T>builder().addAll(stack).add(extraElement).build();
    +1327    }
    +1328
    +1329    // Pops the last result off of a list.
    +1330    private static void pop(List<?> stack) {
    +1331        stack.remove(stack.size() - 1);
    +1332    }
    +1333
    +1334    /**
    +1335     * A message difference reporter that writes a textual description of the differences to a
    +1336     * character stream.
    +1337     */
    +1338    public static final class StreamReporter implements Reporter {
    +1339        private final Appendable output;
    +1340        private final boolean reportModifiedAggregates;
    +1341
    +1342        /** Equivalent to {@code new StreamReporter(output, false)}. */
    +1343        public StreamReporter(Appendable output) {
    +1344            this(output, false);
    +1345        }
    +1346
    +1347        /**
    +1348         * Creates a new reporter.
    +1349         *
    +1350         * @param output where to write the output to
    +1351         * @param reportModifiedAggregates when set to true, the stream reporter will also output
    +1352         *     aggregates nodes (i.e. messages and groups) whose subfields have been modified. When
    +1353         *     false, will only report the individual subfields. Defaults to false.
    +1354         */
    +1355        public StreamReporter(Appendable output, boolean reportModifiedAggregates) {
    +1356            this.output = Preconditions.checkNotNull(output);
    +1357            this.reportModifiedAggregates = reportModifiedAggregates;
    +1358        }
    +1359
    +1360        /** I/O exceptions that occur during reporting are wrapped by this type. */
    +1361        public static final class StreamException extends RuntimeException {
    +1362            private StreamException(IOException e) {
    +1363                super(e);
    +1364            }
    +1365        }
    +1366
    +1367        @Override
    +1368        public void report(
    +1369                ReportType type,
    +1370                Message message1,
    +1371                Message message2,
    +1372                ImmutableList<SpecificField> fieldPath) {
    +1373            try {
    +1374                if ((type == ReportType.MODIFIED) && !reportModifiedAggregates) {
    +1375                    SpecificField specificField = Iterables.getLast(fieldPath);
    +1376                    if (specificField.getField() == null) {
    +1377                        if (specificField.getUnknown().getFieldType() == UnknownFieldType.GROUP) {
    +1378                            // Any changes to the subfields have already been printed.
    +1379                            return;
    +1380                        }
    +1381                    } else if (specificField.getField().getJavaType() == JavaType.MESSAGE) {
    +1382                        // Any changes to the subfields have already been printed.
    +1383                        return;
    +1384                    }
    +1385                }
    +1386                String tentativeNewline = "";
    +1387                if (fieldPath.size() == 1) {
    +1388                    tentativeNewline = "\n";
    +1389                }
    +1390                output.append(type.name().toLowerCase()).append(": ");
    +1391                switch (type) {
    +1392                    case ADDED:
    +1393                        appendPath(fieldPath, false);
    +1394                        output.append(": ");
    +1395                        appendValue(message2, fieldPath, false);
    +1396                        break;
    +1397                    case DELETED:
    +1398                        appendPath(fieldPath, true);
    +1399                        output.append(": ");
    +1400                        appendValue(message1, fieldPath, true);
    +1401                        break;
    +1402                    case IGNORED:
    +1403                        appendPath(fieldPath, false);
    +1404                        break;
    +1405                    case MOVED:
    +1406                        appendPath(fieldPath, true);
    +1407                        output.append(" -> ");
    +1408                        appendPath(fieldPath, false);
    +1409                        output.append(" : ");
    +1410                        appendValue(message1, fieldPath, true);
    +1411                        break;
    +1412                    case MODIFIED:
    +1413                        appendPath(fieldPath, true);
    +1414                        if (checkPathChanged(fieldPath)) {
    +1415                            output.append(" -> ");
    +1416                            appendPath(fieldPath, false);
    +1417                        }
    +1418                        output.append(":" + tentativeNewline);
    +1419                        appendValue(message1, fieldPath, true);
    +1420                        output.append(" -> " + tentativeNewline);
    +1421                        appendValue(message2, fieldPath, false);
    +1422                        break;
    +1423                    case MATCHED:
    +1424                        appendPath(fieldPath, true);
    +1425                        if (checkPathChanged(fieldPath)) {
    +1426                            output.append(" -> ");
    +1427                            appendPath(fieldPath, false);
    +1428                        }
    +1429                        output.append(" : ");
    +1430                        appendValue(message1, fieldPath, true);
    +1431                        break;
    +1432                    default:
    +1433                        throw new RuntimeException("Unknown ReportType");
    +1434                }
    +1435                output.append("\n" + tentativeNewline);
    +1436            } catch (IOException e) {
    +1437                throw new StreamException(e);
    +1438            }
    +1439        }
    +1440
    +1441        private boolean checkPathChanged(ImmutableList<SpecificField> fieldPath) {
    +1442            for (SpecificField specificField : fieldPath) {
    +1443                if (specificField.getIndex() != specificField.getNewIndex()) {
    +1444                    return true;
    +1445                }
    +1446            }
    +1447            return false;
    +1448        }
    +1449
    +1450        private void appendPath(ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1451                throws IOException {
    +1452            for (Iterator<SpecificField> it = fieldPath.iterator(); it.hasNext(); ) {
    +1453                SpecificField specificField = it.next();
    +1454                FieldDescriptor field = specificField.getField();
    +1455                if (field != null) {
    +1456                    if (field.isExtension()) {
    +1457                        output.append("(").append(field.getFullName()).append(")");
    +1458                    } else {
    +1459                        output.append(field.getName());
    +1460                    }
    +1461                } else {
    +1462                    output.append(String.valueOf(specificField.getUnknown().getFieldNumber()));
    +1463                }
    +1464                if (leftSide && (specificField.getIndex() >= 0)) {
    +1465                    output.append("[").append(String.valueOf(specificField.getIndex())).append("]");
    +1466                }
    +1467                if (!leftSide && (specificField.getNewIndex() >= 0)) {
    +1468                    output.append("[").append(String.valueOf(specificField.getNewIndex())).append("]");
    +1469                }
    +1470                if (it.hasNext()) {
    +1471                    output.append(".");
    +1472                }
    +1473            }
    +1474        }
    +1475
    +1476        private void appendValue(
    +1477                Message message, ImmutableList<SpecificField> fieldPath, boolean leftSide)
    +1478                throws IOException {
    +1479            SpecificField specificField = Iterables.getLast(fieldPath);
    +1480            FieldDescriptor field = specificField.getField();
    +1481            if (field != null) {
    +1482                int index = leftSide ? specificField.getIndex() : specificField.getNewIndex();
    +1483                Object value =
    +1484                        field.isRepeated() ? message.getRepeatedField(field, index) : message.getField(field);
    +1485                if (field.getJavaType() == JavaType.MESSAGE) {
    +1486                    output.append(wrapDebugString(TextFormat.shortDebugString((Message) value)));
    +1487                } else {
    +1488                    TextFormat.printFieldValue(field, value, output);
    +1489                }
    +1490            } else {
    +1491                UnknownFieldSet unknownFields = message.getUnknownFields();
    +1492                UnknownFieldSet.Field unknownField = null;
    +1493                UnknownDescriptor unknownDescriptor = null;
    +1494                for (SpecificField node : fieldPath) {
    +1495                    unknownDescriptor = node.getUnknown();
    +1496                    if (unknownDescriptor != null) {
    +1497                        unknownField = unknownFields.getField(unknownDescriptor.getFieldNumber());
    +1498                        if (unknownDescriptor.getFieldType() == UnknownFieldType.GROUP) {
    +1499                            unknownFields = unknownField.getGroupList().get(node.getIndex());
    +1500                        }
    +1501                    }
    +1502                }
    +1503                UnknownFieldType unknownType = unknownDescriptor.getFieldType();
    +1504                Object value = unknownType.getValues(unknownField).get(specificField.getIndex());
    +1505                int wireFormat = unknownType.getWireFormat();
    +1506                if (wireFormat == WireFormat.WIRETYPE_START_GROUP) {
    +1507                    output.append(wrapDebugString(TextFormat.shortDebugString((UnknownFieldSet) value)));
    +1508                } else {
    +1509                    TextFormat.printUnknownFieldValue(wireFormat, value, output);
    +1510                }
    +1511            }
    +1512        }
    +1513    }
    +1514
    +1515    // Wraps a message debug string in curly braces.
    +1516    private static String wrapDebugString(String debugString) {
    +1517        return debugString.isEmpty() ? "{ }" : ("{ " + debugString + " }");
    +1518    }
    +1519
    +1520    /** Basic implementation of FieldComparator. */
    +1521    @Immutable
    +1522    public static final class DefaultFieldComparator implements FieldComparator {
    +1523        private final FloatComparison floatComparison;
    +1524
    +1525        public DefaultFieldComparator(FloatComparison floatComparison) {
    +1526            this.floatComparison = Preconditions.checkNotNull(floatComparison);
    +1527        }
    +1528
    +1529        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-5f * 32. */
    +1530        @VisibleForTesting
    +1531        static boolean almostEquals(float x, float y) {
    +1532            return almostEquals(x, y, 1.0e-5f * 32);
    +1533        }
    +1534
    +1535        /** Port of C++ MathUtil::AlmostEquals, with STD_ERR of 1e-9d * 32. */
    +1536        @VisibleForTesting
    +1537        static boolean almostEquals(double x, double y) {
    +1538            return almostEquals(x, y, 1.0e-9d * 32);
    +1539        }
    +1540
    +1541        private static boolean almostEquals(double x, double y, double stdErr) {
    +1542            if (x == y) {
    +1543                return true;
    +1544            }
    +1545            // It's convenient in many ways to treat NaN as equal to NaN - it's also
    +1546            // what the exact comparison does, by virtue of using Double.equals instead
    +1547            // of ==.
    +1548            if (Double.isNaN(x) && Double.isNaN(y)) {
    +1549                return true;
    +1550            }
    +1551            if (Double.isInfinite(x) || Double.isInfinite(y)) {
    +1552                return false;
    +1553            }
    +1554            if ((Math.abs(x) <= stdErr) && (Math.abs(y) <= stdErr)) {
    +1555                return true;
    +1556            }
    +1557            double absDiff = (x > y) ? (x - y) : (y - x);
    +1558            return absDiff <= Math.max(stdErr, stdErr * Math.max(Math.abs(x), Math.abs(y)));
    +1559        }
    +1560
    +1561        @Override
    +1562        public ComparisonResult compare(
    +1563                Message message1,
    +1564                Message message2,
    +1565                FieldDescriptor field,
    +1566                int index1,
    +1567                int index2,
    +1568                ImmutableList<SpecificField> parentFields) {
    +1569            Object value1 =
    +1570                    field.isRepeated() ? message1.getRepeatedField(field, index1) : message1.getField(field);
    +1571            Object value2 =
    +1572                    field.isRepeated() ? message2.getRepeatedField(field, index2) : message2.getField(field);
    +1573
    +1574            switch (field.getJavaType()) {
    +1575                case MESSAGE:
    +1576                    return ComparisonResult.RECURSE;
    +1577                case INT:
    +1578                case LONG:
    +1579                case BOOLEAN:
    +1580                case STRING:
    +1581                case BYTE_STRING:
    +1582                case ENUM:
    +1583                    return ComparisonResult.of(value1.equals(value2));
    +1584                case FLOAT:
    +1585                    if (floatComparison == FloatComparison.EXACT) {
    +1586                        return ComparisonResult.of(value1.equals(value2));
    +1587                    } else {
    +1588                        return ComparisonResult.of(
    +1589                                almostEquals(((Number) value1).floatValue(), ((Number) value2).floatValue()));
    +1590                    }
    +1591                case DOUBLE:
    +1592                    if (floatComparison == FloatComparison.EXACT) {
    +1593                        return ComparisonResult.of(value1.equals(value2));
    +1594                    } else {
    +1595                        return ComparisonResult.of(
    +1596                                almostEquals(((Number) value1).doubleValue(), ((Number) value2).doubleValue()));
    +1597                    }
    +1598                default:
    +1599                    throw new IllegalArgumentException("Bad field type " + field.getJavaType());
    +1600            }
    +1601        }
    +1602    }
    +1603}
    +1604
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/docs/java/src-html/gust/util/Pair.html b/docs/java/src-html/gust/util/Pair.html new file mode 100644 index 000000000..e7ef71e14 --- /dev/null +++ b/docs/java/src-html/gust/util/Pair.html @@ -0,0 +1,129 @@ + + + +Source code + + + +
    + +
    + + diff --git a/docs/java/stylesheet.css b/docs/java/stylesheet.css new file mode 100644 index 000000000..fa246765c --- /dev/null +++ b/docs/java/stylesheet.css @@ -0,0 +1,906 @@ +/* + * Javadoc style sheet + */ + +@import url('resources/fonts/dejavu.css'); + +/* + * Styles for individual HTML elements. + * + * These are styles that are specific to individual HTML elements. Changing them affects the style of a particular + * HTML element throughout the page. + */ + +body { + background-color:#ffffff; + color:#353833; + font-family:'DejaVu Sans', Arial, Helvetica, sans-serif; + font-size:14px; + margin:0; + padding:0; + height:100%; + width:100%; +} +iframe { + margin:0; + padding:0; + height:100%; + width:100%; + overflow-y:scroll; + border:none; +} +a:link, a:visited { + text-decoration:none; + color:#4A6782; +} +a[href]:hover, a[href]:focus { + text-decoration:none; + color:#bb7a2a; +} +a[name] { + color:#353833; +} +a[name]:before, a[name]:target, a[id]:before, a[id]:target { + content:""; + display:inline-block; + position:relative; + padding-top:129px; + margin-top:-129px; +} +pre { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; +} +h1 { + font-size:20px; +} +h2 { + font-size:18px; +} +h3 { + font-size:16px; + font-style:italic; +} +h4 { + font-size:13px; +} +h5 { + font-size:12px; +} +h6 { + font-size:11px; +} +ul { + list-style-type:disc; +} +code, tt { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; + margin-top:8px; + line-height:1.4em; +} +dt code { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; +} +table tr td dt code { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + vertical-align:top; + padding-top:4px; +} +sup { + font-size:8px; +} + +/* + * Styles for HTML generated by javadoc. + * + * These are style classes that are used by the standard doclet to generate HTML documentation. + */ + +/* + * Styles for document title and copyright. + */ +.clear { + clear:both; + height:0px; + overflow:hidden; +} +.aboutLanguage { + float:right; + padding:0px 21px; + font-size:11px; + z-index:200; + margin-top:-9px; +} +.legalCopy { + margin-left:.5em; +} +.bar a, .bar a:link, .bar a:visited, .bar a:active { + color:#FFFFFF; + text-decoration:none; +} +.bar a:hover, .bar a:focus { + color:#bb7a2a; +} +.tab { + background-color:#0066FF; + color:#ffffff; + padding:8px; + width:5em; + font-weight:bold; +} +/* + * Styles for navigation bar. + */ +.bar { + background-color:#4D7A97; + color:#FFFFFF; + padding:.8em .5em .4em .8em; + height:auto;/*height:1.8em;*/ + font-size:11px; + margin:0; +} +.navPadding { + padding-top: 107px; +} +.fixedNav { + position:fixed; + width:100%; + z-index:999; + background-color:#ffffff; +} +.topNav { + background-color:#4D7A97; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + font-size:12px; +} +.bottomNav { + margin-top:10px; + background-color:#4D7A97; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + font-size:12px; +} +.subNav { + background-color:#dee3e9; + float:left; + width:100%; + overflow:hidden; + font-size:12px; +} +.subNav div { + clear:left; + float:left; + padding:0 0 5px 6px; + text-transform:uppercase; +} +ul.navList, ul.subNavList { + float:left; + margin:0 25px 0 0; + padding:0; +} +ul.navList li{ + list-style:none; + float:left; + padding: 5px 6px; + text-transform:uppercase; +} +ul.navListSearch { + float:right; + margin:0 0 0 0; + padding:0; +} +ul.navListSearch li { + list-style:none; + float:right; + padding: 5px 6px; + text-transform:uppercase; +} +ul.navListSearch li label { + position:relative; + right:-16px; +} +ul.subNavList li { + list-style:none; + float:left; +} +.topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited { + color:#FFFFFF; + text-decoration:none; + text-transform:uppercase; +} +.topNav a:hover, .bottomNav a:hover { + text-decoration:none; + color:#bb7a2a; + text-transform:uppercase; +} +.navBarCell1Rev { + background-color:#F8981D; + color:#253441; + margin: auto 5px; +} +.skipNav { + position:absolute; + top:auto; + left:-9999px; + overflow:hidden; +} +/* + * Styles for page header and footer. + */ +.header, .footer { + clear:both; + margin:0 20px; + padding:5px 0 0 0; +} +.indexNav { + position:relative; + font-size:12px; + background-color:#dee3e9; +} +.indexNav ul { + margin-top:0; + padding:5px; +} +.indexNav ul li { + display:inline; + list-style-type:none; + padding-right:10px; + text-transform:uppercase; +} +.indexNav h1 { + font-size:13px; +} +.title { + color:#2c4557; + margin:10px 0; +} +.subTitle { + margin:5px 0 0 0; +} +.header ul { + margin:0 0 15px 0; + padding:0; +} +.footer ul { + margin:20px 0 5px 0; +} +.header ul li, .footer ul li { + list-style:none; + font-size:13px; +} +/* + * Styles for headings. + */ +div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 { + background-color:#dee3e9; + border:1px solid #d0d9e0; + margin:0 0 6px -8px; + padding:7px 5px; +} +ul.blockList ul.blockList ul.blockList li.blockList h3 { + background-color:#dee3e9; + border:1px solid #d0d9e0; + margin:0 0 6px -8px; + padding:7px 5px; +} +ul.blockList ul.blockList li.blockList h3 { + padding:0; + margin:15px 0; +} +ul.blockList li.blockList h2 { + padding:0px 0 20px 0; +} +/* + * Styles for page layout containers. + */ +.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer, +.allClassesContainer, .allPackagesContainer { + clear:both; + padding:10px 20px; + position:relative; +} +.indexContainer { + margin:10px; + position:relative; + font-size:12px; +} +.indexContainer h2 { + font-size:13px; + padding:0 0 3px 0; +} +.indexContainer ul { + margin:0; + padding:0; +} +.indexContainer ul li { + list-style:none; + padding-top:2px; +} +.contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt { + font-size:12px; + font-weight:bold; + margin:10px 0 0 0; + color:#4E4E4E; +} +.contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd { + margin:5px 0 10px 0px; + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; +} +.serializedFormContainer dl.nameValue dt { + margin-left:1px; + font-size:1.1em; + display:inline; + font-weight:bold; +} +.serializedFormContainer dl.nameValue dd { + margin:0 0 0 1px; + font-size:1.1em; + display:inline; +} +/* + * Styles for lists. + */ +li.circle { + list-style:circle; +} +ul.horizontal li { + display:inline; + font-size:0.9em; +} +ul.inheritance { + margin:0; + padding:0; +} +ul.inheritance li { + display:inline; + list-style:none; +} +ul.inheritance li ul.inheritance { + margin-left:15px; + padding-left:15px; + padding-top:1px; +} +ul.blockList, ul.blockListLast { + margin:10px 0 10px 0; + padding:0; +} +ul.blockList li.blockList, ul.blockListLast li.blockList { + list-style:none; + margin-bottom:15px; + line-height:1.4; +} +ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList { + padding:0px 20px 5px 10px; + border:1px solid #ededed; + background-color:#f8f8f8; +} +ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList { + padding:0 0 5px 8px; + background-color:#ffffff; + border:none; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { + margin-left:0; + padding-left:0; + padding-bottom:15px; + border:none; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { + list-style:none; + border-bottom:none; + padding-bottom:0; +} +table tr td dl, table tr td dl dt, table tr td dl dd { + margin-top:0; + margin-bottom:1px; +} +/* + * Styles for tables. + */ +.overviewSummary, .memberSummary, .typeSummary, .useSummary, .constantsSummary, .deprecatedSummary, +.requiresSummary, .packagesSummary, .providesSummary, .usesSummary { + width:100%; + border-spacing:0; + border-left:1px solid #EEE; + border-right:1px solid #EEE; + border-bottom:1px solid #EEE; +} +.overviewSummary, .memberSummary, .requiresSummary, .packagesSummary, .providesSummary, .usesSummary { + padding:0px; +} +.overviewSummary caption, .memberSummary caption, .typeSummary caption, +.useSummary caption, .constantsSummary caption, .deprecatedSummary caption, +.requiresSummary caption, .packagesSummary caption, .providesSummary caption, .usesSummary caption { + position:relative; + text-align:left; + background-repeat:no-repeat; + color:#253441; + font-weight:bold; + clear:none; + overflow:hidden; + padding:0px; + padding-top:10px; + padding-left:1px; + margin:0px; + white-space:pre; +} +.overviewSummary caption a:link, .memberSummary caption a:link, .typeSummary caption a:link, +.constantsSummary caption a:link, .deprecatedSummary caption a:link, +.requiresSummary caption a:link, .packagesSummary caption a:link, .providesSummary caption a:link, +.usesSummary caption a:link, +.overviewSummary caption a:hover, .memberSummary caption a:hover, .typeSummary caption a:hover, +.constantsSummary caption a:hover, .deprecatedSummary caption a:hover, +.requiresSummary caption a:hover, .packagesSummary caption a:hover, .providesSummary caption a:hover, +.usesSummary caption a:hover, +.overviewSummary caption a:active, .memberSummary caption a:active, .typeSummary caption a:active, +.constantsSummary caption a:active, .deprecatedSummary caption a:active, +.requiresSummary caption a:active, .packagesSummary caption a:active, .providesSummary caption a:active, +.usesSummary caption a:active, +.overviewSummary caption a:visited, .memberSummary caption a:visited, .typeSummary caption a:visited, +.constantsSummary caption a:visited, .deprecatedSummary caption a:visited, +.requiresSummary caption a:visited, .packagesSummary caption a:visited, .providesSummary caption a:visited, +.usesSummary caption a:visited { + color:#FFFFFF; +} +.useSummary caption a:link, .useSummary caption a:hover, .useSummary caption a:active, +.useSummary caption a:visited { + color:#1f389c; +} +.overviewSummary caption span, .memberSummary caption span, .typeSummary caption span, +.useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span, +.requiresSummary caption span, .packagesSummary caption span, .providesSummary caption span, +.usesSummary caption span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + padding-bottom:7px; + display:inline-block; + float:left; + background-color:#F8981D; + border: none; + height:16px; +} +.memberSummary caption span.activeTableTab span, .packagesSummary caption span.activeTableTab span, +.overviewSummary caption span.activeTableTab span, .typeSummary caption span.activeTableTab span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + margin-right:3px; + display:inline-block; + float:left; + background-color:#F8981D; + height:16px; +} +.memberSummary caption span.tableTab span, .packagesSummary caption span.tableTab span, +.overviewSummary caption span.tableTab span, .typeSummary caption span.tableTab span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + margin-right:3px; + display:inline-block; + float:left; + background-color:#4D7A97; + height:16px; +} +.memberSummary caption span.tableTab, .memberSummary caption span.activeTableTab, +.packagesSummary caption span.tableTab, .packagesSummary caption span.activeTableTab, +.overviewSummary caption span.tableTab, .overviewSummary caption span.activeTableTab, +.typeSummary caption span.tableTab, .typeSummary caption span.activeTableTab { + padding-top:0px; + padding-left:0px; + padding-right:0px; + background-image:none; + float:none; + display:inline; +} +.overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd, +.useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd, +.requiresSummary .tabEnd, .packagesSummary .tabEnd, .providesSummary .tabEnd, .usesSummary .tabEnd { + display:none; + width:5px; + position:relative; + float:left; + background-color:#F8981D; +} +.memberSummary .activeTableTab .tabEnd, .packagesSummary .activeTableTab .tabEnd, +.overviewSummary .activeTableTab .tabEnd, .typeSummary .activeTableTab .tabEnd { + display:none; + width:5px; + margin-right:3px; + position:relative; + float:left; + background-color:#F8981D; +} +.memberSummary .tableTab .tabEnd, .packagesSummary .tableTab .tabEnd, +.overviewSummary .tableTab .tabEnd, .typeSummary .tableTab .tabEnd { + display:none; + width:5px; + margin-right:3px; + position:relative; + background-color:#4D7A97; + float:left; +} +.rowColor th, .altColor th { + font-weight:normal; +} +.overviewSummary td, .memberSummary td, .typeSummary td, +.useSummary td, .constantsSummary td, .deprecatedSummary td, +.requiresSummary td, .packagesSummary td, .providesSummary td, .usesSummary td { + text-align:left; + padding:0px 0px 12px 10px; +} +th.colFirst, th.colSecond, th.colLast, th.colConstructorName, th.colDeprecatedItemName, .useSummary th, +.constantsSummary th, .packagesSummary th, td.colFirst, td.colSecond, td.colLast, .useSummary td, +.constantsSummary td { + vertical-align:top; + padding-right:0px; + padding-top:8px; + padding-bottom:3px; +} +th.colFirst, th.colSecond, th.colLast, th.colConstructorName, th.colDeprecatedItemName, .constantsSummary th, +.packagesSummary th { + background:#dee3e9; + text-align:left; + padding:8px 3px 3px 7px; +} +td.colFirst, th.colFirst { + font-size:13px; +} +td.colSecond, th.colSecond, td.colLast, th.colConstructorName, th.colDeprecatedItemName, th.colLast { + font-size:13px; +} +.constantsSummary th, .packagesSummary th { + font-size:13px; +} +.providesSummary th.colFirst, .providesSummary th.colLast, .providesSummary td.colFirst, +.providesSummary td.colLast { + white-space:normal; + font-size:13px; +} +.overviewSummary td.colFirst, .overviewSummary th.colFirst, +.requiresSummary td.colFirst, .requiresSummary th.colFirst, +.packagesSummary td.colFirst, .packagesSummary td.colSecond, .packagesSummary th.colFirst, .packagesSummary th, +.usesSummary td.colFirst, .usesSummary th.colFirst, +.providesSummary td.colFirst, .providesSummary th.colFirst, +.memberSummary td.colFirst, .memberSummary th.colFirst, +.memberSummary td.colSecond, .memberSummary th.colSecond, .memberSummary th.colConstructorName, +.typeSummary td.colFirst, .typeSummary th.colFirst { + vertical-align:top; +} +.packagesSummary th.colLast, .packagesSummary td.colLast { + white-space:normal; +} +td.colFirst a:link, td.colFirst a:visited, +td.colSecond a:link, td.colSecond a:visited, +th.colFirst a:link, th.colFirst a:visited, +th.colSecond a:link, th.colSecond a:visited, +th.colConstructorName a:link, th.colConstructorName a:visited, +th.colDeprecatedItemName a:link, th.colDeprecatedItemName a:visited, +.constantValuesContainer td a:link, .constantValuesContainer td a:visited, +.allClassesContainer td a:link, .allClassesContainer td a:visited, +.allPackagesContainer td a:link, .allPackagesContainer td a:visited { + font-weight:bold; +} +.tableSubHeadingColor { + background-color:#EEEEFF; +} +.altColor, .altColor th { + background-color:#FFFFFF; +} +.rowColor, .rowColor th { + background-color:#EEEEEF; +} +/* + * Styles for contents. + */ +.description pre { + margin-top:0; +} +.deprecatedContent { + margin:0; + padding:10px 0; +} +.docSummary { + padding:0; +} +ul.blockList ul.blockList ul.blockList li.blockList h3 { + font-style:normal; +} +div.block { + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; +} +td.colLast div { + padding-top:0px; +} +td.colLast a { + padding-bottom:3px; +} +/* + * Styles for formatting effect. + */ +.sourceLineNo { + color:green; + padding:0 30px 0 0; +} +h1.hidden { + visibility:hidden; + overflow:hidden; + font-size:10px; +} +.block { + display:block; + margin:3px 10px 2px 0px; + color:#474747; +} +.deprecatedLabel, .descfrmTypeLabel, .implementationLabel, .memberNameLabel, .memberNameLink, +.moduleLabelInPackage, .moduleLabelInType, .overrideSpecifyLabel, .packageLabelInType, +.packageHierarchyLabel, .paramLabel, .returnLabel, .seeLabel, .simpleTagLabel, +.throwsLabel, .typeNameLabel, .typeNameLink, .searchTagLink { + font-weight:bold; +} +.deprecationComment, .emphasizedPhrase, .interfaceName { + font-style:italic; +} +.deprecationBlock { + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; + border-style:solid; + border-width:thin; + border-radius:10px; + padding:10px; + margin-bottom:10px; + margin-right:10px; + display:inline-block; +} +div.block div.deprecationComment, div.block div.block span.emphasizedPhrase, +div.block div.block span.interfaceName { + font-style:normal; +} +div.contentContainer ul.blockList li.blockList h2 { + padding-bottom:0px; +} +/* + * Styles for IFRAME. + */ +.mainContainer { + margin:0 auto; + padding:0; + height:100%; + width:100%; + position:fixed; + top:0; + left:0; +} +.leftContainer { + height:100%; + position:fixed; + width:320px; +} +.leftTop { + position:relative; + float:left; + width:315px; + top:0; + left:0; + height:30%; + border-right:6px solid #ccc; + border-bottom:6px solid #ccc; +} +.leftBottom { + position:relative; + float:left; + width:315px; + bottom:0; + left:0; + height:70%; + border-right:6px solid #ccc; + border-top:1px solid #000; +} +.rightContainer { + position:absolute; + left:320px; + top:0; + bottom:0; + height:100%; + right:0; + border-left:1px solid #000; +} +.rightIframe { + margin:0; + padding:0; + height:100%; + right:30px; + width:100%; + overflow:visible; + margin-bottom:30px; +} +/* + * Styles specific to HTML5 elements. + */ +main, nav, header, footer, section { + display:block; +} +/* + * Styles for javadoc search. + */ +.ui-autocomplete-category { + font-weight:bold; + font-size:15px; + padding:7px 0 7px 3px; + background-color:#4D7A97; + color:#FFFFFF; +} +.resultItem { + font-size:13px; +} +.ui-autocomplete { + max-height:85%; + max-width:65%; + overflow-y:scroll; + overflow-x:scroll; + white-space:nowrap; + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); +} +ul.ui-autocomplete { + position:fixed; + z-index:999999; +} +ul.ui-autocomplete li { + float:left; + clear:both; + width:100%; +} +.resultHighlight { + font-weight:bold; +} +#search { + background-image:url('resources/glass.png'); + background-size:13px; + background-repeat:no-repeat; + background-position:2px 3px; + padding-left:20px; + position:relative; + right:-18px; +} +#reset { + background-color: rgb(255,255,255); + background-image:url('resources/x.png'); + background-position:center; + background-repeat:no-repeat; + background-size:12px; + border:0 none; + width:16px; + height:17px; + position:relative; + left:-4px; + top:-4px; + font-size:0px; +} +.watermark { + color:#545454; +} +.searchTagDescResult { + font-style:italic; + font-size:11px; +} +.searchTagHolderResult { + font-style:italic; + font-size:12px; +} +.searchTagResult:before, .searchTagResult:target { + color:red; +} +.moduleGraph span { + display:none; + position:absolute; +} +.moduleGraph:hover span { + display:block; + margin: -100px 0 0 100px; + z-index: 1; +} +.methodSignature { + white-space:normal; +} + +/* + * Styles for user-provided tables. + * + * borderless: + * No borders, vertical margins, styled caption. + * This style is provided for use with existing doc comments. + * In general, borderless tables should not be used for layout purposes. + * + * plain: + * Plain borders around table and cells, vertical margins, styled caption. + * Best for small tables or for complex tables for tables with cells that span + * rows and columns, when the "striped" style does not work well. + * + * striped: + * Borders around the table and vertical borders between cells, striped rows, + * vertical margins, styled caption. + * Best for tables that have a header row, and a body containing a series of simple rows. + */ + +table.borderless, +table.plain, +table.striped { + margin-top: 10px; + margin-bottom: 10px; +} +table.borderless > caption, +table.plain > caption, +table.striped > caption { + font-weight: bold; + font-size: smaller; +} +table.borderless th, table.borderless td, +table.plain th, table.plain td, +table.striped th, table.striped td { + padding: 2px 5px; +} +table.borderless, +table.borderless > thead > tr > th, table.borderless > tbody > tr > th, table.borderless > tr > th, +table.borderless > thead > tr > td, table.borderless > tbody > tr > td, table.borderless > tr > td { + border: none; +} +table.borderless > thead > tr, table.borderless > tbody > tr, table.borderless > tr { + background-color: transparent; +} +table.plain { + border-collapse: collapse; + border: 1px solid black; +} +table.plain > thead > tr, table.plain > tbody tr, table.plain > tr { + background-color: transparent; +} +table.plain > thead > tr > th, table.plain > tbody > tr > th, table.plain > tr > th, +table.plain > thead > tr > td, table.plain > tbody > tr > td, table.plain > tr > td { + border: 1px solid black; +} +table.striped { + border-collapse: collapse; + border: 1px solid black; +} +table.striped > thead { + background-color: #E3E3E3; +} +table.striped > thead > tr > th, table.striped > thead > tr > td { + border: 1px solid black; +} +table.striped > tbody > tr:nth-child(even) { + background-color: #EEE +} +table.striped > tbody > tr:nth-child(odd) { + background-color: #FFF +} +table.striped > tbody > tr > th, table.striped > tbody > tr > td { + border-left: 1px solid black; + border-right: 1px solid black; +} +table.striped > tbody > tr > th { + font-weight: normal; +} diff --git a/docs/java/type-search-index.js b/docs/java/type-search-index.js new file mode 100644 index 000000000..640d31c0f --- /dev/null +++ b/docs/java/type-search-index.js @@ -0,0 +1 @@ +typeSearchIndex = [{"l":"All Classes","url":"allclasses-index.html"},{"p":"gust.backend","l":"AppController"},{"p":"gust.backend","l":"Application"},{"p":"gust.backend","l":"ApplicationBoot"},{"p":"gust.backend","l":"AssetConfiguration.AssetCachingConfiguration"},{"p":"gust.backend","l":"AssetConfiguration.AssetCompressionConfiguration"},{"p":"gust.backend","l":"AssetConfiguration"},{"p":"gust.backend","l":"AssetController"},{"p":"gust.backend.runtime","l":"AssetManager"},{"p":"gust.backend","l":"AssetController.AssetsAwareCspFilter"},{"p":"gust.backend","l":"AssetConfiguration.AssetVarianceConfiguration"},{"p":"gust.backend","l":"BaseController"},{"p":"gust.backend.driver.spanner","l":"SpannerGeneratedDDL.Builder"},{"p":"gust.backend.driver.spanner","l":"SpannerManager.Builder"},{"p":"gust.util","l":"MessageDifferencer.Builder"},{"p":"gust.backend.model","l":"CacheDriver"},{"p":"gust.backend.model","l":"CacheOptions"},{"p":"gust.backend","l":"DynamicServingConfiguration.ClientHintsConfiguration"},{"p":"gust.backend.model","l":"CollapsedMessage"},{"p":"gust.backend.model","l":"CollapsedMessageCodec"},{"p":"gust.backend.model","l":"CollapsedMessageSerializer"},{"p":"gust.util","l":"MessageDifferencer.FieldComparator.ComparisonResult"},{"p":"gust.backend.runtime","l":"ReactiveFuture.CompletableFuturePublisher"},{"p":"gust.backend.runtime","l":"ReactiveFuture.CompletableFuturePublisher.CompletableFutureSubscription"},{"p":"gust.backend.driver.spanner","l":"SpannerManager.ConfiguredSpannerManager"},{"p":"gust.backend","l":"AssetConfiguration.ContentDistributionConfiguration"},{"p":"gust","l":"Core"},{"p":"gust.backend","l":"AssetConfiguration.CrossOriginResourceConfiguration"},{"p":"gust.backend.model","l":"DatabaseAdapter"},{"p":"gust.backend.model","l":"DatabaseDriver"},{"p":"gust.backend.model","l":"DatabaseManager"},{"p":"gust.util","l":"MessageDifferencer.DefaultFieldComparator"},{"p":"gust.backend.driver.spanner","l":"SpannerDriverSettings.DefaultSettings"},{"p":"gust.backend.model","l":"DeleteOptions"},{"p":"gust.backend.model","l":"ModelDeserializer.DeserializationError"},{"p":"gust.backend","l":"DynamicServingConfiguration.DynamicETagsConfiguration"},{"p":"gust.backend","l":"DynamicServingConfiguration"},{"p":"gust.backend","l":"DynamicServingConfiguration.DynamicVarianceConfiguration"},{"p":"gust.backend.model","l":"EncodedModel"},{"p":"gust.backend.model","l":"EncodingMode"},{"p":"gust.backend.model","l":"ModelSerializer.EnumSerializeMode"},{"p":"gust.backend.model","l":"CacheOptions.EvictionMode"},{"p":"gust.backend","l":"DynamicServingConfiguration.FeaturePolicyConfiguration"},{"p":"gust.backend.model","l":"FetchOptions"},{"p":"gust.util","l":"MessageDifferencer.FieldComparator"},{"p":"gust.backend.model","l":"ModelMetadata.FieldContainer"},{"p":"gust.backend.model","l":"ModelMetadata.FieldPointer"},{"p":"gust.backend.driver.firestore","l":"FirestoreAdapter"},{"p":"gust.backend.driver.firestore","l":"FirestoreDriver"},{"p":"gust.backend.driver.firestore","l":"FirestoreManager"},{"p":"gust.backend.driver.firestore","l":"FirestoreTransportConfig"},{"p":"gust.util","l":"MessageDifferencer.FloatComparison"},{"p":"gust.backend.transport","l":"GoogleAPIChannel"},{"p":"gust.backend.transport","l":"GoogleService"},{"p":"gust.backend.transport","l":"GoogleTransportConfig"},{"p":"gust.backend.transport","l":"GoogleTransportManager"},{"p":"gust.backend.transport","l":"GrpcTransportConfig"},{"p":"gust.backend.transport","l":"GrpcTransportCredentials"},{"p":"gust.util","l":"Hex"},{"p":"gust.util","l":"MessageDifferencer.IgnoreCriteria"},{"p":"gust.backend.driver.inmemory","l":"InMemoryAdapter"},{"p":"gust.backend.driver.inmemory","l":"InMemoryCache"},{"p":"gust.backend.driver.inmemory","l":"InMemoryDriver"},{"p":"gust.backend.driver.inmemory","l":"InMemoryManager"},{"p":"gust.util","l":"InstantFactory"},{"p":"gust.backend.model","l":"ModelSerializer.InstantSerializeMode"},{"p":"gust.backend.driver.spanner","l":"SpannerGeneratedDDL.InterleaveTarget"},{"p":"gust.backend.model","l":"PersistenceDriver.Internals"},{"p":"gust.backend.model","l":"InvalidModelType"},{"p":"gust.backend.annotations","l":"Js"},{"p":"gust.backend.runtime","l":"ReactiveFuture.ListenableFuturePublisher"},{"p":"gust.backend.runtime","l":"ReactiveFuture.ListenableFuturePublisher.ListenableFutureSubscription"},{"p":"gust.backend.runtime","l":"Logging"},{"p":"gust.backend.runtime","l":"AssetManager.ManagedAsset"},{"p":"gust.backend.runtime","l":"AssetManager.ManagedAssetContent"},{"p":"gust.util","l":"MessageDifferencer.MapKeyComparator"},{"p":"gust.backend.model","l":"FetchOptions.MaskMode"},{"p":"gust.backend.annotations","l":"Style.MediaType"},{"p":"gust.util","l":"MessageDifferencer"},{"p":"gust.util","l":"MessageDifferencer.MessageFieldComparison"},{"p":"gust.backend.model","l":"MissingAnnotatedField"},{"p":"gust.backend.model","l":"ModelAdapter"},{"p":"gust.backend.model","l":"ModelCodec"},{"p":"gust.backend.model","l":"ModelDeflateException"},{"p":"gust.backend.model","l":"ModelDeserializer"},{"p":"gust.backend.model","l":"ModelInflateException"},{"p":"gust.backend.model","l":"ModelMetadata"},{"p":"gust.backend.model","l":"ModelSerializer"},{"p":"gust.backend.model","l":"ModelWriteConflict"},{"p":"gust.backend.model","l":"ModelWriteFailure"},{"p":"gust.backend.runtime","l":"AssetManager.ModuleType"},{"p":"gust.backend.model","l":"CollapsedMessage.Operation"},{"p":"gust.backend.model","l":"OperationOptions"},{"p":"gust.backend.annotations","l":"Page"},{"p":"gust.backend","l":"PageContext"},{"p":"gust.backend","l":"PageContextManager"},{"p":"gust.backend","l":"PageRender"},{"p":"gust.util","l":"Pair"},{"p":"gust.backend.model","l":"PersistenceDriver"},{"p":"gust.backend.model","l":"PersistenceException"},{"p":"gust.backend.model","l":"PersistenceFailure"},{"p":"gust.backend.model","l":"PersistenceManager"},{"p":"gust.backend.model","l":"PersistenceOperationFailed"},{"p":"gust.backend.transport","l":"PooledTransportConfig"},{"p":"gust.backend.driver.spanner","l":"SpannerGeneratedDDL.PropagatedAction"},{"p":"gust.backend.model","l":"ProtoModelCodec"},{"p":"gust.backend.runtime","l":"ReactiveFuture.PublisherListenableFuture"},{"p":"gust.backend.runtime","l":"ReactiveFuture"},{"p":"gust.backend.driver.spanner","l":"SpannerGeneratedDDL.RenderableStatement"},{"p":"gust.util","l":"MessageDifferencer.RepeatedFieldComparison"},{"p":"gust.util","l":"MessageDifferencer.Reporter"},{"p":"gust.util","l":"MessageDifferencer.ReportType"},{"p":"gust.util","l":"MessageDifferencer.Scope"},{"p":"gust.backend.model","l":"ModelSerializer.SerializationError"},{"p":"gust.backend.model","l":"SerializedModel"},{"p":"gust.backend.driver.spanner","l":"SpannerGeneratedDDL.SortDirection"},{"p":"gust.backend.driver.spanner","l":"SpannerAdapter"},{"p":"gust.backend.driver.spanner","l":"SpannerCodec"},{"p":"gust.backend.driver.spanner","l":"SpannerDriver"},{"p":"gust.backend.driver.spanner","l":"SpannerDriverSettings"},{"p":"gust.backend.driver.spanner","l":"SpannerGeneratedDDL"},{"p":"gust.backend.driver.spanner","l":"SpannerManager"},{"p":"gust.backend.driver.spanner","l":"SpannerMutationSerializer"},{"p":"gust.backend.driver.spanner","l":"SpannerStructDeserializer"},{"p":"gust.backend.driver.spanner","l":"SpannerTemporalConverter"},{"p":"gust.backend.driver.spanner","l":"SpannerTransportConfig"},{"p":"gust.backend.driver.spanner","l":"SpannerUtil"},{"p":"gust.util","l":"MessageDifferencer.SpecificField"},{"p":"gust.util","l":"MessageDifferencer.StreamReporter.StreamException"},{"p":"gust.util","l":"MessageDifferencer.StreamReporter"},{"p":"gust.backend.annotations","l":"Style"},{"p":"gust.backend.driver.spanner","l":"SpannerGeneratedDDL.TableConstraint"},{"p":"gust.backend","l":"TemplateProvider"},{"p":"gust.backend.model","l":"Transaction"},{"p":"gust.backend.transport","l":"TransportConfig"},{"p":"gust.backend.transport","l":"TransportCredentials"},{"p":"gust.backend.transport","l":"TransportException"},{"p":"gust.backend.transport","l":"TransportManager"},{"p":"gust.util","l":"MessageDifferencer.UnknownDescriptor"},{"p":"gust.util","l":"MessageDifferencer.UnknownFieldType"},{"p":"gust.backend.model","l":"UpdateOptions"},{"p":"gust.backend.model","l":"ModelSerializer.WriteDisposition"},{"p":"gust.backend.model","l":"WriteOptions.WriteDisposition"},{"p":"gust.backend.model","l":"WriteOptions"},{"p":"gust.backend.model","l":"WriteProxy"},{"p":"gust.backend","l":"DynamicServingConfiguration.XSSProtectionConfiguration"}] \ No newline at end of file diff --git a/docs/java/type-search-index.zip b/docs/java/type-search-index.zip new file mode 100644 index 0000000000000000000000000000000000000000..f586d375a024bf09f7864961e37dbef9cdf8592e GIT binary patch literal 1484 zcmZvcc{mdc9LGmt*fK}trV!=Gks}c=!#24lh2)&8!VGies>rom&9Nn!SLT|Kb6S$4 zkn4>co3mB!a*ST`>aVx=dEU?OkKgxszQ5=9=f?(dLn4Au zDj1Y|u&1wzUw{`XT-6^F7*ORn88EL6*?;tU!1<2Rw1jIIz3TzL-tG8ezBH|{2ZJR> zc8sRfpHWFIx_D(flUB84rGC*YjGku6&mZdi*{%vm*_Z-uZ&2RbTo{?IsbK1G=|_IA zgkhFW6eK&AwwEWg6Xq&Lpw=!+df_tQFx$SIdM9qSNK>2jG{6EV7* z^=CB%w*LSXczsnrFkqN;>^`UGdb?zc;;`(7d`k{qdPbnuJ8Q+fkAB5vt;dq43D!g^ zH`i!IPb<o5$Dr>M{mCKYVNQ78;X^@ABy2 zf%igBy)=s@%@EhZir83R0==t%afdxwX>TZ*q-P>{GTRd4u#1(BcE}oGO?timq`EtAT)gl?4sK^+zwoY}kJop-GHUJML>-u2=lq(m zbHnM3k*3%BW5#OElHw{}i6pAVaa*ygGjEc&4;DJSWP3XzERbkN)2z-+pVeFuc+IH5 zHK;33R1LDE3h!3e*#0H;B(`{Vw1=$6yv3V~N5f#7Y@258+>&3(>}Wd317V*w1QNQZ>x(C&F`*Rk^r1Um1h7gFFW9F{s2T&~2->CBQHC!h9p6Djn(1ZG70ujQp#4MG`pHRLoHB+=nwr$8- z^0(j);;Wx~<{WpAx-Qeb$byUYmbiEvFkcNR(`UF%FB|?qPEn-j?eBM zTLlfu4H`-d)3%<_vVX+hFlgnS+MHESZu9O83`!vrayi6inRMyaF^2UD%&0bzShrNf zU9IJh+#;poU%7f*aD10;%w@HNGP$87qoA{EVG9!+XuFJQ6KpEzH0O-B0c|gs_P`lh z$VpDwM>1 zZ#IX!qT9{anHztk1+8L-2XsLtbBX8#Ysxz-A-R*fYBuc_>uw2;30#=%G6Lk{)KDNp z8~#+8ymktEM7)Zp0ZZ~TtH(Pyq-P>)Y75W$(K5Q0S}?3oVhcwJu7@uZ`c0HacY{WW zODi|J+V!YPOwdszTMH>vxQH!MTEUf)=0SWesEo%sbKalBvp{crEJQvTsRKdxSUYVK zZ7eive3?mLh{vH^FkR)L7Uuq;`QzWrlqg&pa^kg40WKDj(@#7dY*VK5=Y!bR|d=To-?C0 zFhU3#G1(d^5`IW(lenP-wFgUniLk6&teH3+EYOr<@6_?Pof}1Z_s&f5s(tz{ao!vJ z?7YJ9VuK(C*J-%t#Tbz5B1DyTV2K=qe?V1XclTMbHV7_YG1Zfz$^igq$pQd22sU;h hz`xG@z5C_d|MLF^wn1=k{<>p39Mi)ceE1sx_y<@@x5WSe literal 0 HcmV?d00001 diff --git a/gust/core/datamodel.proto b/gust/core/datamodel.proto index 5a3c816b9..3d68c0409 100644 --- a/gust/core/datamodel.proto +++ b/gust/core/datamodel.proto @@ -378,6 +378,37 @@ message TableFieldOptions { } +// Extended options for use with Cloud Spanner, which may be affixed to an individual model field. +message SpannerFieldOptions { + // Override column name in Spanner only. + string column = 1; + + // Whether to ignore this column in Spanner specifically. + bool ignore = 2; + + // Override the field type in Spanner only. + SpannerOptions.SpannerType type = 3; + + // Override the field to store STRUCT objects as JSON. + bool json = 4; + + // Length of this field in the database, when expressed as a string. + uint32 size = 5; + + // Specifies this column as `NONNULL`, in which case you cannot store `NULL` values in this field. + bool nonnull = 6; + + // Expression value for this field. Converts this field into a calculated value. + string expression = 7; + + // Specifies the `STORED` option for this field. Only applicable if an `expression` is present. + bool stored = 8; + + // Marks a field as a member of a Spanner row's primary key. + bool primary_key = 9; +} + + // Specifies mappings for an arbitrary protobuf message object. message ObjectMapping { // Maps an enumeration instance to this object. Enumeration membership is contextual. @@ -423,6 +454,9 @@ extend google.protobuf.FieldOptions { // Unique ID or path assigned to this field in a universally specified data model. string id = 7004; + + // Specifies extended options related specifically to use with Cloud Spanner. + SpannerFieldOptions spanner = 7005; } diff --git a/images/base/alpine/Dockerfile b/images/base/alpine/Dockerfile index a526de8d8..8a22011b2 100644 --- a/images/base/alpine/Dockerfile +++ b/images/base/alpine/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.11 +FROM alpine:3.14.0@sha256:8d99168167baa6a6a0d7851b9684625df9c1455116a9601835c2127df2aaa2f5 ENV LANG=C.UTF-8 diff --git a/images/base/node/Dockerfile b/images/base/node/Dockerfile index f2291626c..57d66222e 100644 --- a/images/base/node/Dockerfile +++ b/images/base/node/Dockerfile @@ -1,4 +1,4 @@ -FROM node:13-alpine +FROM node:lts-alpine3.14@sha256:e64db06f244b6248e33820eafb5a26ab291bc342ddc50745999de1f951f40c8b ENV ELIDE=1 diff --git a/java/gust/backend/annotations/BUILD.bazel b/java/gust/backend/annotations/BUILD.bazel index f7591e904..94413f83e 100644 --- a/java/gust/backend/annotations/BUILD.bazel +++ b/java/gust/backend/annotations/BUILD.bazel @@ -36,6 +36,11 @@ _COMMON_DEPS = [ ] +java_library( + name = "package-info", + srcs = ["package-info.java"], +) + java_library( name = "Js", srcs = ["Js.java"], @@ -67,6 +72,7 @@ java_library( ":Js", ":Page", ":Style", + ":package-info", ] ) diff --git a/java/gust/backend/annotations/package-info.java b/java/gust/backend/annotations/package-info.java new file mode 100644 index 000000000..b531362dc --- /dev/null +++ b/java/gust/backend/annotations/package-info.java @@ -0,0 +1,3 @@ + +/** Provides annotations for backend Java applications. */ +package gust.backend.annotations; diff --git a/java/gust/backend/driver/BUILD.bazel b/java/gust/backend/driver/BUILD.bazel index a6a66612e..de55a80fc 100644 --- a/java/gust/backend/driver/BUILD.bazel +++ b/java/gust/backend/driver/BUILD.bazel @@ -52,5 +52,6 @@ filegroup( srcs = glob(["*.java"]) + [ "//java/gust/backend/driver/inmemory:sources", "//java/gust/backend/driver/firestore:sources", + "//java/gust/backend/driver/spanner:sources", ] ) diff --git a/java/gust/backend/driver/firestore/BUILD.bazel b/java/gust/backend/driver/firestore/BUILD.bazel index d18af50d0..62e7da4f8 100644 --- a/java/gust/backend/driver/firestore/BUILD.bazel +++ b/java/gust/backend/driver/firestore/BUILD.bazel @@ -116,6 +116,7 @@ java_library( srcs = ["FirestoreManager.java"], deps = [ ":FirestoreDriver", + ":FirestoreAdapter", "//java/gust/backend/model:DatabaseManager", ] + _COMMON_DEPS, ) diff --git a/java/gust/backend/driver/firestore/FirestoreManager.java b/java/gust/backend/driver/firestore/FirestoreManager.java index 43e464638..c6b25b836 100644 --- a/java/gust/backend/driver/firestore/FirestoreManager.java +++ b/java/gust/backend/driver/firestore/FirestoreManager.java @@ -19,6 +19,6 @@ /** * */ -@Factory -public final class FirestoreManager implements DatabaseManager { +@SuppressWarnings("rawtypes") +public final class FirestoreManager implements DatabaseManager { } diff --git a/java/gust/backend/driver/inmemory/InMemoryCache.java b/java/gust/backend/driver/inmemory/InMemoryCache.java index dd4f9d38b..c6363c0ea 100644 --- a/java/gust/backend/driver/inmemory/InMemoryCache.java +++ b/java/gust/backend/driver/inmemory/InMemoryCache.java @@ -76,7 +76,7 @@ private InMemoryCaching() { * @param Generic model type managed by this cache. * @return Instance of the acquired cache engine. */ - static @Nonnull InMemoryCache acquire() { + public static @Nonnull InMemoryCache acquire() { return new InMemoryCache<>(); } diff --git a/java/gust/backend/driver/inmemory/InMemoryManager.java b/java/gust/backend/driver/inmemory/InMemoryManager.java index 5e7a3107f..671b4843a 100644 --- a/java/gust/backend/driver/inmemory/InMemoryManager.java +++ b/java/gust/backend/driver/inmemory/InMemoryManager.java @@ -18,5 +18,5 @@ /** * */ -public final class InMemoryManager implements PersistenceManager { +public final class InMemoryManager implements PersistenceManager { } diff --git a/java/gust/backend/driver/spanner/BUILD.bazel b/java/gust/backend/driver/spanner/BUILD.bazel new file mode 100644 index 000000000..5498526e8 --- /dev/null +++ b/java/gust/backend/driver/spanner/BUILD.bazel @@ -0,0 +1,231 @@ +## +# Copyright © 2020, The Gust Framework Authors. All rights reserved. +# +# The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, +# are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of +# this code in object or source form requires and implies consent and agreement to that license in principle and +# practice. Source or object code not listing this header, or unless specified otherwise, remain the property of +# Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to +# Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected +# by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, +# is strictly forbidden except in adherence with assigned license requirements. +## + +package( + default_visibility = ["//visibility:public"], +) + +load( + "//defs/toolchain/java:rules.bzl", + java_library = "jdk_library", +) +load( + "//defs/toolchain:deps.bzl", + "javaproto", + "maven", +) + +_COMMON_DEPS = [ + "@javax_annotation_api", + "@com_google_code_findbugs_jsr305", + "@com_google_protobuf//:protobuf_java", + "//java/gust/backend/runtime:runtime", + "//java/gust/backend/model:ModelMetadata", + maven("org.slf4j:slf4j-api"), + maven("com.google.cloud:google-cloud-spanner"), + maven("com.google.cloud:google-cloud-core"), + maven("com.google.guava:guava"), + maven("io.grpc:grpc-alts"), + maven("io.micronaut:micronaut-inject"), + maven("io.micronaut:micronaut-runtime"), + maven("io.micronaut:micronaut-context"), + maven("org.reactivestreams:reactive-streams"), + maven("javax.validation:validation-api"), + maven("com.google.api.grpc:proto-google-common-protos"), +] + +java_library( + name = "package-info", + srcs = ["package-info.java"], +) + +java_library( + name = "SpannerAdapter", + srcs = ["SpannerAdapter.java"], + deps = [ + ":SpannerDriverSettings", + ":SpannerDriver", + ":SpannerCodec", + ":SpannerStructDeserializer", + ":SpannerMutationSerializer", + "//java/gust/backend/model:CacheDriver", + "//java/gust/backend/model:DatabaseAdapter", + "//java/gust/backend/model:DatabaseDriver", + "//java/gust/backend/model:ModelCodec", + "//java/gust/backend/model:PersistenceDriver", + "//java/gust/backend/transport:GoogleService", + "//java/gust/backend/transport:GoogleTransportConfig", + "//java/gust/backend/transport:GoogleTransportManager", + ] + _COMMON_DEPS, +) + +java_library( + name = "SpannerCodec", + srcs = ["SpannerCodec.java"], + deps = [ + ":SpannerDriverSettings", + ":SpannerMutationSerializer", + ":SpannerStructDeserializer", + "//java/gust/backend/model:ModelSerializer", + "//java/gust/backend/model:ModelDeserializer", + "//java/gust/backend/model:ModelCodec", + ] + _COMMON_DEPS, +) + +java_library( + name = "SpannerDriver", + srcs = ["SpannerDriver.java"], + exports = [ + maven("io.grpc:grpc-api"), + maven("io.grpc:grpc-core"), + maven("io.grpc:grpc-auth"), + maven("io.grpc:grpc-stub"), + maven("io.grpc:grpc-protobuf"), + maven("com.google.api:gax"), + maven("com.google.api:gax-grpc"), + maven("com.google.cloud:google-cloud-core"), + maven("com.google.cloud:google-cloud-core-grpc"), + ], + deps = [ + ":SpannerCodec", + ":SpannerDriverSettings", + ":SpannerMutationSerializer", + ":SpannerStructDeserializer", + ":SpannerUtil", + "//java/gust/backend/model:DatabaseDriver", + "//java/gust/backend/model:DeleteOptions", + "//java/gust/backend/model:FetchOptions", + "//java/gust/backend/model:ModelCodec", + "//java/gust/backend/model:OperationOptions", + "//java/gust/backend/model:SerializedModel", + "//java/gust/backend/model:UpdateOptions", + "//java/gust/backend/model:WriteOptions", + "//java/gust/backend/model:WriteProxy", + "//java/gust/backend/transport:GoogleService", + "//java/gust/backend/transport:GoogleTransportManager", + javaproto("//gust/core:datamodel"), + maven("io.grpc:grpc-api"), + maven("com.google.api:gax"), + maven("com.google.api:gax-grpc"), + maven("com.google.api:api-common"), + maven("com.google.cloud:google-cloud-core"), + maven("com.google.cloud:google-cloud-core-grpc"), + "@com_google_protobuf//java/util", + ] + _COMMON_DEPS, +) + +java_library( + name = "SpannerDriverSettings", + srcs = ["SpannerDriverSettings.java"], + deps = [ + ":SpannerTransportConfig", + ] + _COMMON_DEPS, +) + +java_library( + name = "SpannerGeneratedDDL", + srcs = ["SpannerGeneratedDDL.java"], + deps = [ + ":SpannerDriverSettings", + ":SpannerUtil", + javaproto("//gust/core:datamodel"), + ] + _COMMON_DEPS, +) + +java_library( + name = "SpannerManager", + srcs = ["SpannerManager.java"], + deps = [ + ":SpannerAdapter", + ":SpannerDriver", + ":SpannerDriverSettings", + "//java/gust/backend/model:CacheDriver", + "//java/gust/backend/model:DatabaseManager", + ] + _COMMON_DEPS, +) + +java_library( + name = "SpannerMutationSerializer", + srcs = ["SpannerMutationSerializer.java"], + deps = [ + ":SpannerUtil", + ":SpannerDriverSettings", + ":SpannerTemporalConverter", + "@com_google_protobuf//java/util", + "//java/gust/backend/model:ModelDeflateException", + "//java/gust/backend/model:ModelSerializer", + javaproto("//gust/core:datamodel"), + ] + _COMMON_DEPS, +) + +java_library( + name = "SpannerStructDeserializer", + srcs = ["SpannerStructDeserializer.java"], + deps = [ + ":SpannerUtil", + ":SpannerDriverSettings", + ":SpannerTemporalConverter", + "@com_google_protobuf//java/util", + "//java/gust/backend/model:ModelDeserializer", + "//java/gust/backend/model:ModelInflateException", + javaproto("//gust/core:datamodel"), + ] + _COMMON_DEPS, +) + +java_library( + name = "SpannerTemporalConverter", + srcs = ["SpannerTemporalConverter.java"], + deps = [ + # None yet. + ] + _COMMON_DEPS, +) + +java_library( + name = "SpannerTransportConfig", + srcs = ["SpannerTransportConfig.java"], + deps = [ + "//java/gust/backend/transport:GoogleTransportManager", + "//java/gust/backend/transport:GrpcTransportConfig", + ] + _COMMON_DEPS, +) + +java_library( + name = "SpannerUtil", + srcs = ["SpannerUtil.java"], + deps = [ + ":SpannerDriverSettings", + "//java/gust/util:Pair", + javaproto("//gust/core:datamodel"), + ] + _COMMON_DEPS, +) + +java_library( + name = "spanner", + exports = [ + ":SpannerAdapter", + ":SpannerCodec", + ":SpannerDriver", + ":SpannerDriverSettings", + ":SpannerManager", + ":SpannerMutationSerializer", + ":SpannerStructDeserializer", + ":SpannerTransportConfig", + ":SpannerUtil", + ":package-info", + ], +) + +filegroup( + name = "sources", + srcs = glob(["*.java"]), +) diff --git a/java/gust/backend/driver/spanner/SpannerAdapter.java b/java/gust/backend/driver/spanner/SpannerAdapter.java new file mode 100644 index 000000000..f4462ae16 --- /dev/null +++ b/java/gust/backend/driver/spanner/SpannerAdapter.java @@ -0,0 +1,452 @@ +/* + * Copyright © 2020, The Gust Framework Authors. All rights reserved. + * + * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, + * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of + * this code in object or source form requires and implies consent and agreement to that license in principle and + * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of + * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to + * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected + * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, + * is strictly forbidden except in adherence with assigned license requirements. + */ +package gust.backend.driver.spanner; + +import com.google.api.gax.core.CredentialsProvider; +import com.google.api.gax.rpc.TransportChannelProvider; +import com.google.cloud.grpc.GrpcTransportOptions; +import com.google.cloud.spanner.*; +import com.google.cloud.spanner.v1.stub.SpannerStubSettings; +import com.google.common.util.concurrent.ListeningScheduledExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.protobuf.Message; +import gust.backend.model.*; +import gust.backend.transport.GoogleAPIChannel; +import gust.backend.transport.GoogleService; +import io.micronaut.context.annotation.Context; +import io.micronaut.context.annotation.Factory; +import io.micronaut.runtime.context.scope.Refreshable; + +import javax.annotation.Nonnull; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.ThreadSafe; +import java.io.Closeable; +import java.util.Optional; +import java.util.concurrent.Executors; + + +/** + * Implementation of a {@link DatabaseAdapter} backed by Google Cloud Spanner, capable of marshalling arbitrary + * schema-generated {@link Message} objects back and forth from structured columnar storage. + * + *

    This adapter is implemented by the {@link SpannerDriver} and related codec classes ({@link SpannerCodec}, + * {@link SpannerStructDeserializer} and {@link SpannerMutationSerializer}). Background execution and gRPC channels are + * hooked into driver acquisition and may be managed by the developer, or by the framework automatically. See below for + * a summary of application-level Spanner features supported by this engine:

    + * + *

    Caching may be facilitated by any compliant model {@link CacheDriver}.

    + * + *

    Transactions are supported under the hood, and can be controlled via Spanner-extended operation options + * interfaces (such as {@link SpannerDriver.SpannerWriteOptions} and {@link SpannerDriver.SpannerFetchOptions}). + * Invoking code may either opt-in to transactional protection automatically, or drive external transactions with this + * adapter/driver by specifying an input transaction for a given operation.

    + * + *

    Collections are supported by this engine, with additional support for nested models encoded via JSON. In + * cases where model JSON is involved, {@link com.google.protobuf.util.JsonFormat} is used to produce and consume + * compliant Proto-JSON.

    + * + * @param Typed {@link Message} which implements a concrete model key structure, as defined and annotated by the + * core Gust annotations. + * @param Typed {@link Message} which implements a concrete model object structure, as defined and annotated by + * the core Gust annotations. + * @see SpannerManager Adapter instance manager and factory. + * @see SpannerDriverSettings Driver-level settings specific to Spanner. + * @see gust.backend.driver.firestore.FirestoreAdapter Similar adapter implementation, built on top of Cloud Firestore, + * which itself is implemented on top of Cloud Spanner. + */ +@Immutable @ThreadSafe +@SuppressWarnings({"OptionalUsedAsFieldOrParameterType", "UnstableApiUsage"}) +public final class SpannerAdapter + implements DatabaseAdapter, Closeable, AutoCloseable { + /** Spanner database driver. */ + private final @Nonnull SpannerDriver driver; + + /** Serializer and deserializer for this model. */ + private final @Nonnull ModelCodec codec; + + /** Cache to use for model interactions through this adapter (optional). */ + private final @Nonnull Optional> cache; + + /** + * Private constructor for a model-specialized Spanner adapter instance. + * + * @param driver Driver instance created to power this adapter. + * @param codec Codec to use when marshalling objects with this adapter. + * @param cache Optionally, cache to employ when interacting with the underlying driver. + */ + private SpannerAdapter(@Nonnull SpannerDriver driver, + @Nonnull ModelCodec codec, + @Nonnull Optional> cache) { + this.driver = driver; + this.codec = codec; + this.cache = cache; + } + + /** + * Create or resolve a {@link SpannerAdapter} for the pre-fabricated {@link SpannerDriver}. + * + *

    Note: this method has no way to specify a cache. See below for alternatives.

    + * + * @see #forModel(SpannerDriver, Optional) Variant of this method that allows invoking code to provide a + * compliant {@link CacheDriver} instance. + * @param driver Pre-fabricated Spanner driver to wrap with an adapter instance. + * @param Typed model key structure which the resulting adapter should be specialized to. + * @param Typed model object structure which the resulting adapter should be specialized to. + * @return Adapter instance, wrapping the provided driver. + */ + public static @Nonnull SpannerAdapter forModel( + @Nonnull SpannerDriver driver) { + return forModel(driver, Optional.empty()); + } + + /** + * Create or resolve a {@link SpannerAdapter} for the pre-fabricated {@link SpannerDriver}, optionally using the + * provided {@link CacheDriver}, if present. + * + * @param driver Pre-fabricated Spanner driver to wrap with an adapter instance. + * @param cacheDriver Optional cache engine to employ when interacting with the provided driver. + * @param Typed model key structure which the resulting adapter should be specialized to. + * @param Typed model object structure which the resulting adapter should be specialized to. + * @return Adapter instance, wrapping the provided driver. + */ + public static @Nonnull SpannerAdapter forModel( + @Nonnull SpannerDriver driver, + @Nonnull Optional> cacheDriver) { + return new SpannerAdapter<>( + driver, + driver.codec(), + cacheDriver + ); + } + + /** Factory responsible for creating {@link SpannerAdapter} instances from injected dependencies. */ + @Factory static final class SpannerAdapterFactory { + private SpannerAdapterFactory() { /* Disallow construction. */ } + + /** + * Acquire a new instance of the Spanner adapter, using the specified component objects to facilitate model + * serialization/deserialization, and transport communication with Cloud Spanner. + * + * @param driver Driver with which we should talk to Spanner. + * @param cache Driver with which we should cache eligible data. + * @return Spanner driver instance. + */ + @Context + @Refreshable + public static @Nonnull SpannerAdapter acquire( + @Nonnull SpannerDriver driver, + @Nonnull Optional> cache) { + // resolve model builder from type + return SpannerAdapter.forModel( + driver, + cache + ); + } + } + + /** + * Create or acquire a {@link SpannerAdapter} and matching {@link SpannerDriver} for the provided generated model + * key and object structures, working against the provided Spanner {@link DatabaseId}. + * + *

    This method variant is the simplest invocation option for acquiring an adapter. Variants of this method + * provide deeper control over interactions with the Spanner service. See below for alternatives if deeper control + * is necessary. Generally, it is best to let the framework manage transport and stub settings.

    + * + * @see #acquire(Message, Message, DatabaseId, ListeningScheduledExecutorService) For an option which lets invoking + * code specify a background executor for RPC transmission and followup. + * @see #acquire(Message, Message, DatabaseId, SpannerOptions.Builder, ListeningScheduledExecutorService) Variant of + * this same method which offers control of the {@link SpannerOptions} used to spawn RPC clients. + * @see #acquire(SpannerOptions.Builder, DatabaseId, TransportChannelProvider, Optional, Optional, + * GrpcTransportOptions, ListeningScheduledExecutorService, Message, Message, SpannerDriverSettings, Optional) + * Full control over creation of the Spanner adapter and driver. + * @param keyInstance Generated key {@link Message} structure, for which the adapter should be specialized. + * @param messageInstance Generated object {@link Message} structure, for which the adapter should be specialized. + * @param defaultDatabase Default Spanner database to use when interacting with this adapter. This value may be + * overridden on an individual operation basis via specifying custom + * {@link SpannerDriver.SpannerOperationOptions} and descendents. + * @param Model key structure for which the resulting adapter should be specialized. + * @param Model object structure for which the resulting adapter should be specialized. + * @return Spanner adapter instance, specialized to the provided model and key {@link Message}s. + */ + public static @Nonnull SpannerAdapter acquire( + @Nonnull K keyInstance, + @Nonnull M messageInstance, + @Nonnull DatabaseId defaultDatabase) { + return acquire( + SpannerOptions.newBuilder(), + defaultDatabase, + SpannerStubSettings.defaultTransportChannelProvider(), + Optional.of(SpannerStubSettings.defaultCredentialsProviderBuilder().build()), + Optional.empty(), + GrpcTransportOptions.newBuilder().build(), + MoreExecutors.listeningDecorator( + Executors.newScheduledThreadPool(3) + ), + keyInstance, + messageInstance, + SpannerDriverSettings.DEFAULTS, + Optional.empty() + ); + } + + /** + * Create or acquire a {@link SpannerAdapter} and matching {@link SpannerDriver} for the provided generated model + * key and object structures, working against the provided Spanner {@link DatabaseId}. + * + *

    This method variant additionally allows the developer to specify a custom + * {@link ListeningScheduledExecutorService} to use for background operation execution. This executor service is + * injected directly into the {@link SpannerDriver} and underlying Spanner RPC client, and is used for both RPC + * operational execution and async followup.

    + * + *

    Variants of this method provide deeper control over interactions with the Spanner service. See below for + * alternatives.

    + * + * @see #acquire(Message, Message, DatabaseId) For a simpler version of this method which uses managed driver + * settings and a sensible cached threadpool executor. + * @see #acquire(Message, Message, DatabaseId, SpannerOptions.Builder, ListeningScheduledExecutorService) Variant of + * this same method which offers control of the {@link SpannerOptions} used to spawn RPC clients. + * @see #acquire(SpannerOptions.Builder, DatabaseId, TransportChannelProvider, Optional, Optional, + * GrpcTransportOptions, ListeningScheduledExecutorService, Message, Message, SpannerDriverSettings, Optional) + * Full control over creation of the Spanner adapter and driver. + * @param keyInstance Generated key {@link Message} structure, for which the adapter should be specialized. + * @param messageInstance Generated object {@link Message} structure, for which the adapter should be specialized. + * @param defaultDatabase Default Spanner database to use when interacting with this adapter. This value may be + * overridden on an individual operation basis via specifying custom + * {@link SpannerDriver.SpannerOperationOptions} and descendents. + * @param executorService Executor service to use for primary RPC execution and related followup. + * @param Model key structure for which the resulting adapter should be specialized. + * @param Model object structure for which the resulting adapter should be specialized. + * @return Spanner adapter instance, specialized to the provided model and key {@link Message}s. + */ + public static @Nonnull SpannerAdapter acquire( + @Nonnull K keyInstance, + @Nonnull M messageInstance, + @Nonnull DatabaseId defaultDatabase, + @Nonnull ListeningScheduledExecutorService executorService) { + return acquire( + SpannerOptions.newBuilder(), + defaultDatabase, + SpannerStubSettings.defaultTransportChannelProvider(), + Optional.of(SpannerStubSettings.defaultCredentialsProviderBuilder().build()), + Optional.empty(), + GrpcTransportOptions.newBuilder().build(), + executorService, + keyInstance, + messageInstance, + SpannerDriverSettings.DEFAULTS, + Optional.empty() + ); + } + + /** + * Create or acquire a {@link SpannerAdapter} and matching {@link SpannerDriver} for the provided generated model + * key and object structures, working against the provided Spanner {@link DatabaseId}. + * + *

    This method variant is a balanced invocation which allows invoking code to control most settings, + * without coupling too tightly to Google SDKs.

    + * + * @see #acquire(Message, Message, DatabaseId, ListeningScheduledExecutorService) For an option which lets invoking + * code specify a background executor for RPC transmission and followup. + * @see #acquire(Message, Message, DatabaseId, SpannerOptions.Builder, ListeningScheduledExecutorService) Variant of + * this same method which offers control of the {@link SpannerOptions} used to spawn RPC clients. + * @see #acquire(SpannerOptions.Builder, DatabaseId, TransportChannelProvider, Optional, Optional, + * GrpcTransportOptions, ListeningScheduledExecutorService, Message, Message, SpannerDriverSettings, Optional) + * Full control over creation of the Spanner adapter and driver. + * @param keyInstance Generated key {@link Message} structure, for which the adapter should be specialized. + * @param messageInstance Generated object {@link Message} structure, for which the adapter should be specialized. + * @param defaultDatabase Default Spanner database to use when interacting with this adapter. This value may be + * overridden on an individual operation basis via specifying custom + * {@link SpannerDriver.SpannerOperationOptions} and descendents. + * @param executorService Executor service to use for primary RPC execution and related followup. + * @param driverSettings Custom driver settings to apply. {@link SpannerDriverSettings#DEFAULTS} is a good start. + * @param Model key structure for which the resulting adapter should be specialized. + * @param Model object structure for which the resulting adapter should be specialized. + * @return Spanner adapter instance, specialized to the provided model and key {@link Message}s. + */ + public static @Nonnull SpannerAdapter acquire( + @Nonnull K keyInstance, + @Nonnull M messageInstance, + @Nonnull DatabaseId defaultDatabase, + @Nonnull Optional executorService, + @Nonnull Optional driverSettings, + @Nonnull Optional baseOptions, + @Nonnull Optional> cacheDriver) { + return acquire( + baseOptions.orElse(SpannerOptions.newBuilder()), + defaultDatabase, + SpannerStubSettings.defaultTransportChannelProvider(), + Optional.of(SpannerStubSettings.defaultCredentialsProviderBuilder().build()), + Optional.empty(), + GrpcTransportOptions.newBuilder().build(), + executorService.orElseGet(() -> MoreExecutors.listeningDecorator( + Executors.newScheduledThreadPool(3) + )), + keyInstance, + messageInstance, + driverSettings.orElse(SpannerDriverSettings.DEFAULTS), + cacheDriver + ); + } + + /** + * Create or acquire a {@link SpannerAdapter} and matching {@link SpannerDriver} for the provided generated model + * key and object structures, working against the provided Spanner {@link DatabaseId}. + * + *

    This method variant additionally allows the developer to specify a custom + * {@link ListeningScheduledExecutorService} to use for background operation execution, and a set of + * {@link SpannerOptions} to use when spawning RPC clients. This executor service is injected directly into the + * {@link SpannerDriver} and underlying Spanner RPC clients, and is used for both RPC operational execution and + * async followup.

    + * + *

    Variants of this method provide either simpler invocation, or deeper control over interactions with the + * Spanner service. See below for alternatives.

    + * + * @see #acquire(Message, Message, DatabaseId) For a simpler version of this method which uses managed driver + * settings and a sensible cached threadpool executor. + * @see #acquire(Message, Message, DatabaseId, ListeningScheduledExecutorService) For a simpler version of this + * method which uses managed driver settings. + * @see #acquire(SpannerOptions.Builder, DatabaseId, TransportChannelProvider, Optional, Optional, + * GrpcTransportOptions, ListeningScheduledExecutorService, Message, Message, SpannerDriverSettings, Optional) + * Full control over creation of the Spanner adapter and driver. + * @param keyInstance Generated key {@link Message} structure, for which the adapter should be specialized. + * @param messageInstance Generated object {@link Message} structure, for which the adapter should be specialized. + * @param defaultDatabase Default Spanner database to use when interacting with this adapter. This value may be + * overridden on an individual operation basis via specifying custom + * {@link SpannerDriver.SpannerOperationOptions} and descendents. + * @param baseOptions Spanner options to use when spawning RPC clients. + * @param executorService Executor service to use for primary RPC execution and related followup. + * @param Model key structure for which the resulting adapter should be specialized. + * @param Model object structure for which the resulting adapter should be specialized. + * @return Spanner adapter instance, specialized to the provided model and key {@link Message}s. + */ + public static @Nonnull SpannerAdapter acquire( + @Nonnull K keyInstance, + @Nonnull M messageInstance, + @Nonnull DatabaseId defaultDatabase, + @Nonnull SpannerOptions.Builder baseOptions, + @Nonnull ListeningScheduledExecutorService executorService) { + return acquire( + baseOptions, + defaultDatabase, + SpannerStubSettings.defaultTransportChannelProvider(), + Optional.of(SpannerStubSettings.defaultCredentialsProviderBuilder().build()), + Optional.empty(), + GrpcTransportOptions.newBuilder().build(), + executorService, + keyInstance, + messageInstance, + SpannerDriverSettings.DEFAULTS, + Optional.empty() + ); + } + + /** + * Create or acquire a {@link SpannerAdapter} and matching {@link SpannerDriver} for the provided generated model + * key and object structures, working against the provided Spanner {@link DatabaseId}. + * + *

    This method variant additionally allows the developer to specify all custom settings available for the Spanner + * driver and adapter.

    + * + *

    Variants of this method provide simpler invocation, for looser coupling with applications. See below to + * consider these alternatives for situations where deep control isn't necessary.

    + * + * @param baseOptions Spanner options to use when spawning RPC clients. + * @param defaultDatabase Default Spanner database to use when interacting with this adapter. This value may be + * overridden on an individual operation basis via specifying custom + * {@link SpannerDriver.SpannerOperationOptions} and descendents. + * @param spannerChannel Transport channel provider to use when spawning RPC connections to Spanner. + * @param credentialsProvider Credentials provider to use when authorizing calls to Spanner. + * @param callCredentialProvider Call-level credentials provider to use when authorizing calls to Spanner. Optional. + * @param transportOptions Transport options to apply when interacting with Spanner services. + * @param executorService Executor service to use for primary RPC execution and related followup. + * @param keyInstance Generated key {@link Message} structure, for which the adapter should be specialized. + * @param messageInstance Generated object {@link Message} structure, for which the adapter should be specialized. + * @param driverSettings Settings to apply to the Spanner driver and adapter itself. + * @param cacheDriver Cache engine to use when interacting with the underlying driver. + * @param Model key structure for which the resulting adapter should be specialized. + * @param Model object structure for which the resulting adapter should be specialized. + * @return Spanner adapter instance, specialized to the provided model and key {@link Message}s. + */ + public static @Nonnull SpannerAdapter acquire( + @Nonnull SpannerOptions.Builder baseOptions, + @Nonnull DatabaseId defaultDatabase, + @Nonnull @GoogleAPIChannel(service = GoogleService.SPANNER) TransportChannelProvider spannerChannel, + @Nonnull Optional credentialsProvider, + @Nonnull Optional callCredentialProvider, + @Nonnull GrpcTransportOptions transportOptions, + @Nonnull ListeningScheduledExecutorService executorService, + @Nonnull K keyInstance, + @Nonnull M messageInstance, + @Nonnull SpannerDriverSettings driverSettings, + @Nonnull Optional> cacheDriver) { + return SpannerAdapterFactory.acquire( + SpannerDriver.SpannerDriverFactory.acquireDriver( + baseOptions, + defaultDatabase, + spannerChannel, + credentialsProvider, + callCredentialProvider, + transportOptions, + executorService, + keyInstance, + messageInstance, + driverSettings + ), + cacheDriver + ); + } + + // -- API: Closeable -- // + + @Override + public void close() { + // Not yet implemented. + } + + // -- Components -- // + + /** {@inheritDoc} */ + @Override + public @Nonnull ModelCodec codec() { + return this.codec; + } + + /** {@inheritDoc} */ + @Override + public @Nonnull Optional> cache() { + return this.cache; + } + + /** {@inheritDoc} */ + @Override + public @Nonnull DatabaseDriver engine() { + return this.driver; + } + + /** {@inheritDoc} */ + @Override + public @Nonnull ListeningScheduledExecutorService executorService() { + return driver.executorService(); + } + + // -- Spanner: Extended API -- // + + /** + * Acquire the Spanner for Java client powering this adapter. + * + * @return Spanner client for Java. + */ + public @Nonnull Spanner spanner() { + return ((SpannerDriver)engine()).engine; + } +} diff --git a/java/gust/backend/driver/spanner/SpannerCodec.java b/java/gust/backend/driver/spanner/SpannerCodec.java new file mode 100644 index 000000000..b43b79ce9 --- /dev/null +++ b/java/gust/backend/driver/spanner/SpannerCodec.java @@ -0,0 +1,162 @@ +/* + * Copyright © 2020, The Gust Framework Authors. All rights reserved. + * + * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, + * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of + * this code in object or source form requires and implies consent and agreement to that license in principle and + * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of + * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to + * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected + * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, + * is strictly forbidden except in adherence with assigned license requirements. + */ +package gust.backend.driver.spanner; + +import com.google.cloud.spanner.Mutation; +import com.google.cloud.spanner.Struct; +import com.google.protobuf.Message; +import gust.backend.model.*; +import io.micronaut.context.annotation.Context; +import io.micronaut.context.annotation.Factory; + +import javax.annotation.Nonnull; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.ThreadSafe; +import java.io.IOException; + + +/** + * Implements a {@link ModelCodec} for Spanner types used as intermediaries during database operations. In particular, + * Spanner uses {@link Mutation} objects to express writes, and yields reads in the form of {@link Struct} objects which + * each identify a result row. + * + *

    This codec is implemented with custom serialization via {@link SpannerMutationSerializer}, and de-serialization + * via {@link SpannerStructDeserializer}, which are similarly specialized at compile time to a given {@link Message} + * generated schema implementation.

    + * + * @see SpannerMutationSerializer Specialized serialization from {@link Message} objects to Spanner {@link Mutation}s. + * @see SpannerStructDeserializer Specialized de-serialization from {@link Struct} objects to {@link Message}s. + * @param Typed {@link Message} which implements a concrete model object structure, as defined and annotated by + * the core Gust annotations. + */ +@Factory +@Immutable +@ThreadSafe +public final class SpannerCodec implements ModelCodec { + /** Default model instance to build with. */ + private final Model instance; + + /** Serializer for the model, provided at construction time. */ + private final ModelSerializer serializer; + + /** Deserializer for the model, provided at construction time. */ + private final ModelDeserializer deserializer; + + /** + * Construct a mutation codec from scratch. + * + * @param instance Model instance we intend to encode / decode with this codec. + * @param deserializer Deserializer for read-intermediate objects. + */ + private SpannerCodec(@Nonnull Model instance, + @Nonnull ModelSerializer serializer, + @Nonnull ModelDeserializer deserializer) { + this.instance = instance; + this.serializer = serializer; + this.deserializer = deserializer; + } + + /** + * Create a Spanner message codec which adapts the provided builder to a Spanner {@link Mutation} and back with + * default codecs and settings. + * + * @param instance Default instance for the model we will be serializing/deserializing. + * @param Model type for which we will construct or otherwise resolve a collapsed message codec. + * @return Mutation codec bound to the provided message type. + */ + @Context + public static @Nonnull SpannerCodec forModel(@Nonnull M instance) { + return forModel( + instance, + SpannerDriverSettings.DEFAULTS + ); + } + + /** + * Create a Spanner message codec which adapts the provided builder to a Spanner {@link Mutation} and back with + * default codecs and custom settings. + * + * @param instance Default instance for the model we will be serializing/deserializing. + * @param driverSettings Settings for the Spanner driver itself. + * @param Model type for which we will construct or otherwise resolve a collapsed message codec. + * @return Mutation codec bound to the provided message type. + */ + @Context + public static @Nonnull SpannerCodec forModel(@Nonnull M instance, + @Nonnull SpannerDriverSettings driverSettings) { + return forModel( + instance, + SpannerMutationSerializer.forModel( + instance, + driverSettings + ), + SpannerStructDeserializer.forModel( + instance, + driverSettings + ) + ); + } + + /** + * Create a Spanner message codec which adapts the provided builder to a Spanner {@link Mutation} and back. + * + * @param instance Default instance for the model we will be serializing/deserializing. + * @param serializer Custom serializer to use for this codec. + * @param deserializer Custom deserializer to use for this codec. + * @param Model type for which we will construct or otherwise resolve a collapsed message codec. + * @return Mutation codec bound to the provided message type. + */ + @Context + public static @Nonnull SpannerCodec forModel( + @Nonnull M instance, + @Nonnull ModelSerializer serializer, + @Nonnull ModelDeserializer deserializer) { + return new SpannerCodec<>(instance, serializer, deserializer); + } + + /** + * Specialized entrypoint for converting model instances into {@link Mutation} instances so they may be written to + * Spanner. + * + * @param initial Initial empty mutation to populate with the serialized message result. + * @param model Model which we intend to store in Spanner. + * @return Initialized and serialized mutation. + * @throws IOException If some serialization error occurs while processing the model. + */ + public @Nonnull Mutation serialize(@Nonnull Mutation.WriteBuilder initial, + @Nonnull Model model) throws IOException { + return ((SpannerMutationSerializer) this.serializer).initializeMutation( + initial + ).deflate(model); + } + + // -- Implementation: Codec API -- // + + /** @inheritDoc */ + @Override + public @Nonnull Model instance() { + return this.instance; + } + + /** @inheritDoc */ + @Override + public @Nonnull ModelSerializer serializer() { + return this.serializer; + } + + /** @inheritDoc */ + @Override + public @Nonnull ModelDeserializer deserializer() { + return this.deserializer; + } +} diff --git a/java/gust/backend/driver/spanner/SpannerDriver.java b/java/gust/backend/driver/spanner/SpannerDriver.java new file mode 100644 index 000000000..cd4a4d552 --- /dev/null +++ b/java/gust/backend/driver/spanner/SpannerDriver.java @@ -0,0 +1,486 @@ +/* + * Copyright © 2020, The Gust Framework Authors. All rights reserved. + * + * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, + * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of + * this code in object or source form requires and implies consent and agreement to that license in principle and + * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of + * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to + * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected + * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, + * is strictly forbidden except in adherence with assigned license requirements. + */ +package gust.backend.driver.spanner; + +import com.google.api.gax.core.CredentialsProvider; +import com.google.api.gax.rpc.TransportChannelProvider; +import com.google.cloud.grpc.GrpcTransportOptions; +import com.google.cloud.spanner.*; +import com.google.cloud.spanner.SpannerOptions; +import com.google.common.util.concurrent.ListeningScheduledExecutorService; +import com.google.protobuf.FieldMask; +import com.google.protobuf.Message; +import com.google.protobuf.util.FieldMaskUtil; +import gust.backend.model.*; +import gust.backend.runtime.Logging; +import gust.backend.runtime.ReactiveFuture; +import gust.backend.transport.GoogleAPIChannel; +import gust.backend.transport.GoogleService; +import io.micronaut.context.annotation.Context; +import io.micronaut.context.annotation.Factory; +import io.micronaut.runtime.context.scope.Refreshable; +import org.slf4j.Logger; +import tools.elide.core.*; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.ThreadSafe; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.TimeUnit; + +import static gust.backend.driver.spanner.SpannerUtil.*; +import static com.google.common.util.concurrent.Futures.transformAsync; +import static com.google.common.util.concurrent.Futures.immediateFuture; +import static com.google.common.util.concurrent.Futures.withTimeout; +import static gust.backend.runtime.ReactiveFuture.wrap; +import static gust.backend.model.ModelMetadata.*; +import static java.lang.String.format; + + +/** + * Provides a {@link DatabaseDriver} implementation which enables seamless Protocol Buffer persistence with Google Cloud + * Spanner, for any {@link Message}-derived (schema-driven) business model in a given Gust app's ecosystem. + * + *

    Model storage can be deeply customized on a per-model basis, thanks to the built-in proto annotations available + * in gust.core. The Spanner adapter supports basic persistence (i.e. as a regular + *

    PersistenceDriver
    ), but also supports generic, object index-style queries.

    + * + *

    See the main {@link SpannerAdapter} for a full description of supported application-level functionality.

    + * + * @param Typed {@link Message} which implements a concrete model key structure, as defined and annotated by the + * core Gust annotations. + * @param Typed {@link Message} which implements a concrete model object structure, as defined and annotated by + * the core Gust annotations. + * @see SpannerAdapter Main typed adapter interface for Spanner. + * @see SpannerManager Adapter instance manager and factory. + * @see SpannerDriverSettings Driver-level settings specific to Spanner. + * @see gust.backend.driver.firestore.FirestoreDriver Similar driver implementation, built on top of Cloud Firestore, + * which itself is implemented on top of Cloud Spanner. + */ +@Immutable @ThreadSafe +@SuppressWarnings({"UnstableApiUsage", "OptionalUsedAsFieldOrParameterType"}) +public final class SpannerDriver + implements DatabaseDriver { + /** Private log pipe. */ + private static final Logger logging = Logging.logger(SpannerDriver.class); + + /** Executor service to use for async calls. */ + private final @Nonnull ListeningScheduledExecutorService executorService; + + /** Codec to use for serializing/de-serializing models. */ + private final @Nonnull ModelCodec codec; + + /** Default database ID to interact with. */ + private final @Nonnull DatabaseId defaultDatabase; + + /** Settings for the Spanner driver. */ + private final @Nonnull SpannerDriverSettings driverSettings; + + /** Cloud Spanner client engine. */ + final @Nonnull Spanner engine; + + /** Defines generic Spanner operation-specific options. */ + interface SpannerOperationOptions extends OperationOptions { + /** @return Database to use when connecting to Spanner. */ + default @Nonnull Optional databaseId() { + return Optional.empty(); + } + } + + /** Defines Spanner-specific options for all read and query operations. */ + interface SpannerReadOptions extends SpannerOperationOptions, FetchOptions { + /** @return Manual field projection, as applicable. Defaults to any present field mask. */ + default @Nonnull Optional projection() { + return this.fieldMask(); + } + } + + /** Defines Spanner-specific options for all write and mutation operations. */ + interface SpannerWriteOptions extends SpannerOperationOptions, WriteOptions { + // Nothing yet. + } + + /** Defines Spanner-specific fetch options. */ + interface SpannerFetchOptions extends SpannerReadOptions { + /** Default set of fetch options. */ + SpannerFetchOptions DEFAULTS = new SpannerFetchOptions() {}; + + /** @return Timestamp boundary for single-use reads. */ + default @Nonnull Optional timestampBound() { + return Optional.empty(); + } + } + + /** Defines Spanner-specific mutative write options. */ + interface SpannerMutationOptions extends SpannerWriteOptions { + /** Default set of mutation options. */ + SpannerMutationOptions DEFAULTS = new SpannerMutationOptions() {}; + + /** + * Provides a transaction manger for a delete transaction. To activate transactions, one must also set the + * `transactional` configuration setting to `true`. + * + * @return Optional containing the transaction manager to use for this operation. + */ + default @Nonnull Optional transactionContext() { + return Optional.empty(); + } + } + + /** Defines Spanner-specific mutative delete options. */ + interface SpannerDeleteOptions extends SpannerWriteOptions { + /** Default set of delete options. */ + SpannerDeleteOptions DEFAULTS = new SpannerDeleteOptions() {}; + } + + /** + * Construct a new Spanner driver from scratch. + * + * @param baseOptions Base options to apply to the Spanner driver. + * @param channelProvider Managed gRPC channel to use for Spanner RPCAPI interactions. + * @param credentialsProvider Transport credentials provider. + * @param defaultDatabase Default Spanner database to use and interact with. + * @param callCredentialProvider RPC call credential provider. + * @param transportOptions Options to apply to the transport layer. + * @param executorService Executor service to use when executing calls. + * @param codec Model codec to use with this driver. + * @param driverSettings Settings for the Spanner driver itself. + */ + private SpannerDriver(@Nonnull SpannerOptions.Builder baseOptions, + @Nonnull TransportChannelProvider channelProvider, + @Nonnull DatabaseId defaultDatabase, + @Nonnull Optional credentialsProvider, + @Nonnull Optional callCredentialProvider, + @Nonnull GrpcTransportOptions transportOptions, + @Nonnull ListeningScheduledExecutorService executorService, + @Nonnull ModelCodec codec, + @Nonnull SpannerDriverSettings driverSettings) { + this.codec = codec; + this.defaultDatabase = defaultDatabase; + this.executorService = executorService; + this.driverSettings = driverSettings; + SpannerOptions.Builder options = baseOptions + .setChannelProvider(channelProvider) + .setTransportOptions(transportOptions); + + callCredentialProvider.ifPresent(options::setCallCredentialsProvider); + credentialsProvider.ifPresent((credentialProvider) -> options + .getSpannerStubSettingsBuilder() + .setCredentialsProvider(credentialProvider)); + + if (logging.isDebugEnabled()) + logging.debug(String.format("Initializing Spanner driver with options:\n%s", + options.build().getService().getOptions().toString())); + this.engine = options.build().getService(); + } + + /** Factory responsible for creating {@link SpannerDriver} instances from injected dependencies. */ + @Factory static final class SpannerDriverFactory { + private SpannerDriverFactory() { /* Disallow construction. */ } + + /** + * Acquire a new instance of the Spanner driver, using the specified configuration settings, and the specified + * injected channel. + * + * @param baseOptions Base options to apply to the Spanner driver. + * @param spannerChannel Managed gRPC channel provider. + * @param credentialsProvider Transport credentials provider. + * @param callCredentialProvider RPC call credential provider. + * @param transportOptions Options to apply to the Spanner channel. + * @param executorService Executor service to use when executing calls. + * @param keyInstance Key model class we are binding this driver to. + * @param modelInstance Default instance of the model we wish to make a driver for. + * @param driverSettings Settings for the Spanner driver. + * @return Spanner driver instance. + */ + @Context + @Refreshable + public static @Nonnull SpannerDriver acquireDriver( + @Nonnull SpannerOptions.Builder baseOptions, + @Nonnull DatabaseId defaultDatabase, + @Nonnull @GoogleAPIChannel(service = GoogleService.SPANNER) TransportChannelProvider spannerChannel, + @Nonnull Optional credentialsProvider, + @Nonnull Optional callCredentialProvider, + @Nonnull GrpcTransportOptions transportOptions, + @Nonnull ListeningScheduledExecutorService executorService, + @SuppressWarnings("unused") @Nonnull K keyInstance, + @Nonnull M modelInstance, + @Nonnull SpannerDriverSettings driverSettings) { + return new SpannerDriver<>( + baseOptions, + spannerChannel, + defaultDatabase, + credentialsProvider, + callCredentialProvider, + transportOptions, + executorService, + SpannerCodec.forModel( + modelInstance, + SpannerMutationSerializer.forModel( + modelInstance, + driverSettings + ), + SpannerStructDeserializer.forModel( + modelInstance, + driverSettings + ) + ), + driverSettings + ); + } + } + + /** @inheritDoc */ + @Override + public @Nonnull ListeningScheduledExecutorService executorService() { + return executorService; + } + + /** @inheritDoc */ + @Override + public @Nonnull ModelCodec codec() { + return codec; + } + + /** @inheritDoc */ + @Override + public @Nonnull ReactiveFuture> retrieve(@Nonnull Key key, + @Nonnull FetchOptions options) { + // null check all inputs + Objects.requireNonNull(key, "Cannot fetch model with `null` for key."); + Objects.requireNonNull(options, "Cannot fetch model without `options`."); + enforceRole(key, DatapointType.OBJECT_KEY); + var keyId = id(key).orElseThrow(() -> + new IllegalArgumentException("Cannot fetch model with empty key.")); + + // resolve the table where we should look for this entity + var table = resolveTableName(key); + + // next, resolve the executor, database we should operate on, and corresponding client + ListeningScheduledExecutorService exec = options.executorService().orElseGet(this::executorService); + SpannerFetchOptions spannerOpts; + + if (options.getClass().isAssignableFrom(SpannerFetchOptions.class)) { + spannerOpts = ((SpannerFetchOptions) options); + } else { + spannerOpts = SpannerFetchOptions.DEFAULTS; + } + + DatabaseId db = spannerOpts.databaseId().orElse(defaultDatabase); + var client = engine.getDatabaseClient(db); + boolean transactional = spannerOpts.transactional().isPresent() && spannerOpts.transactional().get(); + + // with the DB client in hand, resolve the raw Spanner result + ReadContext context; + if (spannerOpts.timestampBound().isPresent()) { + if (transactional) { + context = client.readOnlyTransaction(spannerOpts.timestampBound().get()); + } else { + context = client.singleUse(spannerOpts.timestampBound().get()); + } + } else { + if (transactional) { + context = client.readOnlyTransaction(); + } else { + context = client.singleUse(); + } + } + + // calculate the fields we should read from Spanner. because Spanner is a SQL-style DB with tables, we must + // enumerate each field we wish to load into the result set. this set of fields can either be specified via a + // field mask attached to the call options, or by generating a set of fields from the top-level model. + List fieldsToRead; + if (spannerOpts.projection().isPresent()) { + fieldsToRead = FieldMaskUtil.normalize(spannerOpts.projection().get()) + .getPathsList(); + } else { + fieldsToRead = calculateDefaultFields( + this.codec().instance().getDescriptorForType(), + driverSettings + ); + } + + var op = wrap(context.readRowAsync( + table, + com.google.cloud.spanner.Key.of(id(key).orElseThrow()), + fieldsToRead + )); + + return wrap(transformAsync(withTimeout(op, 120, TimeUnit.SECONDS, exec), (result) -> { + if (result == null) { + if (logging.isDebugEnabled()) + logging.debug("Query option result was `null`. Returning empty result."); + return immediateFuture(Optional.empty()); + + } else { + if (logging.isDebugEnabled()) + logging.debug("Received non-null `Struct` result from Spanner. Deserializing..."); + + // deserialize the model + var deserialized = codec.deserialize(result); + if (logging.isDebugEnabled()) + logging.debug(format( + "Found and deserialized model at ID '%s' from Spanner. Record follows:\n%s", + keyId, + deserialized)); + + return immediateFuture(Optional.of( + spliceKey(applyMask(deserialized, options), Optional.of(key)) + )); + } + }, exec)); + } + + /** @inheritDoc */ + @Override + public @Nonnull ReactiveFuture persist(@Nullable Key key, + @Nonnull Model model, + @Nonnull WriteOptions options) { + // enforce model constraints + Objects.requireNonNull(key, "Cannot write model with `null` for key."); + Objects.requireNonNull(model, "Cannot write model which is, itself, `null`."); + Objects.requireNonNull(options, "Cannot write model without `options`."); + enforceRole(key, DatapointType.OBJECT_KEY); + + // resolve executor + ListeningScheduledExecutorService exec = options.executorService().orElseGet(this::executorService); + + // resolve existing ID, if any. if none can be resolved, generate one, and splice it into the key, and then + // likewise splice the key into the model. if an explicit ID is present, it is assumed that it is mounted + // correctly on the model. + Optional existingId = id(key); + Object modelId = existingId.orElseGet(() -> this.generateId(model)); + + if (existingId.isEmpty()) { + spliceKey( + model, + Optional.of(spliceId( + key, + Optional.of(modelId) + )) + ); + } + + try { + // resolve extended spanner mutation options + SpannerMutationOptions spannerOpts; + if (options.getClass().isAssignableFrom(SpannerMutationOptions.class)) { + spannerOpts = ((SpannerMutationOptions) options); + } else { + spannerOpts = SpannerMutationOptions.DEFAULTS; + } + + // resolve the table where we should look for this entity + var table = resolveTableName(key); + DatabaseId db = spannerOpts.databaseId().orElse(defaultDatabase); + var client = engine.getDatabaseClient(db); + boolean transactional = spannerOpts.transactional().isPresent() ? + spannerOpts.transactional().get() : + options.transactional().orElse(false); + + var writeMode = spannerOpts.writeMode().isPresent() ? + spannerOpts.writeMode().get() : + options.writeMode().orElse(WriteOptions.WriteDisposition.BLIND); + + if (logging.isDebugEnabled()) + logging.debug("Mode '{}' determined for Spanner write.", writeMode.name()); + + Mutation.WriteBuilder mutation; + switch (writeMode) { + case BLIND: mutation = Mutation.newInsertOrUpdateBuilder(table); break; + case MUST_EXIST: mutation = Mutation.newUpdateBuilder(table); break; + case MUST_NOT_EXIST: mutation = Mutation.newInsertBuilder(table); break; + default: mutation = Mutation.newReplaceBuilder(table); break; + } + + if (codec instanceof SpannerCodec) { + if (logging.isTraceEnabled()) + logging.trace("Serializing model to Spanner `Mutation`: {}", model.toString()); + + // fill in the mutation + var serialized = ((SpannerCodec) codec).serialize(mutation, model); + + return wrap(exec.submit(() -> { + if (transactional && spannerOpts.transactionContext().isPresent()) { + spannerOpts.transactionContext().get() + .buffer(serialized); + } else { + // it's time to actually write the model + var write = client.writeAtLeastOnce(Collections.singleton(serialized)); + Objects.requireNonNull(write, "write result from Spanner should never be null"); + } + return model; + })); + + } else { + throw new IllegalStateException("Cannot serialize Spanner model without `SpannerCodec`."); + } + + } catch (IOException ioe) { + throw new IllegalStateException(ioe); + } + } + + /** @inheritDoc */ + @Override + public @Nonnull ReactiveFuture delete(@Nonnull Key key, + @Nonnull DeleteOptions baseOptions) { + Objects.requireNonNull(key, "cannot delete null key from Spanner"); + Objects.requireNonNull(baseOptions, "cannot delete without valid Spanner options"); + enforceRole(key, DatapointType.OBJECT_KEY); + Object keyId = id(key).orElseThrow(() -> + new IllegalArgumentException("Cannot delete key with empty or missing ID.")); + + SpannerMutationOptions options; + if (baseOptions instanceof SpannerMutationOptions) { + options = (SpannerMutationOptions) baseOptions; + } else { + options = SpannerMutationOptions.DEFAULTS; + } + + // prep for an async delete action + ListeningScheduledExecutorService exec = options.executorService().orElseGet(this::executorService); + + // resolve extended spanner mutation options + SpannerDeleteOptions spannerOpts; + if (options.getClass().isAssignableFrom(SpannerDeleteOptions.class)) { + spannerOpts = ((SpannerDeleteOptions) options); + } else { + spannerOpts = SpannerDeleteOptions.DEFAULTS; + } + + // next, resolve the table we should work with, and any override DB + var table = resolveTableName(key); + DatabaseId db = spannerOpts.databaseId().orElse(defaultDatabase); + var client = engine.getDatabaseClient(db); + boolean transactional = spannerOpts.transactional().isPresent() ? + spannerOpts.transactional().get() : + options.transactional().orElse(false); + + // prep the delete operation and fire it off + var deleteOperation = Mutation.delete(table, com.google.cloud.spanner.Key.of(keyId)); + if (logging.isDebugEnabled()) + logging.debug("Deleting model at ID `{}` in table `{}`.", keyId, table); + return wrap(exec.submit(() -> { + if (transactional && options.transactionContext().isPresent()) { + options.transactionContext().get().buffer(deleteOperation); + } else { + var result = client.write(Collections.singletonList(deleteOperation)); + Objects.requireNonNull(result, "delete result from Spanner should never be null"); + } + return key; + })); + } +} diff --git a/java/gust/backend/driver/spanner/SpannerDriverSettings.java b/java/gust/backend/driver/spanner/SpannerDriverSettings.java new file mode 100644 index 000000000..1d0f8060a --- /dev/null +++ b/java/gust/backend/driver/spanner/SpannerDriverSettings.java @@ -0,0 +1,71 @@ +/* + * Copyright © 2020, The Gust Framework Authors. All rights reserved. + * + * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, + * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of + * this code in object or source form requires and implies consent and agreement to that license in principle and + * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of + * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to + * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected + * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, + * is strictly forbidden except in adherence with assigned license requirements. + */ +package gust.backend.driver.spanner; + +import javax.annotation.Nonnull; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.ThreadSafe; + + +/** Specifies settings for the Spanner driver and data storage implementation. */ +@Immutable +@ThreadSafe +public interface SpannerDriverSettings { + /** Concrete hard-coded driver defaults. */ + final class DefaultSettings { + private DefaultSettings() { /* disallow construction */ } + + /** Default value: Whether to preserve proto field names (`true`) or use JSON names (`false`, default). */ + public static final Boolean DEFAULT_PRESERVE_FIELD_NAMES = false; + + /** Default value: Whether to generate Spanner style names with initial capitals. */ + public static final Boolean DEFAULT_CAPITALIZED_NAMES = true; + + /** Default value: Whether to treat enumeration instances as numbers (`true`) or strings (`false`, default). */ + public static final Boolean DEFAULT_ENUMS_AS_NUMBERS = false; + + /** Default value: Whether to perform runtime deserialization checks (`true`, default) or not (`false`). */ + public static final Boolean DEFAULT_CHECK_EXPECTED_TYPES = true; + + /** Default size for `STRING` or `BYTES` column fields with no explicit setting. */ + private static final int DEFAULT_COLUMN_SIZE = 2048; + } + + /** Default set of configured settings for the Spanner driver. */ + SpannerDriverSettings DEFAULTS = new SpannerDriverSettings() {}; + + /** @return Whether to preserve proto field names (`true`) or use JSON names (defaults to `false`). */ + default @Nonnull Boolean preserveFieldNames() { + return DefaultSettings.DEFAULT_PRESERVE_FIELD_NAMES; + } + + /** @return Whether to generate Spanner style names with initial capitals (i.e. `Name` instead of `name`). */ + default @Nonnull Boolean defaultCapitalizedNames() { + return DefaultSettings.DEFAULT_CAPITALIZED_NAMES; + } + + /** @return Whether to treat enumeration instances as numbers (`true`) or strings (defaults to `false`). */ + default @Nonnull Boolean enumsAsNumbers() { + return DefaultSettings.DEFAULT_ENUMS_AS_NUMBERS; + } + + /** @return Whether to perform runtime deserialization checks (defaults to `true`). */ + default @Nonnull Boolean checkExpectedTypes() { + return DefaultSettings.DEFAULT_CHECK_EXPECTED_TYPES; + } + + /** @return Default size to use for `STRING` or `BYTES` columns that don't otherwise specify a size. */ + default int defaultColumnSize() { + return DefaultSettings.DEFAULT_COLUMN_SIZE; + } +} diff --git a/java/gust/backend/driver/spanner/SpannerGeneratedDDL.java b/java/gust/backend/driver/spanner/SpannerGeneratedDDL.java new file mode 100644 index 000000000..f162d1049 --- /dev/null +++ b/java/gust/backend/driver/spanner/SpannerGeneratedDDL.java @@ -0,0 +1,785 @@ +/* + * Copyright © 2020, The Gust Framework Authors. All rights reserved. + * + * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, + * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of + * this code in object or source form requires and implies consent and agreement to that license in principle and + * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of + * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to + * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected + * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, + * is strictly forbidden except in adherence with assigned license requirements. + */ +package gust.backend.driver.spanner; + +import com.google.cloud.spanner.Type; +import com.google.protobuf.Message; +import com.google.protobuf.Timestamp; +import tools.elide.core.SpannerFieldOptions; + +import javax.annotation.Nonnull; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.ThreadSafe; +import java.util.*; +import java.util.stream.Collectors; + +import static com.google.protobuf.Descriptors.Descriptor; +import static com.google.protobuf.Descriptors.FieldDescriptor; +import static gust.backend.driver.spanner.SpannerUtil.*; +import static gust.backend.model.ModelMetadata.*; +import static java.lang.String.format; +import static java.lang.String.join; + + +/** Container for generated schema-driven Spanner DDL. */ +@Immutable +@ThreadSafe +public final class SpannerGeneratedDDL { + /** Represents a DDL statement structure in code that can be rendered down to a string. */ + public interface RenderableStatement { + /** + * Render this statement into a String buffer. + * + * @return Rendered statement. + */ + @Nonnull StringBuilder render(); + } + + /** Sort direction settings which can apply to columns. */ + public enum SortDirection { + /** Sort values in the column in ascending order. This is the default value. */ + ASC, + + /** Sort values in the column in descending order. */ + DESC + } + + /** Specifies options for reference action propagation (i.e. on-delete or on-update). */ + public enum PropagatedAction { + /** Take no action. This is the default value. */ + NO_ACTION, + + /** Cascade changes on delete or update. */ + CASCADE + } + + /** Specifies a generic table constraint to include in a DDL statement. */ + public static final class TableConstraint implements RenderableStatement { + final @Nonnull String name; + final @Nonnull String expression; + + /** + * Private constructor for a table constraint specification. + * + * @param name Name of the table constraint. + * @param expression Expression to use as a constraint. + */ + private TableConstraint(@Nonnull String name, @Nonnull String expression) { + this.name = name; + this.expression = expression; + } + + /** + * Spawn a table constraint at the provided name, enforcing the provided expression. + * + * @param name Name of the constraint to enclose in the DDL statement. + * @param expression Expression to enforce as a table constraint. + * @return Table constraint specification. + */ + public static @Nonnull TableConstraint named(@Nonnull String name, + @Nonnull String expression) { + return new TableConstraint(name, expression); + } + + /** @inheritDoc */ + @Override + public @Nonnull StringBuilder render() { + return (new StringBuilder()).append(format( + "CONSTRAINT %s CHECK ( %s )", + this.name, + this.expression + )); + } + } + + /** Specify a parent table against which this table is interleaved. */ + public static final class InterleaveTarget implements RenderableStatement { + final @Nonnull String parent; + final @Nonnull Optional action; + + /** + * Private constructor for an interleave target for a Spanner table. + * + * @param parent Parent table where we should interleave this table. + * @param action Action to take, if any, when deletes or changes happen in the parent table. + */ + private InterleaveTarget(@Nonnull String parent, + @Nonnull Optional action) { + this.parent = parent; + this.action = action; + } + + /** + * Generate an interleave target specification for the provided parent table name. + * + * @see #forParent(String, Optional) To pass a propagation action. + * @param parent Parent table where we should interleave a given table. + * @return Interleave target specification. + */ + public static @Nonnull InterleaveTarget forParent(@Nonnull String parent) { + return forParent(parent, Optional.empty()); + } + + /** + * Generate an interleave target specification for the provided parent table name, optionally applying the + * provided propagation action. + * + * @param parent Parent table where we should interleave a given table. + * @param action Action to take, if any, when parent rows change that should affect the child table. + * @return Interleave target specification. + */ + public static @Nonnull InterleaveTarget forParent(@Nonnull String parent, + @Nonnull Optional action) { + return new InterleaveTarget(parent, action); + } + + /** @inheritDoc */ + @Override + public @Nonnull StringBuilder render() { + var buf = new StringBuilder(format( + "INTERLEAVE IN PARENT %s", + this.parent + )); + + if (this.action.isPresent()) { + buf.append(" "); + buf.append(format( + "ON DELETE %s", + this.action.get().name() + )); + } + + return buf; + } + } + + /** Specifies an individual field as part of a DDL statement. */ + private static final class ColumnSpec implements RenderableStatement { + final @Nonnull String name; + final @Nonnull Type type; + final @Nonnull Integer length; + final @Nonnull FieldDescriptor field; + final @Nonnull Boolean nonnull; + final @Nonnull Optional expression; + final @Nonnull Boolean expressionStored; + final @Nonnull Boolean allowCommitTimestamp; + + /** + * Private constructor. + * + * @param name Column name in Spanner. + * @param type Type specification in Spanner. + * @param length Length for string fields, or `0`. + * @param nonnull Whether to mark the field as non-null. + * @param allowCommitTimestamp Whether to fill this column with the commit timestamp. + * @param field Model field that spawned this column specification. + */ + ColumnSpec(@Nonnull String name, + @Nonnull Type type, + @Nonnull Integer length, + @Nonnull Optional nonnull, + @Nonnull Optional expression, + @Nonnull Optional expressionStored, + @Nonnull Optional allowCommitTimestamp, + @Nonnull FieldDescriptor field) { + this.name = name; + this.type = type; + this.length = length; + this.nonnull = nonnull.orElse(false); + this.expression = expression; + this.expressionStored = expressionStored.orElse(false); + this.allowCommitTimestamp = allowCommitTimestamp.orElse(false); + this.field = field; + } + + /** + * Create a column spec for the provided field information, considering any active driver settings. + * + * @param fieldPointer Pointer to the field we should consider. + * @param settings Active set of Spanner driver settings. + * @return Spawned column corresponding to the provided field. + */ + static @Nonnull ColumnSpec columnSpecForField(@Nonnull FieldPointer fieldPointer, + @Nonnull SpannerDriverSettings settings) { + var columnOpts = columnOpts(fieldPointer); + var spannerOpts = spannerOpts(fieldPointer); + var fieldOpts = fieldOpts(fieldPointer); + var fieldName = resolveColumnName(fieldPointer, spannerOpts, columnOpts, settings); + var fieldType = resolveColumnType(fieldPointer, spannerOpts, columnOpts, settings); + Type innerType = fieldPointer.getField().isRepeated() ? + fieldType.getArrayElementType() : + fieldType; + var fieldSize = innerType.getCode() == Type.Code.STRING || innerType.getCode() == Type.Code.BYTES ? + resolveColumnSize(fieldPointer.getField(), spannerOpts, columnOpts, settings) : + -1; + + // resolve spanner opts or defaults + var resolvedSpannerOpts = spannerOpts.orElse(SpannerFieldOptions.getDefaultInstance()); + var expression = resolvedSpannerOpts.getExpression().length() > 0 ? + Optional.of(resolvedSpannerOpts.getExpression()) : Optional.empty(); + + var commitUpdate = false; + var protoType = fieldPointer.getField().getType(); + if (fieldOpts.isPresent() && fieldOpts.get().getStampUpdate()) { + switch (protoType) { + case STRING: + case UINT64: + case FIXED64: + commitUpdate = true; + break; + + case MESSAGE: + if (fieldPointer.getField().getMessageType().getFullName().equals( + Timestamp.getDescriptor().getFullName())) { + commitUpdate = true; // we can decode from a `Timestamp` record + } + break; + + default: + // any other field type represents an illegal state + throw new IllegalStateException(format( + "Cannot place `commit_timestamp` in field of type '%s'", protoType.name())); + } + } + + return new ColumnSpec( + fieldName, + fieldType, + fieldSize, + Optional.of(resolvedSpannerOpts.getNonnull()), + expression, + Optional.of(resolvedSpannerOpts.getStored()), + Optional.of(commitUpdate), + fieldPointer.getField() + ); + } + + /** + * Create a column spec for the provided model key field, considering any active driver settings. + * + * @param model Model schema for the object or key record. + * @param keyField Primary key field pre-resolved for a given Spanner table. + * @param settings Active Spanner driver settings. + * @return Spawned primary key column corresponding to the provided model key. + */ + static @Nonnull ColumnSpec columnSpecForKey(@Nonnull Descriptor model, + @Nonnull FieldPointer keyField, + @Nonnull SpannerDriverSettings settings) { + var idField = idField(model).orElseThrow(); + var keyName = resolveKeyColumn(idField, settings); + var keyType = resolveKeyType(idField); + var spannerOpts = spannerOpts(idField); + var columnOpts = columnOpts(idField); + int columnSize = -1; + if (keyType.getCode() == Type.Code.STRING || + keyType.getCode() == Type.Code.BYTES) { + columnSize = resolveColumnSize(keyField.getField(), spannerOpts, columnOpts, settings); + } + + return new ColumnSpec( + keyName, + keyType, + columnSize, + Optional.of(true), // primary keys are always set to `NOT NULL`. + Optional.empty(), // primary keys do not support expressions + Optional.empty(), + Optional.empty(), // primary keys cannot be set to the commit timestamp + keyField.getField() + ); + } + + /** + * Render this column spec into a definition statement, suitable for use when creating a table. + * + * @return Rendered column spec statement. + */ + @Override + public @Nonnull StringBuilder render() { + // prepare field statement + var buf = new StringBuilder(); + + // calculate field type designation first + String fieldType; + Type.Code innerType = this.field.isRepeated() ? + this.type.getArrayElementType().getCode() : + this.type.getCode(); + + String innerTypeSpec; + if (innerType == Type.Code.STRING || innerType == Type.Code.BYTES) { + innerTypeSpec = format( + "%s(%s)", + innerType.name(), + this.length + ); + } else { + innerTypeSpec = innerType.name(); + } + if (this.type.getCode() == Type.Code.ARRAY) { + // it's a repeated field + fieldType = format( + "ARRAY<%s>", + innerTypeSpec + ); + } else { + // it's a singular field. make sure to cover the special case for strings. + fieldType = innerTypeSpec; + } + + buf.append(format( + "%s %s", + this.name, + fieldType + )); + + // prepare field options + var optionsBuffer = new ArrayList(); + + // consider NONNULL + if (this.nonnull) { + optionsBuffer.add("NOT NULL"); + } + + // consider expressions + if (this.expression.isPresent()) { + optionsBuffer.add(format("AS ( %s )", this.expression.get())); + if (this.expressionStored) + optionsBuffer.add("STORED"); + } + + // consider options + if (this.allowCommitTimestamp) { + optionsBuffer.add("OPTIONS allow_commit_timestamp = true"); + } + if (!optionsBuffer.isEmpty()) { + buf.append(" "); + buf.append(join(" ", optionsBuffer)); + } + return buf; + } + } + + /** + * Build properties for a generated Spanner table DDL statement, based on a given model instance as a base for + * configuring the table name (via annotations / calculated defaults) and set of typed Spanner value columns. + * + *

    To build the actual DDL statement, fill out the builder, build it, and then ask the resulting object for the + * DDL as a string.

    + */ + @SuppressWarnings("unused") + public static final class Builder { + /** Base model on which this builder will operate. Immutable. */ + final @Nonnull Descriptor model; + + /** Active set of driver settings. Immutable. */ + final @Nonnull SpannerDriverSettings settings; + + /** Immutable: Name of the table in Spanner. */ + final @Nonnull String tableName; + + /** Immutable: Name of the primary key column. */ + final @Nonnull String primaryKey; + + /** Immutable: Generated columns in Spanner. */ + final @Nonnull List columns; + + /** Mutable: Key column sort direction. */ + @Nonnull SortDirection keySortDirection; + + /** Mutable: List of table constraints. */ + @Nonnull Optional> tableConstraints; + + /** Mutable: Optimizer version to apply. */ + @Nonnull Optional optimizerVersion; + + /** Mutable: Version retention period. */ + @Nonnull Optional versionRetentionPeriod; + + /** Mutable: Table interleave target. */ + @Nonnull Optional interleaveTarget; + + /** + * Package-private constructor for a builder. + * + * @see SpannerGeneratedDDL#generateTableDDL(Descriptor, SpannerDriverSettings) to spawn one of + * these from regular library or application code. + * @param model Descriptor for the model we are building against. + * @param primaryKey Primary key field name to use for this table by default. + * @param tableName Resolved table name to use for this table. + * @param defaultColumns Default set of columns to use for this table. + * @param settings Active driver settings to apply/consider. + */ + Builder(@Nonnull Descriptor model, + @Nonnull String tableName, + @Nonnull String primaryKey, + @Nonnull List defaultColumns, + @Nonnull SpannerDriverSettings settings) { + this.model = model; + this.tableName = tableName; + this.settings = settings; + this.columns = defaultColumns; + this.primaryKey = primaryKey; + this.keySortDirection = SortDirection.ASC; + this.tableConstraints = Optional.empty(); + this.optimizerVersion = Optional.empty(); + this.versionRetentionPeriod = Optional.empty(); + this.interleaveTarget = Optional.empty(); + } + + /** + * Render column definition statements for a final DDL table create statement. + * + * @return Column definition statements, stacked in a buffer. + */ + @Nonnull String renderColumnStatements() { + return this.columns.stream() + .map(ColumnSpec::render) + .collect(Collectors.joining(", ")); + } + + /** + * Render table-level constraint statements for a final DDL table create statement. + * + * @return Any applicable rendered table constraints. + */ + @Nonnull String renderConstraintStatements() { + return this.tableConstraints.map(constraints -> constraints + .stream() + .map(TableConstraint::render) + .collect(Collectors.joining(", "))) + .orElse(""); + } + + /** + * Render inner statements in the CREATE TABLE DDL statement, including columns and constraints, as applicable. + * If no constraints are present, we simply return the column definitions alone. + * + * @return Rendered definitions of columns and table constraints. + */ + @Nonnull String renderColumnStatementsAndConstraints() { + var columnList = renderColumnStatements(); + if (this.tableConstraints.isPresent()) { + var constraints = renderConstraintStatements(); + return format("%s, %s", columnList, constraints); + } + return columnList; + } + + /** + * Render the primary key specification for a final DDL table create statement. + * + * @return Rendered primary key specification. + */ + @Nonnull String renderPrimaryKey() { + return format( + "%s %s", + this.primaryKey, + this.keySortDirection.name() + ); + } + + /** + * Render the prepared DDL statement details into a statement string which can be passed to Spanner. + * + * @return Rendered DDL statement, according to local object settings. + */ + @Nonnull StringBuilder renderCreateDDLStatement() { + var builder = new StringBuilder(); + var buf = new ArrayList(); + buf.add(new StringBuilder(format( + "CREATE TABLE %s (%s) PRIMARY KEY (%s)", + this.tableName, + this.renderColumnStatementsAndConstraints(), + this.renderPrimaryKey() + ))); + + // add interleave target statement, if specified + this.interleaveTarget.ifPresent(target -> buf.add(target.render())); + + builder.append(join(", ", buf)); + return builder; + } + + /** + * Collapse the builder into an immutable DDL statement container + * + * @return Immutable DDL statement container. + */ + public @Nonnull SpannerGeneratedDDL build() { + var fields = forEachField( + model, + Optional.of(onlySpannerEligibleFields(settings)) + ).map((fieldPointer) -> + ColumnSpec.columnSpecForField(fieldPointer, settings) + ).collect(Collectors.toUnmodifiableList()); + + return new SpannerGeneratedDDL( + tableName, + fields, + model, + renderCreateDDLStatement() + ); + } + + // -- Builder API: Getters -- // + + /** @return Model descriptor this builder wraps. */ + public @Nonnull Descriptor getModel() { + return model; + } + + /** @return Active Spanner driver settings. */ + public @Nonnull SpannerDriverSettings getSettings() { + return settings; + } + + /** @return Generated or resolved Spanner table name. */ + public @Nonnull String getTableName() { + return tableName; + } + + /** @return Primary key column for this model/table. */ + public @Nonnull String getPrimaryKey() { + return primaryKey; + } + + /** @return Set of generated columns for this model in Spanner. */ + public @Nonnull List getColumns() { + return columns; + } + + /** @return Primary key column sort direction. */ + public @Nonnull SortDirection getKeySortDirection() { + return keySortDirection; + } + + /** @return Set of constraints to apply to this table. */ + public @Nonnull Optional> getTableConstraints() { + return tableConstraints; + } + + /** @return Optimizer version to set for this table. */ + public @Nonnull Optional getOptimizerVersion() { + return optimizerVersion; + } + + /** @return Data versioning retention period to set for this table. */ + public @Nonnull Optional getVersionRetentionPeriod() { + return versionRetentionPeriod; + } + + /** @return Parent interleaving target for this table. */ + public @Nonnull Optional getInterleaveTarget() { + return interleaveTarget; + } + + // -- Builder API: Setters -- // + + /** + * Set the sort direction for the primary key column in this table. + * + * @param keySortDirection Key column sort direction. + * @return Self, for chained calls to the builder. + */ + public @Nonnull Builder setKeySortDirection(@Nonnull SortDirection keySortDirection) { + this.keySortDirection = keySortDirection; + return this; + } + + /** + * Set, or clear, the set of table constraints added to this table. + * + * @param tableConstraints Desired table constraints to set or clear, as applicable. + * @return Self, for chained calls to the builder. + */ + public @Nonnull Builder setTableConstraints(@Nonnull Optional> tableConstraints) { + this.tableConstraints = tableConstraints; + return this; + } + + /** + * Set, or clear, the optimizer version to apply when creating this table. + * + * @param optimizerVersion Desired optimizer version to apply, as applicable. + * @return Self, for chained calls to the builder. + */ + public @Nonnull Builder setOptimizerVersion(@Nonnull Optional optimizerVersion) { + this.optimizerVersion = optimizerVersion; + return this; + } + + /** + * Set, or clear, the data versioning retention period for this table. + * + * @param versionRetentionPeriod Desired data versioning retention period, as applicable. + * @return Self, for chained calls to the builder. + */ + public @Nonnull Builder setVersionRetentionPeriod(@Nonnull Optional versionRetentionPeriod) { + this.versionRetentionPeriod = versionRetentionPeriod; + return this; + } + + /** + * Set, or clear, the parent interleave target for this table. + * + * @param interleaveTarget Desired parent interleave target, as applicable. + * @return Self, for chained calls to the builder. + */ + public @Nonnull Builder setInterleaveTarget(@Nonnull Optional interleaveTarget) { + this.interleaveTarget = interleaveTarget; + return this; + } + } + + /** Model that relates to this generated statement. */ + private final @Nonnull Descriptor model; + + /** Resolved name of the table. */ + private final @Nonnull String tableName; + + /** Set of generated columns determined to be part of this table. */ + private final @Nonnull List columns; + + /** Holds the generated query in a string buffer. */ + private final @Nonnull StringBuilder generatedStatement; + + /** + * Private constructor. + * + * @param tableName Name of the generated table. + * @param columns Generated set of columns. + * @param model Model this table corresponds to. + * @param generatedStatement Rendered DDL statement. + */ + private SpannerGeneratedDDL(@Nonnull String tableName, + @Nonnull List columns, + @Nonnull Descriptor model, + @Nonnull StringBuilder generatedStatement) { + this.tableName = tableName; + this.columns = columns; + this.generatedStatement = generatedStatement; + this.model = model; + } + + /** + * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing + * that model's properties. This method variant operates from a full model instance. + * + *

    This method offers no ability to control driver settings. See below if you need alternatives.

    + * + * @see #generateTableDDL(Message, Optional) For control over driver settings, optionally. + * @param instance Model instance to generate a table statement for. + * @return Generated DDL statement object. + */ + public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL(@Nonnull Message instance) { + return generateTableDDL(instance, Optional.of(SpannerDriverSettings.DEFAULTS)); + } + + /** + * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing + * that model's properties. This method variant operates from a full model instance. + * + * @param instance Model instance to generate a table statement for. + * @param settings Settings to employ for the driver. These must align at runtime. + * @return Generated DDL statement object. + */ + public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL( + @Nonnull Message instance, + @Nonnull Optional settings) { + return generateTableDDL( + instance.getDescriptorForType(), + settings.orElse(SpannerDriverSettings.DEFAULTS) + ); + } + + /** + * Given a model definition, produce a generated DDL statement which creates a backing table in Spanner implementing + * that model's properties. + * + * @param model Model schema to generate a table statement for. + * @return Generated DDL statement object. + */ + public static @Nonnull SpannerGeneratedDDL.Builder generateTableDDL( + @Nonnull Descriptor model, + @Nonnull SpannerDriverSettings settings) { + return new SpannerGeneratedDDL.Builder( + model, + resolveTableName(model), + resolveKeyColumn(idField(model).orElseThrow(), settings), + resolveDefaultColumns(model, settings), + settings + ); + } + + /** + * Resolve the default calculated set of Spanner columns for a given model structure. + * + * @param model Model to traverse and generate columns for. + * @return Set of generated and type-resolved columns. + */ + public static @Nonnull List resolveDefaultColumns(@Nonnull Descriptor model, + @Nonnull SpannerDriverSettings settings) { + var keyField = keyField(model).orElseThrow(); + var fieldSet = new LinkedList(); + + // first up: generate the column which implements the model's primary key + fieldSet.add(ColumnSpec.columnSpecForKey( + model, + keyField, + settings + )); + + // next: generate all remaining data columns + forEachField( + model, + Optional.of(onlySpannerEligibleFields(settings)) + ).filter((fieldPointer) -> + // filter out key fields: we'll handle those separately + !keyField.getField().getFullName().equals(fieldPointer.getField().getFullName()) + ).map((fieldPointer) -> + ColumnSpec.columnSpecForField(fieldPointer, settings) + ).forEach(fieldSet::add); + + return Collections.unmodifiableList(fieldSet); + } + + // -- Accessors -- // + + /** @return Model for which this object generates a table create statement. */ + public @Nonnull Descriptor getModel() { + return model; + } + + /** @return Resolved name of the table to be created. */ + public @Nonnull String getTableName() { + return tableName; + } + + /** @return Resolved set of Spanner columns. */ + public @Nonnull List getColumns() { + return columns; + } + + /** @return Rendered generated DDL statement. */ + public @Nonnull StringBuilder getGeneratedStatement() { + return generatedStatement; + } + + @Override + public String toString() { + return "SpannerDDL{" + + "model=" + model.getFullName() + + ", tableName='" + tableName + '\'' + + ", statement=\"" + generatedStatement.toString() + + "\"}"; + } +} diff --git a/java/gust/backend/driver/spanner/SpannerManager.java b/java/gust/backend/driver/spanner/SpannerManager.java new file mode 100644 index 000000000..70260c4a0 --- /dev/null +++ b/java/gust/backend/driver/spanner/SpannerManager.java @@ -0,0 +1,459 @@ +/* + * Copyright © 2020, The Gust Framework Authors. All rights reserved. + * + * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, + * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of + * this code in object or source form requires and implies consent and agreement to that license in principle and + * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of + * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to + * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected + * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, + * is strictly forbidden except in adherence with assigned license requirements. + */ +package gust.backend.driver.spanner; + +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.SpannerOptions; +import com.google.common.util.concurrent.ListeningScheduledExecutorService; +import com.google.protobuf.Empty; +import com.google.protobuf.Message; +import gust.backend.model.CacheDriver; +import gust.backend.model.DatabaseManager; +import gust.backend.runtime.Logging; +import io.micronaut.context.annotation.Factory; +import io.micronaut.runtime.context.scope.Refreshable; +import org.slf4j.Logger; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.ThreadSafe; +import java.io.Closeable; +import java.lang.ref.WeakReference; +import java.util.*; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.atomic.AtomicBoolean; + + +/** + * Main adapter manager interface for interaction between Gust/Elide apps and Google Cloud Spanner, enabling seamless + * persistence for generated {@link Message}-driven models. + * + *

    This {@link DatabaseManager} implementation is backed by a customized {@link SpannerDriver} and + * {@link SpannerAdapter} which manage remote database interactions and cache/transaction state, respectively. While + * these objects may be acquired directly, `SpannerManager` has the added benefit of a generic singleton pattern which + * saves re-cycling of the adapter and driver objects.

    + * + * @see SpannerDriver `SpannerDriver`, the main driver for interacting with Cloud Spanner + * @see SpannerAdapter `SpannerAdapter`, which manages cache/transaction state + */ +@Immutable @ThreadSafe @Refreshable +@SuppressWarnings({"UnstableApiUsage", "rawtypes"}) +public final class SpannerManager + implements DatabaseManager, Closeable, AutoCloseable { + private static final @Nonnull Logger logging = Logging.logger(SpannerManager.class); + + /** Keeps track of configured managers spawned by this manager. */ + private final @Nonnull ConcurrentMap> configuredManagers; + + /** Spanner manager singleton container. */ + private static final class SpannerManagerSingleton { + private SpannerManagerSingleton() { /* Disallow construction. */ } + + // Global singleton. + static volatile @Nullable SpannerManager __singleton = null; + } + + /** + * Default constructor. + */ + private SpannerManager() { + configuredManagers = new ConcurrentSkipListMap<>(); + } + + /** + * Returns the full set of known configured Spanner managers, JVM-wide. This is mostly useful as a utility to shut + * down connections globally when needed (for instance, during testing). + * + * @return Unmodifiable list of weak references to all known-active managers. + */ + public static @Nonnull Collection> allManagers() { + var target = acquire(); + if (logging.isTraceEnabled()) + logging.trace( + "All `SpannerManager` instances requested. Total to return: {}.", + target.configuredManagers.size()); + return Collections.unmodifiableCollection(target.configuredManagers.values()); + } + + /** + * Acquire a singleton instance of the Spanner manager, which can be used safely across threads to interact with + * Google Cloud Spanner, driven by {@link Message}-generated models. + * + *

    The manager can then be used to request instances of {@link SpannerAdapter} specialized to a given model. + * Adapter instances acquired in this way are not guaranteed to be new, and are safe to use across threads.

    + * + * @return Singleton instance of the Spanner manager. + */ + @Factory + public static @Nonnull SpannerManager acquire() { + var singleton = SpannerManagerSingleton.__singleton; + if (singleton == null) { + singleton = new SpannerManager(); + SpannerManagerSingleton.__singleton = singleton; + if (logging.isTraceEnabled()) + logging.trace("Acquired fresh singleton for `SpannerManager` request."); + } + if (logging.isTraceEnabled()) + logging.trace("Acquired recycled singleton for `SpannerManager` request."); + return singleton; + } + + /** + * Close all active Spanner connections tracked or controlled by this manager. + * + * @throws RuntimeException If the underlying connections raise IO exceptions. + */ + @Override + public void close() { + if (logging.isInfoEnabled()) + logging.info("Shutting down all active `SpannerManager` instances..."); + allManagers().forEach((manager) -> { + var target = manager.get(); + if (target != null) { + target.close(); + } + }); + } + + /** Intermediate builder which can gather settings for an eventually-immutable {@link ConfiguredSpannerManager}. */ + public final class Builder { + /** Required/immutable: Main Spanner database this manager will interact with. */ + private final @Nonnull DatabaseId database; + + /** Optional cache driver to use when caching reads. */ + private @Nonnull Optional> cache = Optional.empty(); + + /** Default set of driver settings to use when spawning adapters. */ + private @Nonnull Optional settings = Optional.empty(); + + /** Base set of options to use when spawning Spanner clients via this manager. */ + private @Nonnull Optional options = Optional.empty(); + + /** Custom executor to use for adapters spawned via this builder. */ + private @Nonnull Optional executor = Optional.empty(); + + /** Private constructor. */ + Builder(@Nonnull DatabaseId database) { + this.database = database; + } + + // -- Builder Getters -- // + + /** @return Spanner database which the target manager will be bound to. */ + public @Nonnull DatabaseId getDatabase() { + return database; + } + + /** @return Cache adapter, if any, to apply when acquiring adapters/drivers through the target manager. */ + public @Nonnull Optional> getCache() { + return cache; + } + + /** @return Custom driver settings to apply when acquiring adapters/drivers through the target manager. */ + public @Nonnull Optional getSettings() { + return settings; + } + + /** @return Base set of Spanner options to apply when spawning Spanner clients from this manager. */ + public @Nonnull Optional getOptions() { + return options; + } + + /** @return Custom executor to apply when acquiring adapters/drivers through the target manager. */ + public @Nonnull Optional getExecutor() { + return executor; + } + + // -- Builder Setters -- // + + /** + * Set the cache for the target configured Spanner manager. + * + * @param cache Cache to employ, or none if {@link Optional#empty()}. + * @return Self, for chainability. + */ + public @Nonnull Builder setCache(@Nonnull Optional> cache) { + this.cache = cache; + return this; + } + + /** + * Set the main driver settings to use when spawning adapters in the target configured Spanner manager. + * + * @param settings Driver settings to apply. + * @return Self, for chainability. + */ + public @Nonnull Builder setSettings(@Nonnull Optional settings) { + this.settings = settings; + return this; + } + + /** + * Set the base package of Spanner client options to use when spawning new clients via the target configured + * Spanner manager. + * + * @param options Spanner client options to apply, or none if {@link Optional#empty()}. + * @return Self, for chainability. + */ + public @Nonnull Builder setOptions(@Nonnull Optional options) { + this.options = options; + return this; + } + + /** + * Set the executor used by adapters and drivers spawned by this manager. + * + * @param executor Executor to use when spawning adapters and drivers. + * @return Self, for chainability. + */ + public @Nonnull Builder setExecutor(@Nonnull Optional executor) { + this.executor = executor; + return this; + } + + /** + * Build this builder into a configured and immutable {@link ConfiguredSpannerManager} instance, capable of + * producing managed {@link SpannerAdapter}s specialized to {@link Message} instances. + * + * @return Configured and immutable Spanner manager. + */ + public @Nonnull ConfiguredSpannerManager build() { + var assignedId = configuredManagers.size(); + var manager = new ConfiguredSpannerManager( + assignedId, + database, + cache, + options, + executor, + settings + ); + + try { + configuredManagers.put( + assignedId, + new WeakReference<>(manager) + ); + return manager; + } finally { + WeakReference.reachabilityFence(manager); + } + } + } + + /** + * Configure a vanilla Spanner manager instance for a given database. + * + * @param database Spanner database. + * @return Spanner manager builder. + */ + public @Nonnull Builder configureForDatabase(@Nonnull DatabaseId database) { + return new Builder(database); + } + + /** + * Represents a configured version of a central {@link SpannerManager}, which has been sealed for immutable use at + * runtime. Once built via {@link Builder}, fields on a configured manager cannot change. + */ + @Immutable @ThreadSafe @Refreshable + public final class ConfiguredSpannerManager implements + DatabaseManager, Closeable, AutoCloseable { + /** Main cache of adapters generated for concrete models. */ + private final @Nonnull ConcurrentMap> adapterCache; + + /** Database we should interact with. */ + private final @Nonnull DatabaseId database; + + /** Settings to apply to Spanner clients derived from this manager. */ + private final @Nonnull Optional baseOptions; + + /** Settings to apply to spawned adapters/drivers. */ + private final @Nonnull Optional settings; + + /** Custom executor service to apply, if any, to spawned adapters/drivers from this manager. */ + private final @Nonnull Optional executorService; + + /** Cache to apply, if any, when reading cacheable models. */ + private final @Nonnull Optional> cache; + + /** Whether this configured manager has closed, in which case it cannot spawn adapters or operations. */ + private final @Nonnull AtomicBoolean closed = new AtomicBoolean(false); + + /** ID assigned to this configured Spanner manager. */ + private final int id; + + /** + * Package-private constructor. + * + * @param id Assigned ID for this manager. + * @param database Database we should bind the resulting Spanner manager to. + * @param cache Optional cache adapter to apply to this one. + * @param baseOptions Base Spanner driver option set to apply. + * @param executorService Optional custom executor service to use. + * @param settings Settings to apply when spawning adapters with this manager. + */ + ConfiguredSpannerManager(int id, + @Nonnull DatabaseId database, + @Nonnull Optional> cache, + @Nonnull Optional baseOptions, + @Nonnull Optional executorService, + @Nonnull Optional settings) { + this.database = Objects.requireNonNull(database); + this.adapterCache = new ConcurrentSkipListMap<>(); + this.executorService = executorService; + this.baseOptions = baseOptions; + this.settings = settings; + this.cache = cache; + this.id = id; + } + + /** + * Acquire a generic adapter instance designed to work with all {@link Message}-inheriting model types. + * + *

    Adapter instances and backing drivers acquired via this route are not guaranteed to be new, which in most + * a performance benefit with negligible costs. Since adapters and drivers are required to be threadsafe, they + * can be re-used safely with no internal state involved.

    + * + * @see #adapter(Message, Message) + * @return Generic Spanner adapter instance. + */ + @Factory + public @Nonnull SpannerAdapter generic() { + return adapter(Empty.getDefaultInstance(), Empty.getDefaultInstance()); + } + + /** + * Acquire a typed adapter instance specialized to the provided key and model types, which should derive from + * schema-driven {@link Message} classes. + * + *

    Adapters and backing drivers acquired via this route are not guaranteed to be new, which in most cases is + * a performance benefit with negligible costs. Since adapters and drivers are required to be threadsafe, they + * can be re-used safely with no internal state involved.

    + * + *

    Alternatively, drivers/adapters can also be acquired directly, via methods like + * {@link SpannerAdapter#acquire(Message, Message, DatabaseId)} and friends.

    + * + * @param keyInstance Model key instance for which a specialized adapter should be returned. + * @param modelInstance Model object instance for which a specialized adapter should be returned. + * @param Key type to which the adapter will be specialized. + * @param Model type to which the adapter will be specialized. + * @throws IllegalArgumentException If the provided key or model instance is not duly marked as a key. + * @throws IllegalStateException If the provided key or model instance is not duly marked with a table name. + * @return New or recycled model adapter instance for the provided key and model types. + */ + @Factory + @SuppressWarnings("unchecked") + public @Nonnull SpannerAdapter adapter( + @Nonnull Key keyInstance, + @Nonnull Model modelInstance) { + Objects.requireNonNull(keyInstance); + Objects.requireNonNull(modelInstance); + if (this.getClosed()) + throw new IllegalStateException("Cannot spawn adapters with a closed manager."); + if (logging.isDebugEnabled()) + logging.info("Acquiring `SpannerAdapter` for model '{}' (key: '{}').", + modelInstance.getDescriptorForType().getFullName(), + keyInstance.getDescriptorForType().getFullName()); + + var modelFingerprint = keyInstance.getDescriptorForType().getFullName().concat( + modelInstance.getDescriptorForType().getFullName() + ).hashCode(); + if (logging.isTraceEnabled()) + logging.info("Model fingerprint for desired adapter: {}.", modelFingerprint); + + if (!adapterCache.containsKey(modelFingerprint)) { + if (logging.isTraceEnabled()) + logging.info("No cached adapter. Spawning new one for fingerprint '{}'...", modelFingerprint); + + // spawn a new adapter, place it in the cache + SpannerAdapter adapter = (SpannerAdapter)SpannerAdapter.acquire( + keyInstance, + modelInstance, + database, + executorService, + settings, + baseOptions, + cache + ); + adapterCache.put(modelFingerprint, adapter); + return adapter; + } else if (logging.isTraceEnabled()) { + logging.info("Cached adapter found for fingerprint '{}'. Returning.", modelFingerprint); + } + return (SpannerAdapter)adapterCache.get(modelFingerprint); + } + + /** @return Database bound to this manager. */ + public @Nonnull DatabaseId getDatabase() { + return database; + } + + /** @return Closed/open state of this manager. */ + public boolean getClosed() { + return closed.get(); + } + + /** + * Returns the full set of known configured Spanner managers, JVM-wide. This is mostly useful as a utility to + * shut down connections globally when needed (for instance, during testing). + * + * @return Unmodifiable list of weak references to all known-active managers. + */ + public @Nonnull Collection allAdapters() { + if (logging.isTraceEnabled()) + logging.trace("All adapters requested from 'SpannerManager' at ID '{}'. Total: {}.", + this.id, + this.adapterCache.size()); + return Collections.unmodifiableCollection(this.adapterCache.values()); + } + + /** + * Close all active Spanner connections tracked or controlled by this configured manager. + * + * @throws RuntimeException If the underlying connections raise IO exceptions. + */ + @Override + public void close() { + if (logging.isTraceEnabled()) + logging.trace("Close requested for `SpannerManager` at ID '{}'.", this.id); + if (this.getClosed()) { + if (logging.isDebugEnabled()) + logging.debug("Close requested, but but `SpannerManager` at ID '{}' is already closed.", this.id); + return; + } + try { + if (logging.isInfoEnabled()) + logging.info("Closing `SpannerManager` at ID '{}'.", this.id); + closed.compareAndSet(false, true); + allAdapters().forEach(SpannerAdapter::close); + } finally { + adapterCache.clear(); + configuredManagers.remove(this.id); // deregister self + } + } + + // -- Configured Manager: Getters -- // + + /** @return Settings for this configured manager. */ + public @Nonnull SpannerDriverSettings getSettings() { + return settings.orElse(SpannerDriverSettings.DEFAULTS); + } + + /** @return Cache applied to reads, if any. */ + public @Nonnull Optional> getCache() { + return cache; + } + } +} diff --git a/java/gust/backend/driver/spanner/SpannerMutationSerializer.java b/java/gust/backend/driver/spanner/SpannerMutationSerializer.java new file mode 100644 index 000000000..5ecb5a068 --- /dev/null +++ b/java/gust/backend/driver/spanner/SpannerMutationSerializer.java @@ -0,0 +1,421 @@ +/* + * Copyright © 2020, The Gust Framework Authors. All rights reserved. + * + * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, + * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of + * this code in object or source form requires and implies consent and agreement to that license in principle and + * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of + * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to + * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected + * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, + * is strictly forbidden except in adherence with assigned license requirements. + */ +package gust.backend.driver.spanner; + +import com.google.cloud.ByteArray; +import com.google.cloud.Date; +import com.google.cloud.Timestamp; +import com.google.cloud.spanner.*; +import com.google.common.annotations.VisibleForTesting; +import com.google.protobuf.ByteString; +import com.google.protobuf.Descriptors; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; +import com.google.protobuf.util.JsonFormat; +import gust.backend.model.ModelDeflateException; +import gust.backend.model.ModelSerializer; +import gust.backend.runtime.Logging; +import org.slf4j.Logger; +import tools.elide.core.FieldType; +import tools.elide.core.SpannerFieldOptions; +import tools.elide.core.SpannerOptions; +import tools.elide.core.TableFieldOptions; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; + +import static gust.backend.driver.spanner.SpannerUtil.*; +import static gust.backend.model.ModelMetadata.*; + + +/** + * Implements a specialized serializer, capable of converting generated {@link Message}-derived objects into Spanner + * {@link Mutation} records during write operations. + * + * @see SpannerStructDeserializer For an equivalent specialized de-serializer, working atop Spanner {@link Struct}s. + * @param Typed {@link Message} which implements a concrete model object structure, as defined and annotated by + * the core Gust annotations. + */ +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public final class SpannerMutationSerializer implements ModelSerializer { + private static final Logger logging = Logging.logger(SpannerMutationSerializer.class); + private static final JsonFormat.Printer defaultJsonPrinter = JsonFormat + .printer() + .includingDefaultValueFields() + .omittingInsignificantWhitespace() + .sortingMapKeys(); + + /** Model structure for the backing instance. */ + private final @Nonnull Descriptors.Descriptor model; + + /** Settings for the Spanner driver itself. */ + private final @Nonnull SpannerDriverSettings driverSettings; + + /** Target mutation we intend to initialize. */ + private volatile @Nullable Mutation.WriteBuilder target = null; + + /** + * Private constructor. + * + * @param instance Default instance for the model we intend to serialize. + * @param driverSettings Settings for the Spanner driver itself. + */ + SpannerMutationSerializer(@Nonnull Model instance, + @Nonnull SpannerDriverSettings driverSettings) { + this.driverSettings = driverSettings; + this.model = instance.getDescriptorForType(); + } + + /** + * Initialize a new mutation cycle for the provided mutation builder object. We hold this object and fill it in when + * the next serialization call occurs. After serialization, the value is cleared for later use. + * + * @param initial In-progress mutation to initialize. + */ + @Nonnull SpannerMutationSerializer initializeMutation(@Nonnull Mutation.WriteBuilder initial) { + target = initial; + return this; + } + + /** + * Inject a model instance's key ID at the expected place in a given Spanner {@link Mutation} target, given any + * annotations present on the key model field. + * + *

    Keys in Gust are implemented as objects. Typically these keys have a property present on them which is itself + * annotated as an ID property. This method makes sure that we collapse that ID into the key's column in the table, + * which should store a native value (i.e. an ID string or number) rather than an encoded message or sub-collection + * entry, which is the norm for sub-messages outside of special cases like keys, timestamps, and dates.

    + * + * @param keyField Pointer to the key field on the model. + * @param instance Message instance where we should pluck the key/ID from. + * @param target Mutation target where we should write the resulting ID value. + * @throws IllegalStateException For invalid key types. Only `STRING` and `INT64` are supported as column types in + * Spanner for primary keys. + */ + @VisibleForTesting + void collapseRowKey(@Nonnull FieldPointer keyField, + @Nonnull Message instance, + @Nonnull Mutation.WriteBuilder target) { + var idField = idField(instance).orElseThrow(); + var id = id(instance).orElseThrow(); + var column = resolveKeyColumn(idField, driverSettings); + var valueBinder = target.set(column); + var type = resolveKeyType(idField); + + if (type.getCode() == Type.Code.STRING) { + valueBinder.to((String)id); + } else if (type.getCode() == Type.Code.INT64) { + valueBinder.to((Long)id); + } else { + throw new IllegalStateException( + String.format("Unsupported key field type: '%s'.", keyField.getField().getType().name())); + } + } + + @SuppressWarnings("unchecked") + void bindValueTyped(@Nonnull Descriptors.FieldDescriptor field, + @Nonnull ValueBinder valueBinder, + @Nonnull Type columnType, + @Nonnull Object rawValue, + @Nonnull Optional spannerOpts, + @Nonnull Optional columnOpts) { + boolean repeated = columnType.getCode() == Type.Code.ARRAY; + Objects.requireNonNull(rawValue, "should never get `NULL` for present protocol buffer value"); + + Type innerType = repeated ? + columnType.getArrayElementType() : + columnType; + + switch (innerType.getCode()) { + case BOOL: + if (repeated) { + valueBinder.toBoolArray((Iterable)rawValue); + } else { + valueBinder.to((Boolean)rawValue); + } + break; + + case INT64: + // we need to check if it's an ENUM, and if ENUMs-as-strings is off. if both of these conditions + // match, we need to resolve the enumerated instance and assign that instead. + if (field.getType() == Descriptors.FieldDescriptor.Type.ENUM && + driverSettings.enumsAsNumbers()) { + var descriptor = (Descriptors.EnumValueDescriptor)rawValue; + valueBinder.to(descriptor.getNumber()); + + } else { + // otherwise, we should treat it as a native numeric type, repeated or singular. + if (repeated) { + valueBinder.toInt64Array((Iterable) rawValue); + } else { + if (rawValue instanceof Long) { + valueBinder.to((Long) rawValue); + } else { + valueBinder.to((Integer) rawValue); + } + } + } + break; + + case FLOAT64: + if (repeated) { + valueBinder.toFloat64Array((Iterable)rawValue); + } else { + valueBinder.to((Double)rawValue); + } + break; + + case STRING: + // special case: JSON fields + if (field.getType() == Descriptors.FieldDescriptor.Type.MESSAGE && + (spannerOpts.isPresent() && spannerOpts.get().getType() == SpannerOptions.SpannerType.JSON) || + (columnOpts.isPresent() && columnOpts.get().getSptype() == SpannerOptions.SpannerType.JSON)) { + // it's a repeated JSON field, so serialize it as an array of sub-messages instead of strings. + try { + if (repeated) { + var arr = new LinkedList(); + for (var encodableModel : (Iterable) rawValue) { + arr.add(defaultJsonPrinter.print(encodableModel)); + } + valueBinder.toStringArray(arr); + } else { + // it's a singular JSON field, so serialize it as a sub-message instead of a string. + valueBinder.to(defaultJsonPrinter.print((Message)rawValue)); + } + } catch (InvalidProtocolBufferException ipbe) { + logging.error("!! Invalid protocol buffer for JSON encoding.", ipbe); + throw new IllegalStateException(ipbe); + } + } else { + // we need to check if it's an ENUM, and if ENUMs-as-strings is on. if both of these conditions + // match, we need to resolve the enumerated instance and assign that instead. + if (field.getType() == Descriptors.FieldDescriptor.Type.ENUM && + !driverSettings.enumsAsNumbers()) { + var descriptor = (Descriptors.EnumValueDescriptor)rawValue; + valueBinder.to(descriptor.getName()); + + } else { + // it's not a JSON field or an enum, or enum serialization as strings isn't on, so serialize it + // as a regular string value (either singular or repeated). + if (repeated) { + valueBinder.toStringArray((Iterable)rawValue); + } else { + valueBinder.to((String)rawValue); + } + } + } + break; + + case BYTES: + if (repeated) { + var arr = new LinkedList(); + for (var bytes : (Iterable) rawValue) { + arr.add(ByteArray.copyFrom(bytes.asReadOnlyByteBuffer())); + } + valueBinder.toBytesArray(arr); + } else { + valueBinder.to(ByteArray.copyFrom(((ByteString)rawValue).asReadOnlyByteBuffer())); + } + break; + + case STRUCT: + throw new IllegalArgumentException(String.format( + "STRUCT types are expressions and are not valid for storage. Please use either a `JSON` field or " + + "valid sub-collection binding, at field path '%s'.", + field.getFullName() + )); + + case TIMESTAMP: + if (com.google.protobuf.Timestamp.getDescriptor() + .getFullName() + .equals(field.getMessageType().getFullName())) { + if (repeated) { + var arr = new LinkedList(); + for (var ts : (Iterable) rawValue) { + arr.add(SpannerTemporalConverter.cloudTimestampFromProto(ts)); + } + valueBinder.toTimestampArray(arr); + } else { + valueBinder.to(SpannerTemporalConverter.cloudTimestampFromProto( + ((com.google.protobuf.Timestamp)rawValue))); + } + break; + } + + // any sub-message type other than `Timestamp` is invalid. + throw new IllegalStateException( + "Type 'TIMESTAMP' is not yet supported for use with record " + + "'" + field.getMessageType().getFullName() + "'."); + + case DATE: + if (com.google.type.Date.getDescriptor() + .getFullName() + .equals(field.getMessageType().getFullName())) { + if (repeated) { + var arr = new LinkedList(); + for (var date : (Iterable) rawValue) { + arr.add(SpannerTemporalConverter.cloudDateFromProto(date)); + } + valueBinder.toDateArray(arr); + } else { + valueBinder.to(SpannerTemporalConverter.cloudDateFromProto( + ((com.google.type.Date)rawValue))); + } + break; + } + + // any sub-message type other than `Date` is invalid. + throw new IllegalStateException( + "Type 'DATE' is not yet supported for use with record " + + "'" + field.getMessageType().getFullName() + "'."); + + case NUMERIC: + // source fields for `NUMERIC` columns can only be strings. while we could safely store 32-bit/64-bit + // signed or unsigned (fixed or unfixed) integer types, and float/double types, safely in this field, + // we cannot safely decode them from this field, which may break the `long` and `short` ceilings. so, + // following Google's advice on the matter (https://cloud.google.com/spanner/docs/working-with-numerics) + // they are implemented as strings. + if (field.getType() == Descriptors.FieldDescriptor.Type.STRING) { + valueBinder.to(Value.string((String)rawValue)); + break; + } + + // throw illegal state + throw new IllegalStateException( + "NUMERIC fields must be expressed as proto-strings, in exponent notation if necessary. For " + + "more information, please see the Cloud Spanner documentation regarding NUMERIC types: " + + "https://cloud.google.com/spanner/docs/working-with-numerics"); + + case ARRAY: throw new IllegalStateException("Illegal array received for flattened serialization."); + } + } + + /** + * Collapse an individual {@link Message} field into the expected column slow against a given Spanner + * {@link Mutation} record, which is in the process of being assembled. + * + *

    Each individual model field which is eligible for storage in Spanner must be resolvable to a valid column name + * and type. This process is usually conducted via model annotations, with sensible defaults built in. Before a + * value can be duly bound, this method resolves such ancillary values and feeds them into + * {@link #bindValueTyped(Descriptors.FieldDescriptor, ValueBinder, Type, Object, Optional, Optional)}, where the + * binding itself takes place.

    + * + * @see #bindValueTyped(Descriptors.FieldDescriptor, ValueBinder, Type, Object, Optional, Optional) Inner post-check + * typed value injection. + * @param instance Model instance we should pluck the field value from. + * @param fieldPointer Resolved pointer to the model field we are collapsing into a column value. + * @param target Mutation target we should write the resulting value to, as applicable. + */ + @VisibleForTesting + void collapseColumnField(@Nonnull Model instance, + @Nonnull FieldPointer fieldPointer, + @Nonnull Mutation.WriteBuilder target) { + var field = fieldPointer.getField(); + var fieldValue = pluck(instance, fieldPointer.getName()); + + if (!field.isRepeated() && !instance.hasField(field) || fieldValue.getValue().isEmpty() || + field.isRepeated() && instance.getRepeatedFieldCount(field) < 1) { + // field has no value. skip, but log about it. + if (logging.isTraceEnabled()) + logging.trace( + "Field '{}' on model '{}' had no value. Skipping.", + field, + model.getFullName() + ); + return; + } + + // virtualize the key property, when encountered + if (!field.isRepeated() && matchFieldAnnotation(field, FieldType.KEY)) { + this.collapseRowKey(fieldPointer, instance, target); + return; + } else if (field.isRepeated() && matchFieldAnnotation(field, FieldType.KEY)) { + throw new IllegalStateException( + "Cannot make `KEY` field repeated (on model '" + field.getMessageType().getFullName() + "'." + ); + } + + // resolve any column or spanner options + var columnOpts = columnOpts(fieldPointer); + var spannerOpts = spannerOpts(fieldPointer); + + // resolve column name... + var columnName = resolveColumnName( + fieldPointer, + spannerOpts, + columnOpts, + driverSettings + ); + + // then type... + var columnType = resolveColumnType( + fieldPointer, + spannerOpts, + columnOpts, + driverSettings + ); + + // then raw value... + var valueBinder = target.set(columnName); + var rawValue = fieldValue.getValue().orElseThrow(); + + bindValueTyped( + field, + valueBinder, + columnType, + rawValue, + spannerOpts, + columnOpts + ); + } + + /** + * Construct a {@link Mutation} serializer for the provided instance. + * + * @param instance Model instance to acquire a mutation serializer for. + * @param driverSettings Settings for the Spanner driver itself. + * @param Model type to deserialize. + * @return Snapshot deserializer instance. + */ + @SuppressWarnings("SameParameterValue") + static SpannerMutationSerializer forModel(@Nonnull M instance, + @Nonnull SpannerDriverSettings driverSettings) { + return new SpannerMutationSerializer<>( + instance, + driverSettings + ); + } + + /** @inheritDoc */ + @Override + public @Nonnull Mutation deflate(@Nonnull Model input) throws ModelDeflateException { + // `initializeMutation` must be called before `deflate`. it is force-emptied each cycle to prevent + var writeBuilder = this.target; + Objects.requireNonNull(input, "cannot deflate `null` input for Spanner mutation"); + Objects.requireNonNull(writeBuilder, "cannot deflate model with no initialized write target."); + + // stream all non-recursive model fields, filtering down only to fields which are eligible for storage in + // Spanner. for each field, invoke `collapseColumnField`, which mutates the held builder in-place. + forEachField( + model, + Optional.of(onlySpannerEligibleFields(driverSettings)) + ).forEach((field) -> + this.collapseColumnField(input, field, writeBuilder) + ); + + final Mutation mutation = writeBuilder.build(); + this.target = null; + return mutation; + } +} diff --git a/java/gust/backend/driver/spanner/SpannerStructDeserializer.java b/java/gust/backend/driver/spanner/SpannerStructDeserializer.java new file mode 100644 index 000000000..d6e5c8bc8 --- /dev/null +++ b/java/gust/backend/driver/spanner/SpannerStructDeserializer.java @@ -0,0 +1,525 @@ +/* + * Copyright © 2020, The Gust Framework Authors. All rights reserved. + * + * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, + * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of + * this code in object or source form requires and implies consent and agreement to that license in principle and + * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of + * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to + * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected + * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, + * is strictly forbidden except in adherence with assigned license requirements. + */ +package gust.backend.driver.spanner; + +import com.google.cloud.spanner.Mutation; +import com.google.cloud.spanner.Struct; +import com.google.cloud.spanner.Type; +import com.google.cloud.spanner.Value; +import com.google.protobuf.Descriptors; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; +import com.google.protobuf.Timestamp; +import com.google.protobuf.util.JsonFormat; +import com.google.type.Date; +import gust.backend.model.ModelDeserializer; +import gust.backend.model.ModelInflateException; +import gust.backend.model.ModelMetadata; +import gust.backend.runtime.Logging; +import org.slf4j.Logger; +import tools.elide.core.FieldType; +import tools.elide.core.SpannerOptions; + +import javax.annotation.Nonnull; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.ThreadSafe; +import java.util.*; +import java.util.stream.Collectors; + +import static gust.backend.driver.spanner.SpannerTemporalConverter.*; +import static gust.backend.driver.spanner.SpannerUtil.*; +import static gust.backend.model.ModelMetadata.*; + +import static java.lang.String.format; + + +/** + * Implements a specialized de-serializer, capable of converting runtime-inhabited Spanner {@link Struct}-records into + * typed {@link Message}-derived objects. + * + * @see SpannerMutationSerializer For an equivalent specialized serializer, working atop Spanner {@link Mutation}s. + * @param Typed {@link Message} which implements a concrete model object structure, as defined and annotated by + * the core Gust annotations. + */ +@Immutable +@ThreadSafe +public final class SpannerStructDeserializer implements ModelDeserializer { + private static final Logger logging = Logging.logger(SpannerStructDeserializer.class); + private static final @Nonnull JsonFormat.Parser defaultJsonParser = JsonFormat.parser() + .ignoringUnknownFields(); + + /** Encapsulated object deserializer. */ + private final Model defaultInstance; + + /** Descriptor for the root model we're decoding. */ + private final Descriptors.Descriptor modelDescriptor; + + /** Settings for the Spanner driver. */ + private final @Nonnull SpannerDriverSettings driverSettings; + + /** + * Private constructor. + * + * @param instance Model instance to deserialize. + * @param driverSettings Settings for the Spanner driver. + */ + private SpannerStructDeserializer(@Nonnull Model instance, + @Nonnull SpannerDriverSettings driverSettings) { + this.defaultInstance = instance; + this.driverSettings = driverSettings; + this.modelDescriptor = instance.getDescriptorForType(); + } + + /** + * Construct a {@link Struct} deserializer for the provided instance. + * + * @param instance Model instance to acquire a data deserializer for. + * @param driverSettings Settings for the Spanner driver itself. + * @param Model type to deserialize. + * @return Snapshot deserializer instance. + */ + @SuppressWarnings("SameParameterValue") + public static SpannerStructDeserializer forModel( + @Nonnull M instance, + @Nonnull SpannerDriverSettings driverSettings) { + return new SpannerStructDeserializer<>( + instance, + driverSettings + ); + } + + /** + * Inflate a field from a Spanner row ID into a message/model Key instance, with the provided value object. + * + *

    The provided field pointer is used to fill in the primary key ID pulled from a given Spanner row result. That + * ID is spliced into the key record, which is then spliced into the target builder.

    + * + * @param target Target message builder. + * @param value Resolved value for the ID. + */ + private void inflateRowKey(@Nonnull Message.Builder target, + @Nonnull Value value) { + Objects.requireNonNull(value, "cannot inflate Spanner key from NULL value"); + var baseInstance = target.getDefaultInstanceForType(); + var keyField = keyField(baseInstance).orElseThrow(); + var idField = idField(baseInstance).orElseThrow(); + + var keyBuilder = target.newBuilderForField(keyField.getField()); + var keyType = resolveKeyType(idField); + + // splice the ID into the key + if (keyType.getCode() == Type.Code.STRING) { + spliceIdBuilder(keyBuilder, Optional.of(value.getString())); + } else if (keyType.getCode() == Type.Code.INT64) { + // special case: stringify if so instructed + if (idField.getField().getType().equals(Descriptors.FieldDescriptor.Type.STRING)) { + spliceIdBuilder(keyBuilder, Optional.of(String.valueOf(value.getInt64()))); + } else { + spliceIdBuilder(keyBuilder, Optional.of(value.getInt64())); + } + } else { + throw new IllegalStateException(format("Unsupported key type: '%s'.", keyType.getCode().name())); + } + + // splice the key into the model + target.setField(keyField.getField(), keyBuilder.build()); + } + + /** + * Resolve a Cloud Spanner column and value for the provided model field, from the source row structure, if one + * is present. + * + *

    If the column has no value, it is skipped. If the column has a value present, it is decoded according to the + * Cloud Spanner {@link Type} specified for the column, each of which are mapped to primitive Protocol Buffer + * object field types.

    + * + * @param target Target protocol buffer builder to fill in. + * @param source Row result from Spanner to fill from. + * @param fieldPointer Field we are resolving from the proto. + */ + private void convergeColumnField(@Nonnull Message.Builder target, + @Nonnull Struct source, + @Nonnull ModelMetadata.FieldPointer fieldPointer) { + // resolve any generic column options and spanner extension options + var columnOpts = columnOpts(fieldPointer); + var spannerOpts = spannerOpts(fieldPointer); + var columnName = resolveColumnName(fieldPointer.getField(), driverSettings); + + if (source.isNull(columnName)) { + if (logging.isTraceEnabled()) + logging.trace("Resolved column value for field '{}' was NULL. Skipping.", + fieldPointer.getName()); + } else { + // resolve the expected column index + var columnValue = resolveColumnValue( + source, + fieldPointer, + spannerOpts, + columnOpts, + driverSettings + ); + + // first up, check to see if this is a key or ID field, and decode it properly if so + var field = fieldPointer.getField(); + if (matchFieldAnnotation(field, FieldType.ID)) { + this.inflateRowKey(target, columnValue); + return; + } else if (matchFieldAnnotation(field, FieldType.KEY)) { + throw new IllegalStateException("Should not get KEY-type fields in convergence loop."); + } + + // if so directed, check the expected type against the real type indicated by Spanner. if this + // feature is turned off, soft logging errors turn into value exceptions. + if (driverSettings.checkExpectedTypes()) { + // resolve the expected column type + var columnType = resolveColumnType( + fieldPointer, + spannerOpts, + columnOpts, + driverSettings + ); + + if (logging.isTraceEnabled()) + logging.trace("Resolved Spanner type for field '{}': '{}'", + fieldPointer.getName(), + columnType.toString()); + + if (!columnType.getCode().equals(columnValue.getType().getCode())) { + logging.error( + "Type mismatch: field '{}' expected type {}, but got {}.", + fieldPointer.getField().getFullName(), + columnType.getCode().name(), + columnValue.getType().getCode().name() + ); + return; + } + } + + // if we make it this far, the following conditions are true, and we are ready to copy a value from + // the source struct into the proto: + // + // 1) we have a non-NULL value in Spanner for a given column, with a resolved name and type. + // 2) the name and type match the proto model, where we also have a resolved proto native type. + // 3) we are certainly operating on a leaf field. + boolean repeated = columnValue.getType().getCode() == Type.Code.ARRAY; + Type.Code innerType = repeated ? + columnValue.getType().getArrayElementType().getCode() : + columnValue.getType().getCode(); + + switch (innerType) { + case BOOL: + spliceBuilder( + target, + fieldPointer, + Optional.of(repeated ? columnValue.getBoolArray() : columnValue.getBool()) + ); + break; + case INT64: + spliceBuilder( + target, + fieldPointer, + Optional.of(repeated ? columnValue.getInt64Array() : columnValue.getInt64()) + ); + break; + + case FLOAT64: + spliceBuilder( + target, + fieldPointer, + Optional.of(repeated ? columnValue.getFloat64Array() : columnValue.getFloat64()) + ); + break; + + case STRING: + // special case: string fields containing model-compliant JSON + if (fieldPointer.getField().getType() == Descriptors.FieldDescriptor.Type.MESSAGE && + (spannerOpts.isPresent() && spannerOpts.get().getType() == SpannerOptions.SpannerType.JSON || + columnOpts.isPresent() && columnOpts.get().getSptype() == SpannerOptions.SpannerType.JSON)) { + int modelIndex = 0; + try { + if (repeated) { + // decode as a list of JSON-encoded model instances. + var encodedModels = columnValue.getStringArray(); + var modelResults = new ArrayList<>(encodedModels.size()); + for (var encodedModel : encodedModels) { + var subBuilder = target.newBuilderForField(fieldPointer.getField()); + defaultJsonParser.merge(encodedModel, subBuilder); + modelResults.add(subBuilder.build()); + modelIndex++; + } + + // mount set of models on the target proto + spliceBuilder( + target, + fieldPointer, + Optional.of(modelResults) + ); + + } else { + // decode as a singular JSON-encoded model instance. + var encodedModel = columnValue.getString(); + var subBuilder = target.newBuilderForField(fieldPointer.getField()); + defaultJsonParser.merge(encodedModel, subBuilder); + spliceBuilder( + target, + fieldPointer, + Optional.of(subBuilder.build()) + ); + } + + } catch (InvalidProtocolBufferException invalidProtoException) { + if (repeated) { + logging.error(format( + "Failed to deserialize JSON model at path '%s', at index %s.", + fieldPointer.getField().getFullName(), + modelIndex + ), invalidProtoException); + } else { + logging.error(format( + "Failed to deserialize JSON model at path '%s'.", + fieldPointer.getField().getFullName() + ), invalidProtoException); + } + + throw new IllegalStateException(invalidProtoException); + } + } else { + spliceBuilder( + target, + fieldPointer, + Optional.of(repeated ? columnValue.getStringArray() : columnValue.getString()) + ); + } + break; + + case BYTES: + spliceBuilder( + target, + fieldPointer, + Optional.of(repeated ? columnValue.getBytesArray() : columnValue.getBytes()) + ); + break; + + case NUMERIC: + if (field.getType() == Descriptors.FieldDescriptor.Type.STRING) { + // grab the string value and splice + spliceBuilder( + target, + fieldPointer, + Optional.of(repeated ? columnValue.getStringArray() : columnValue.getString()) + ); + break; + } + + // throw illegal state + throw new IllegalStateException( + "NUMERIC fields must be expressed as proto-strings, in exponent notation if necessary. For " + + "more information, please see the Cloud Spanner documentation regarding NUMERIC types: " + + "https://cloud.google.com/spanner/docs/working-with-numerics"); + + case TIMESTAMP: + // extract timestamp value + var timestampValue = columnValue.getTimestamp(); + + switch (field.getType()) { + case UINT64: + case FIXED64: + // we're being asked to put a Google Cloud timestamp record into an unsigned integer field, + // with a `long`-style size. this is the only possible safe native conversion. + spliceBuilder( + target, + fieldPointer, + Optional.of((timestampValue.getSeconds() * 1000) + timestampValue.getNanos()) + ); + break; + + case STRING: + // we're being asked to put a Google Cloud timestamp record into a string field. in this + // case, the adapter leverages any date options or otherwise defaults to ISO8601. + spliceBuilder( + target, + fieldPointer, + Optional.of(timestampValue.toString()) + ); + break; + + case MESSAGE: + // if we have a sub-message in the same spot as a timestamp, it's worth checking to see if + // it's a native Google Cloud timestamp, in which case we can just use it. otherwise, if we + // encounter a standard PB timestamp, we need to convert. + if (Timestamp.getDescriptor().getFullName().equals(field.getMessageType().getFullName())) { + spliceBuilder( + target, + fieldPointer, + Optional.of(protoTimestampFromCloud(timestampValue)) + ); + break; + } + + // any other sub-message type represents an illegal state. + throw new IllegalStateException( + "Cannot convert Spanner TIMESTAMP value to unsupported sub-message type " + + "'" + field.getMessageType().getFullName() + "'." + ); + + default: + // any other expressed field represents an illegal state. + throw new IllegalStateException( + "Cannot convert Spanner TIMESTAMP value to proto-type '" + field.getType().name() + "'." + ); + } + + case DATE: + // extract date value + var dateValue = columnValue.getDate(); + if (field.getType() == Descriptors.FieldDescriptor.Type.STRING) { + // we're being asked to put a Google Cloud structured date record into a string field. in + // this case, the adapter leverages any date options or otherwise defaults to ISO8601. + spliceBuilder( + target, + fieldPointer, + Optional.of(format( + "%s/%s/%s", + dateValue.getYear(), + dateValue.getMonth(), + dateValue.getDayOfMonth() + )) + ); + break; + } else if (field.getType() == Descriptors.FieldDescriptor.Type.MESSAGE && + Date.getDescriptor().getFullName().equals(field.getMessageType().getFullName())) { + // if we have a sub-message in the same spot as a date, we need to convert to a standard + // proto date, which is the only supported target here. + spliceBuilder( + target, + fieldPointer, + Optional.of(protoDateFromCloud(dateValue)) + ); + break; + } else if (field.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) { + throw new IllegalStateException( + "Cannot convert Spanner DATE value to unsupported sub-message type " + + "'" + field.getMessageType().getFullName() + "'." + ); + } else { + // any other expressed field represents an illegal state. + throw new IllegalStateException( + "Cannot convert Spanner DATE value to proto-type '" + field.getType().name() + "'." + ); + } + + case ARRAY: + throw new IllegalStateException( + "Should not receive `ARRAY` field types for concrete decoding." + ); + + case STRUCT: + convergeFields( + target.newBuilderForField(fieldPointer.getField()), + fieldPointer.getField().getMessageType(), + columnValue.getStruct(), + columnValue.getStruct().getType().getStructFields() + ); + } + } + } + + /** + * Resolve all fields for the provided `target` `model`, from the provided row struct `source`. If a value is + * present, decode it according to the assigned Spanner {@link Type} and any present or implied model + * annotations. + * + *

    This method performs recursion for nested `STRUCT` objects inside the row. Such structures are interpreted + * according to model annotations present or implied on the target builder. In such cases, `base` is set to the + * root builder being filled in. For the initial case, `base` is always {@link Optional#empty()}.

    + * + * @param target Target builder which we intend to fill in with values. + * @param model Model descriptor for the object we are building. + * @param source Source row structure to pull data from. + * @param fields List of fields present in the row, for efficient model filtering. + */ + private void convergeFields(@Nonnull Message.Builder target, + @Nonnull Descriptors.Descriptor model, + @Nonnull Struct source, + @Nonnull List fields) { + if (logging.isDebugEnabled()) logging.trace( + "More than one column value present in row. Decoding as {}...", + modelDescriptor.getFullName()); + + // compute a set of projection fields + SortedSet eligibleFields = fields.isEmpty() ? new TreeSet<>() : fields + .stream() + .map(Type.StructField::getName) + .collect(Collectors.toCollection(TreeSet::new)); + + forEachField( + model, + Optional.of(onlySpannerEligibleFields(eligibleFields, driverSettings)), + (pointer) -> { + // decide if we should recurse. we should never recurse for `JSON` fields, or for fields marked with + // `ignore` on the column or spanner options. + var spannerOpts = spannerOpts(pointer); + var columnOpts = columnOpts(pointer); + return !( + (columnOpts.isPresent() && columnOpts.get().getIgnore()) || + (spannerOpts.isPresent() && spannerOpts.get().getIgnore()) || + (spannerOpts.isPresent() && spannerOpts.get().getType().equals(SpannerOptions.SpannerType.JSON)) + ); + } + ).forEach((fieldPointer) -> { + if (logging.isDebugEnabled()) logging.trace( + "Converging eligible column field {}...", + fieldPointer.getField().getFullName()); + + convergeColumnField( + target, + source, + fieldPointer + ); + }); + } + + /** @inheritDoc */ + @Override + public @Nonnull Model inflate(@Nonnull Struct rowStruct) throws ModelInflateException { + Objects.requireNonNull(rowStruct, "cannot inflate null row struct from spanner"); + + // grab field count and begin iterating over fields, and assigning by type + var fieldCount = rowStruct.getColumnCount(); + if (fieldCount > 0 && logging.isDebugEnabled()) logging.debug( + "Inflating {} fields from Spanner row struct.", + fieldCount); + + if (fieldCount < 1) { + logging.warn("Empty rowStruct. Discarding."); + //noinspection unchecked + return (Model)defaultInstance.newBuilderForType().build(); + } else { + if (logging.isTraceEnabled()) logging.trace( + "More than one column value present in row. Decoding as {}...", + modelDescriptor.getFullName()); + + // create a new builder, converge against it from the source row structure. + var builder = defaultInstance.newBuilderForType(); + convergeFields( + builder, + modelDescriptor, + rowStruct, + rowStruct.getType().getStructFields() + ); + + //noinspection unchecked + return (Model)builder.build(); + } + } +} diff --git a/java/gust/backend/driver/spanner/SpannerTemporalConverter.java b/java/gust/backend/driver/spanner/SpannerTemporalConverter.java new file mode 100644 index 000000000..63c2d5cd5 --- /dev/null +++ b/java/gust/backend/driver/spanner/SpannerTemporalConverter.java @@ -0,0 +1,79 @@ +/* + * Copyright © 2020, The Gust Framework Authors. All rights reserved. + * + * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, + * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of + * this code in object or source form requires and implies consent and agreement to that license in principle and + * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of + * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to + * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected + * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, + * is strictly forbidden except in adherence with assigned license requirements. + */ +package gust.backend.driver.spanner; + +import com.google.cloud.Date; +import com.google.cloud.Timestamp; + +import javax.annotation.Nonnull; +import javax.annotation.concurrent.ThreadSafe; + + +/** + * Serialize back and forth between Google Cloud / Protocol Buffer standard TIMESTAMP and DATE records. + */ +@ThreadSafe +public final class SpannerTemporalConverter { + private SpannerTemporalConverter() { /* Disallow construction. */ } + + /** + * Convert a regular/standard Protocol Buffers timestamp into a Google Cloud timestamp without loss of resolution. + * + * @param timestamp Standard timestamp to convert. + * @return Cloud timestamp value. + */ + public static @Nonnull Timestamp cloudTimestampFromProto(com.google.protobuf.Timestamp timestamp) { + return Timestamp.fromProto(timestamp); + } + + /** + * Convert a Google Cloud timestamp into a standard Protocol Buffers timestamp without loss of resolution. + * + * @param timestamp Cloud timestamp to convert. + * @return Protocol buffers timestamp value. + */ + public static @Nonnull com.google.protobuf.Timestamp protoTimestampFromCloud(Timestamp timestamp) { + return com.google.protobuf.Timestamp.newBuilder() + .setSeconds(timestamp.getSeconds()) + .setNanos(timestamp.getNanos()) + .build(); + } + + /** + * Convert a regular/standard Protocol Buffers date into a Google Cloud date without loss of resolution. + * + * @param date Standard date to convert. + * @return Cloud date value. + */ + public static @Nonnull Date cloudDateFromProto(com.google.type.Date date) { + return Date.fromYearMonthDay( + date.getYear(), + date.getMonth(), + date.getDay() + ); + } + + /** + * Convert a Google Cloud date into a standard Protocol Buffers date without loss of resolution. + * + * @param date Cloud date to convert. + * @return Protocol buffers date value. + */ + public static @Nonnull com.google.type.Date protoDateFromCloud(Date date) { + return com.google.type.Date.newBuilder() + .setYear(date.getYear()) + .setMonth(date.getMonth()) + .setDay(date.getDayOfMonth()) + .build(); + } +} diff --git a/java/gust/backend/driver/spanner/SpannerTransportConfig.java b/java/gust/backend/driver/spanner/SpannerTransportConfig.java new file mode 100644 index 000000000..baeb1810b --- /dev/null +++ b/java/gust/backend/driver/spanner/SpannerTransportConfig.java @@ -0,0 +1,24 @@ +/* + * Copyright © 2020, The Gust Framework Authors. All rights reserved. + * + * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, + * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of + * this code in object or source form requires and implies consent and agreement to that license in principle and + * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of + * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to + * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected + * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, + * is strictly forbidden except in adherence with assigned license requirements. + */ +package gust.backend.driver.spanner; + + +/** + * Configures gRPC transport channels for use with Google Cloud Spanner, either in production or emulated circumstances + * for unit and integration testing. + * + * @see SpannerDriverSettings Main settings object which provides driver access to this transport configuration. + */ +public final class SpannerTransportConfig { + private SpannerTransportConfig() { /* Disallow construction. */ } +} diff --git a/java/gust/backend/driver/spanner/SpannerUtil.java b/java/gust/backend/driver/spanner/SpannerUtil.java new file mode 100644 index 000000000..6a72ba614 --- /dev/null +++ b/java/gust/backend/driver/spanner/SpannerUtil.java @@ -0,0 +1,816 @@ +/* + * Copyright © 2020, The Gust Framework Authors. All rights reserved. + * + * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, + * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of + * this code in object or source form requires and implies consent and agreement to that license in principle and + * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of + * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to + * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected + * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, + * is strictly forbidden except in adherence with assigned license requirements. + */ +package gust.backend.driver.spanner; + +import com.google.cloud.spanner.Struct; +import com.google.cloud.spanner.Type; +import com.google.cloud.spanner.Value; +import com.google.protobuf.Descriptors; +import com.google.protobuf.Message; +import com.google.protobuf.Timestamp; +import com.google.type.Date; +import gust.backend.runtime.Logging; +import gust.util.Pair; +import org.slf4j.Logger; +import tools.elide.core.*; + +import javax.annotation.Nonnull; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static gust.backend.model.ModelMetadata.*; + + +/** + * Provides utilities related to operations with Spanner, including tools for resolving column names and types from + * models and annotations, and producing default sets of columns for DDL statements. + */ +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public final class SpannerUtil { + private static final Logger logging = Logging.logger(SpannerUtil.class); + + private SpannerUtil() { /* disallow construction */ } + + /** Default predicate to use when filtering for eligible Spanner fields. */ + private static final @Nonnull Predicate defaultFieldPredicate; + + static { + defaultFieldPredicate = (fieldPointer) -> { + var field = fieldPointer.getField(); + var fieldOpts = fieldAnnotation(field, Datamodel.field); + var columnOpts = fieldAnnotation(field, Datamodel.column); + var spannerOpts = fieldAnnotation(field, Datamodel.spanner); + + return !( + // field cannot be present if skipped via `column.ignore` + (columnOpts.isPresent() && columnOpts.get().getIgnore()) || + + // field cannot be present if skipped via `spanner.ignore` + (spannerOpts.isPresent() && spannerOpts.get().getIgnore()) || + + // properties marked as `INTERNAL` should always be withheld + (fieldOpts.isPresent() && fieldOpts.get().getVisibility() == FieldVisibility.INTERNAL) + ); + }; + } + + /** + * For a given key field pointer, resolve the column name which should be used for the primary key in Spanner, + * according to the annotation structure present on the key. + * + * @see #resolveKeyType(FieldPointer) To resolve the primary key column type. + * @param idField Resolved field pointer to a given model's ID field. + * @param driverSettings Settings for the Spanner driver. + * @return Name of the column we should use for the primary key. + */ + public static @Nonnull String resolveKeyColumn(@Nonnull FieldPointer idField, + @Nonnull SpannerDriverSettings driverSettings) { + var fieldAnnos = fieldAnnotation(idField.getField(), Datamodel.field); + if (fieldAnnos.orElseThrow().getType() != FieldType.ID) { + throw new IllegalStateException( + "Cannot use non-ID field as key column: '" + idField.getField().getFullName() + "'." + ); + } + + // resolve key field and column name corresponding to that key field + return resolveColumnName( + idField, + spannerOpts(idField), + columnOpts(idField), + driverSettings + ); + } + + /** + * Given a schema-driven model or key object, determine the table name that should be used in Spanner. All keys and + * objects used with Spanner must have such annotations or use generated defaults. This method variant operates from + * a full message instance. + * + * @see #resolveTableName(Descriptors.Descriptor) For the wrapped version of this message. + * @param message Message type to resolve a table name for. + * @return Resolved table name, from annotations, or calculated as a default. + */ + public static @Nonnull String resolveTableName(@Nonnull Message message) { + return resolveTableName( + message.getDescriptorForType() + ); + } + + /** + * Given a schema-driven model or key object, determine the table name that should be used in Spanner. All keys and + * objects used with Spanner must have such annotations or use generated defaults. + * + * @param message Message type to resolve a table name for. + * @return Resolved table name, from annotations, or calculated as a default. + */ + public static @Nonnull String resolveTableName(@Nonnull Descriptors.Descriptor message) { + return modelAnnotation( + message, + Datamodel.table, + true + ).orElseThrow(() -> new IllegalArgumentException( + "Must annotate key or object model '" + message.getFullName() + "' with table name to use with Spanner." + )).getName(); + } + + /** + * For a given key field pointer, resolve the column type which should be used for the primary key in Spanner, + * according to the annotation structure present on the key. + * + * @see #resolveKeyColumn(FieldPointer, SpannerDriverSettings) To resolve the primary key column name. + * @param idField Resolved field pointer to a given model key's ID field. + * @return Spanner column type to use for this model's ID. + */ + public static @Nonnull Type resolveKeyType(@Nonnull FieldPointer idField) { + // resolve the expected key column type. validate it on the way. + if (idField.getField().isRepeated()) { + throw new IllegalStateException( + String.format( + "Unsupported key field type: '%s'. Keys cannot be repeated.", + idField.getField().getType().name())); + } else if (idField.getField().getType() == Descriptors.FieldDescriptor.Type.STRING) { + return Type.string(); + } else if ( + idField.getField().getType() == Descriptors.FieldDescriptor.Type.UINT64 || + idField.getField().getType() == Descriptors.FieldDescriptor.Type.FIXED64) { + return Type.int64(); + } else { + throw new IllegalStateException( + String.format("Unsupported key field type: '%s'.", idField.getField().getType().name())); + } + } + + /** + * Given a model field pointer which translates to a `STRING` or `BYTES` column in Spanner, determine the size that + * should be used when declaring the string column. + * + *

    If an explicit column size is specified via model annotations, that prevails. If not, the default size value + * is used.

    + * + * @param spannerOpts Spanner-specific options on the field. + * @param columnOpts Column-generic options on the field. + * @param settings Settings for the Spanner driver. + * @return Expected name of the field when expressed as a column in Spanner. + */ + public static int resolveColumnSize(@Nonnull Descriptors.FieldDescriptor field, + @Nonnull Optional spannerOpts, + @Nonnull Optional columnOpts, + @Nonnull SpannerDriverSettings settings) { + if (spannerOpts.isPresent()) { + var spannerOptsUnwrapped = spannerOpts.get(); + if (spannerOptsUnwrapped.getSize() > 0) { + return spannerOptsUnwrapped.getSize(); + } + } + if (columnOpts.isPresent()) { + var columnOptsUnwrapped = columnOpts.get(); + if (columnOptsUnwrapped.getSize() > 0) { + return columnOptsUnwrapped.getSize(); + } + } + if (field.getType() == Descriptors.FieldDescriptor.Type.ENUM) { + return 32; // special case: string ENUM fields should have a sensible default + } + return settings.defaultColumnSize(); + } + + /** + * Given a resolved field pointer resolve the expected/configured column name in Spanner for a given typed model + * field. If no specialized Spanner or table column annotations are present, fallback to a calculated default name. + * + * @see #resolveColumnName(Descriptors.FieldDescriptor, Optional, Optional, SpannerDriverSettings) For the full + * un-sugared version of this method. + * @param field Pre-resolved model field descriptor. + * @return Expected name of the field when expressed as a column in Spanner. + */ + public static @Nonnull String resolveColumnName(@Nonnull Descriptors.FieldDescriptor field) { + return resolveColumnName( + field, + SpannerDriverSettings.DEFAULTS + ); + } + + /** + * Given a resolved field pointer resolve the expected/configured column name in Spanner for a given typed model + * field. If no specialized Spanner or table column annotations are present, fallback to a calculated default name. + * Apply any active driver settings as well. + * + * @see #resolveColumnName(Descriptors.FieldDescriptor, Optional, Optional, SpannerDriverSettings) For the full + * un-sugared version of this method. + * @param field Pre-resolved model field descriptor. + * @param settings Settings for the Spanner driver. + * @return Expected name of the field when expressed as a column in Spanner. + */ + public static @Nonnull String resolveColumnName(@Nonnull Descriptors.FieldDescriptor field, + @Nonnull SpannerDriverSettings settings) { + return resolveColumnName( + field, + spannerOpts(field), + columnOpts(field), + settings + ); + } + + /** + * Given a resolved field pointer and set of annotations, resolve the expected/configured column name in Spanner for + * a given typed model field. If no specialized Spanner or table column annotations are present, fallback to a + * calculated default name. + * + * @see #resolveColumnName(Descriptors.FieldDescriptor, Optional, Optional, SpannerDriverSettings) For the full + * un-sugared version of this method. + * @param fieldPointer Pre-resolved model field pointer. + * @param spannerOpts Spanner-specific options on the field. + * @param columnOpts Column-generic options on the field. + * @param settings Settings for the Spanner driver. + * @return Expected name of the field when expressed as a column in Spanner. + */ + public static @Nonnull String resolveColumnName(@Nonnull FieldPointer fieldPointer, + @Nonnull Optional spannerOpts, + @Nonnull Optional columnOpts, + @Nonnull SpannerDriverSettings settings) { + return resolveColumnName( + fieldPointer.getField(), + spannerOpts, + columnOpts, + settings + ); + } + + /** + * Given a resolved Protocol Buffer field descriptor and set of annotations, resolve the expected/configured column + * name in Spanner for a given typed model field. If no specialized Spanner or table column annotations are present, + * fallback to a calculated default name. + * + *

    {@link SpannerFieldOptions} always outweigh {@link TableFieldOptions}. If two similar or congruent properties + * are set between options, generic options are applied first, and then specialized options override.

    + * + *

    If {@link SpannerDriverSettings#preserveFieldNames()} is activated when this method is called, default names + * will use the literal field name from the Protocol Buffer definition. Otherwise, JSON-style names are calculated + * and used as default names.

    + * + *

    Similarly, if {@link SpannerDriverSettings#defaultCapitalizedNames()} is activated when this method is called, + * default names will use JSON-style naming but with initial capitals. For example, `name` turns into `Name` and + * `contact_info` turns into `ContactInfo`. In all cases, explicit property names from specialized or generic + * annotations prevail, then {@link SpannerDriverSettings#preserveFieldNames()} prevails, then the default form of + * naming with Spanner capitalized names active.

    + * + * @see #resolveColumnName(FieldPointer, Optional, Optional, SpannerDriverSettings) For a version of this method + * which operates on {@link FieldPointer} objects. + * @param field Protocol Buffer field descriptor for which we should resolve a Spanner column name. + * @param spannerOpts Spanner options applied to this field as annotations. + * @param columnOpts Generic table column settings applied to this field as annotations. + * @param settings Settings for the Spanner driver. + * @return Resolved column name, from explicit annotations, or by way of default calculation, as described above. + */ + public static @Nonnull String resolveColumnName(@Nonnull Descriptors.FieldDescriptor field, + @Nonnull Optional spannerOpts, + @Nonnull Optional columnOpts, + @Nonnull SpannerDriverSettings settings) { + // resolve the expected column name in Spanner. + String columnName; + if (spannerOpts.isPresent() && !spannerOpts.get().getColumn().isBlank()) { + columnName = spannerOpts.get().getColumn(); + } else if (columnOpts.isPresent() && !columnOpts.get().getName().isBlank()) { + columnName = columnOpts.get().getName(); + } else { + // generate a default name + if (settings.preserveFieldNames()) { + columnName = field.getName(); + } else { + // use JSON field names + if (settings.defaultCapitalizedNames()) { + columnName = String.format( + "%s%s", + field.getJsonName().substring(0, 1).toUpperCase(), + field.getJsonName().substring(1) + ); + } else { + // use unmodified JSON names + columnName = field.getJsonName(); + } + } + } + + if (logging.isTraceEnabled()) + logging.trace("Resolved column name for field '{}': '{}'", + field.getName(), + columnName); + return columnName; + } + + /** + * Given a Spanner row result expressed as a {@link Struct} and a {@link FieldPointer} which is expected to be + * present, with a pre-resolved column name, return the numeric column index. + * + * @param source Row result from Spanner which we should resolve the column index from. + * @param fieldPointer Pointer to the field for which we are resolving an index. Pre-resolved. + * @param name Translated name of the column for which we are resolving an index. Pre-resolved. + * @return Integer index for the column in the provided row result. + */ + public static int resolveColumnIndex(@Nonnull Struct source, + @Nonnull FieldPointer fieldPointer, + @Nonnull String name) { + var columnIndex = source.getColumnIndex(name); + if (logging.isTraceEnabled()) + logging.trace("Resolved column index for field '{}': '{}'", + fieldPointer.getName(), + columnIndex); + return columnIndex; + } + + /** + * Given a Spanner row result expressed as a {@link Struct} and a {@link FieldPointer} which is expected to be + * present, resolve any present {@link Value}. + * + *

    This method additionally resolves the expected column name for the provided field.

    + * + * @see #resolveColumnName(FieldPointer, Optional, Optional, SpannerDriverSettings) For an explanation of model + * field column name calculations and annotation behavior. + * @param source Row result from Spanner from which we should resolve any present value. + * @param fieldPointer Pointer to the model field for which we are resolving a value. + * @param spannerOpts Spanner-specific options and annotations present on the field. + * @param columnOpts Column-generic options and annotations present on the field. + * @param driverSettings Settings for the Spanner driver. + * @return Resolved Spanner value, as applicable. + */ + public static @Nonnull Value resolveColumnValue(@Nonnull Struct source, + @Nonnull FieldPointer fieldPointer, + @Nonnull Optional spannerOpts, + @Nonnull Optional columnOpts, + @Nonnull SpannerDriverSettings driverSettings) { + var columnValue = source.getValue(resolveColumnIndex( + source, + fieldPointer, + resolveColumnName( + fieldPointer, + spannerOpts, + columnOpts, + driverSettings + ) + )); + if (logging.isTraceEnabled()) + logging.trace("Resolved column value for field '{}': '{}'", + fieldPointer.getName(), + columnValue.toString()); + return columnValue; + } + + /** + * Given a resolved and eligible {@link FieldPointer} for a model field which should interact with Spanner, resolve + * an expected Spanner {@link Type}, including any nested structure or complex objects, as mediated and regulated by + * annotations on the model field. + * + *

    If {@link SpannerFieldOptions#getType()} returns a non-default value, it prevails first, with + * {@link TableFieldOptions#getSptype()} after that. If no explicit type is resolvable from the field definition, + * a default type is generated (see method references for more information).

    + * + * @see #resolveDefaultType(FieldPointer, SpannerDriverSettings) Fallback behavior if no explicit type is specified. + * @param fieldPointer Pointer to the model field for which we should resolve a Spanner column type. + * @param spannerOpts Spanner-specific options or annotations present on the field definition, as applicable. + * @param columnOpts Column-generic options or annotations present on the field definition, as applicable. + * @param settings Active settings for the Spanner driver. + * @return Expected Spanner column type corresponding to the provided model field, considering all annotations. + */ + public static @Nonnull Type resolveColumnType(@Nonnull FieldPointer fieldPointer, + @Nonnull Optional spannerOpts, + @Nonnull Optional columnOpts, + @Nonnull SpannerDriverSettings settings) { + //noinspection deprecation + return spannerOpts.isPresent() && spannerOpts.get().getType() != SpannerOptions.SpannerType.UNSPECIFIED_TYPE ? + resolveType(fieldPointer, spannerOpts.get().getType()) : + columnOpts.isPresent() && columnOpts.get().getSptype() != SpannerOptions.SpannerType.UNSPECIFIED_TYPE ? + resolveType(fieldPointer, columnOpts.get().getSptype()) : + resolveDefaultType(fieldPointer, settings); + } + + /** + * Calculate a default projection of Spanner columns, as configured on the provided default model instance. Results + * are returned as a stream of fields paired to their column counterparts. + * + * @param descriptor Default model schema to generate a default set of Spanner columns from. + * @param driverSettings Settings for the Spanner driver. + * @return Default list of Spanner columns. + */ + public static @Nonnull Stream> calculateDefaultFieldStream( + @Nonnull Descriptors.Descriptor descriptor, + @Nonnull SpannerDriverSettings driverSettings) { + // pluck the ID field first, and then concatenante it to a stream of all other fields. + return Stream.concat(Stream.of(idField(descriptor).orElseThrow()), forEachField( + descriptor, + Optional.of(onlySpannerEligibleFields(driverSettings)) + )).map((fieldPointer) -> { + var fieldOpts = fieldAnnotation(fieldPointer.getField(), Datamodel.field); + if (fieldOpts.orElse(FieldPersistenceOptions.getDefaultInstance()).getType() == FieldType.ID) { + // this is an ID field, so skip it outright because the key will inject it. + return null; + } else if (fieldOpts.orElse(FieldPersistenceOptions.getDefaultInstance()).getType() == FieldType.KEY) { + // this is a key field, so skip it and instead inject the ID field. + return Pair.of( + fieldPointer.getName(), + resolveKeyColumn(idField(descriptor).orElseThrow(), driverSettings) + ); + } else { + return Pair.of( + fieldPointer.getName(), + resolveColumnName(fieldPointer, + fieldAnnotation(fieldPointer.getField(), Datamodel.spanner), + fieldAnnotation(fieldPointer.getField(), Datamodel.column), + driverSettings + ) + ); + } + }).filter(Objects::nonNull); + } + + /** + * Calculate a default projection of Spanner columns, as configured on the provided default model instance. + * + * @param descriptor Default model schema to generate a default set of Spanner columns from. + * @param driverSettings Settings for the Spanner driver. + * @return Default list of Spanner columns. + */ + public static @Nonnull List calculateDefaultFields(@Nonnull Descriptors.Descriptor descriptor, + @Nonnull SpannerDriverSettings driverSettings) { + return calculateDefaultFieldStream( + descriptor, + driverSettings + ).map(Pair::getValue).collect(Collectors.toUnmodifiableList()); + } + + /** + * Calculate a default projection of Spanner columns, as configured on the provided default model instance. The + * default set of columns includes any columns considered "eligible" for storage in Spanner, each pre-resolved with + * a column name and type. + * + *

    This method is designed to operate deterministically, by visiting each eligible model field in a predictable + * order and expressing that same order in the output collection.

    + * + * @see #onlySpannerEligibleFields(SpannerDriverSettings) For an explanation of predicate behavior with regard to + * eligibility for interaction with Spanner. + * @see #resolveColumnName(FieldPointer, Optional, Optional, SpannerDriverSettings) For an explanation of Spanner + * column name resolution and default calculation behavior. + * @see #resolveColumnType(FieldPointer, Optional, Optional, SpannerDriverSettings) For an explanation of Spanner + * column type resolution and default decision behavior. + * @param descriptor Default model schema to generate a default set of Spanner columns from. + * @param driverSettings Settings for the Spanner driver. + * @return Default list of Spanner columns. + */ + public static @Nonnull Collection generateStruct(@Nonnull Descriptors.Descriptor descriptor, + @Nonnull SpannerDriverSettings driverSettings) { + return forEachField( + descriptor, + Optional.of(onlySpannerEligibleFields(driverSettings)) + ).filter((fieldPointer) -> + // don't ever include `KEY` fields in structs + fieldAnnotation(fieldPointer.getField(), Datamodel.field).orElse( + FieldPersistenceOptions.getDefaultInstance() + ).getType() != FieldType.KEY + ).map((fieldPointer) -> { + var spannerOpts = fieldAnnotation(fieldPointer.getField(), Datamodel.spanner); + var columnOpts = fieldAnnotation(fieldPointer.getField(), Datamodel.column); + var name = resolveColumnName( + fieldPointer, + spannerOpts, + columnOpts, + driverSettings + ); + var type = resolveColumnType( + fieldPointer, + spannerOpts, + columnOpts, + driverSettings + ); + return Type.StructField.of(name, type); + }).collect(Collectors.toUnmodifiableList()); + } + + /** + * Return a {@link Predicate} implementation which determines field eligibility with regard to interaction with + * Cloud Spanner, optionally considering the provided set of circumstantial higher-order eligible fields (for + * instance, in the case of a known property projection). + * + *

    Field eligibility is determined by the following criteria: + *

      + *
    • Model fields MUST be present on the {@link Message} schema to interact with Spanner.
    • + *
    • Model fields MUST NOT be annotated with {@link TableFieldOptions#getIgnore()}.
    • + *
    • Model fields MUST NOT be annotated with {@link SpannerFieldOptions#getIgnore()}.
    • + *
    • Model fields MUST NOT be annotated with {@link FieldVisibility#INTERNAL}.
    • + *
    • If provided and non-empty, model fields MUST be present in the set of
      eligibleFields
      + * provided to this method.
    • + *

    + * + * @see #onlySpannerEligibleFields(SpannerDriverSettings) For circumstances with no known eligible fields. + * @param eligibleFields Set of higher-order eligible fields, if applicable. Only considered if non-empty. + * @param settings Settings for the Spanner driver. + * @return Predicate which filters {@link FieldPointer} objects according to the provided settings. + */ + public static @Nonnull Predicate onlySpannerEligibleFields(@Nonnull SortedSet eligibleFields, + @Nonnull SpannerDriverSettings settings) { + if (eligibleFields.isEmpty()) { + return defaultFieldPredicate; + } + return defaultFieldPredicate.and((fieldPointer) -> { + var field = fieldPointer.getField(); + var columnOpts = fieldAnnotation(field, Datamodel.column); + var spannerOpts = fieldAnnotation(field, Datamodel.spanner); + + return !( + // field should be omitted if not present in list of row fields, if we have any + !eligibleFields.isEmpty() && !eligibleFields.contains(resolveColumnName( + fieldPointer, + spannerOpts, + columnOpts, + settings + )) + ); + }); + } + + /** + * Return a {@link Predicate} implementation which operates on {@link FieldPointer} objects to determine eligibility + * for interaction with Spanner. + * + *

    This method variant provides no opportunity to filter by higher-order circumstantial fields. Should invoking + * code have an opportunity to do so, it may dispatch the more customizable form of this method (see below). + * Additionally, that method may be referenced for a detailed explanation of field eligibility behavior.

    + * + * @see #onlySpannerEligibleFields(SortedSet, SpannerDriverSettings) For the fully-controllable form of this method, + * which can combine an additional {@link Predicate} to filter by a set of fields known at invocation time. + * @param settings Settings for the Spanner driver. + * @return Predicate to determine {@link FieldPointer} eligibility for interaction with Spanner. + */ + public static @Nonnull Predicate onlySpannerEligibleFields(@Nonnull SpannerDriverSettings settings) { + return onlySpannerEligibleFields(Collections.emptySortedSet(), settings); + } + + /** + * If a concrete type is marked as repeated, wrap it in an array type. Otherwise, just return the type. + * + * @param field Field pointer for the model field. + * @param inner Inner type for the maybe-repeated field. + * @return Either the array-wrapped type or the concrete individual type. + */ + public static @Nonnull Type maybeWrapType(@Nonnull FieldPointer field, + @Nonnull Type inner) { + if (field.getField().isRepeated()) { + return Type.array(inner); + } + return inner; + } + + /** + * Resolve a normalized Spanner type for the provided field `pointer`, with the explicit provided `spannerType`. + * With an explicit type, our job is just to make sure the model configuration is cohesive. + * + * @param pointer Resolved field pointer. + * @param spannerType Explicit Spanner type. + * @return Resolved normalized Spanner type. + */ + public static @Nonnull Type resolveType(@Nonnull FieldPointer pointer, + @Nonnull tools.elide.core.SpannerOptions.SpannerType spannerType) { + switch (spannerType) { + case STRING: + case JSON: return maybeWrapType(pointer, Type.string()); + case NUMERIC: return maybeWrapType(pointer, Type.numeric()); + case FLOAT64: return maybeWrapType(pointer, Type.float64()); + case INT64: return maybeWrapType(pointer, Type.int64()); + case BYTES: return maybeWrapType(pointer, Type.bytes()); + case BOOL: return maybeWrapType(pointer, Type.bool()); + case DATE: return maybeWrapType(pointer, Type.date()); + case TIMESTAMP: return maybeWrapType(pointer, Type.timestamp()); + default: throw new IllegalArgumentException("Unrecognized Spanner type."); + } + } + + /** + * Resolve a default Spanner type for the provided field `pointer`. This selects a sensible default when no + * explicit type annotations are present for a Spanner column's type. + * + * @param pointer Field pointer to resolve a Spanner type for. + * @param settings Settings for the Spanner driver. + * @return Resolved normalized Spanner type. + */ + public static @Nonnull Type resolveDefaultType(@Nonnull FieldPointer pointer, + @Nonnull SpannerDriverSettings settings) { + return resolveDefaultType( + pointer, + pointer.getField().getType(), + settings + ); + } + + /** + * Resolve a default Spanner type for the provided field `pointer`. This selects a sensible default when no + * explicit type annotations are present for a Spanner column's type. + * + * @param pointer Field pointer to resolve a Spanner type for. + * @param protoType Protocol Buffer type to resolve. + * @param settings Settings for the Spanner driver. + * @return Resolved normalized Spanner type. + */ + public static @Nonnull Type resolveDefaultType(@Nonnull FieldPointer pointer, + @Nonnull Descriptors.FieldDescriptor.Type protoType, + @Nonnull SpannerDriverSettings settings) { + switch (protoType) { + case DOUBLE: + case FLOAT: + return maybeWrapType(pointer, Type.float64()); + + case INT32: + case INT64: + case UINT32: + case UINT64: + case FIXED32: + case FIXED64: + case SFIXED32: + case SFIXED64: + case SINT32: + case SINT64: + return maybeWrapType(pointer, Type.int64()); + + case BOOL: return maybeWrapType(pointer, Type.bool()); + case STRING: return maybeWrapType(pointer, Type.string()); + + case BYTES: return maybeWrapType(pointer, Type.bytes()); + case ENUM: + return settings.enumsAsNumbers() ? + maybeWrapType(pointer, Type.int64()) : + maybeWrapType(pointer, Type.string()); + + case MESSAGE: + var messageType = pointer.getField().getMessageType(); + if (Timestamp.getDescriptor().getFullName().equals(messageType.getFullName())) { + // `TIMESTAMP` records should always be expressed as type `TIMESTAMP`. + return maybeWrapType(pointer, Type.timestamp()); + + } else if (Date.getDescriptor().getFullName().equals(messageType.getFullName())) { + // `DATE` records should always be expressed as type `DATE`. + return maybeWrapType(pointer, Type.date()); + + } else { + // special case: if this is a key field, we should treat it like it's actually the key's ID field. + if (fieldAnnotation(pointer.getField(), Datamodel.field).orElse( + FieldPersistenceOptions.getDefaultInstance() + ).getType().equals(FieldType.KEY)) { + // it's a key field, so instead, resolve the model's ID field and add that. + return resolveKeyType(idField(pointer.getBase()).orElseThrow()); + } + + // any other type should fallback to being wrapped as a `STRUCT`. + return maybeWrapType(pointer, Type.struct(generateStruct( + pointer.getField().getMessageType(), + settings + ))); + } + + case GROUP: + default: + throw new IllegalArgumentException(String.format( + "Unrecognized Protocol Buffer type: %s.", + pointer.getField().getType().name() + )); + } + } + + /** + * Given a pre-resolved {@link FieldPointer}, resolve any present {@link TableFieldOptions}. + * + * @see #columnOpts(Descriptors.FieldDescriptor) For the low-level version of this method, which includes a more + * detailed explanation of {@link TableFieldOptions} with regard to Spanner. + * @param fieldPointer Field pointer for which we should resolve any present column-generic options. + * @return Set of specified table field options, or {@link Optional#empty()}. + */ + public static @Nonnull Optional columnOpts(@Nonnull FieldPointer fieldPointer) { + return columnOpts(fieldPointer.getField()); + } + + /** + * Given a resolved Protocol Buffer {@link Descriptors.FieldDescriptor} which is considered eligible for interaction + * with Spanner, resolve any present column-generic options and annotations via {@link TableFieldOptions}. + * + *

    Column-generic options apply to engines which operate in a columnar manner. This includes Spanner, but also + * includes engines like BigQuery and SQL-based systems. To allow adaptation to those systems without curtailing + * control of table and field naming, the Spanner driver respects {@link TableFieldOptions} but defers to any + * present {@link SpannerFieldOptions}.

    + * + * @see #columnOpts(FieldPointer) For a version of this method which operates on {@link FieldPointer}. + * @see #spannerOpts(Descriptors.FieldDescriptor) For the equivalent version of this method that returns Spanner- + * specific field options. + * @param field Field descriptor for which we should resolve any present {@link TableFieldOptions}. + * @return Any present column-generic field options or annotations, or {@link Optional#empty()}. + */ + public static @Nonnull Optional columnOpts(@Nonnull Descriptors.FieldDescriptor field) { + // resolve any generic column options... + var columnOpts = fieldAnnotation( + field, + Datamodel.column + ); + + if (columnOpts.isPresent() && logging.isDebugEnabled()) + logging.debug("Found column options for field '{}': \n{}", + field.getName(), + columnOpts.toString()); + else if (columnOpts.isEmpty() && logging.isDebugEnabled()) { + logging.debug("No column opts for field '{}'. Using defaults.", + field.getName()); + } + return columnOpts; + } + + /** + * Given a pre-resolved {@link FieldPointer}, resolve any present generic {@link FieldPersistenceOptions}. + * + * @see #fieldOpts(Descriptors.FieldDescriptor) For the low-level version of this method, which includes a more + * detailed explanation of {@link FieldPersistenceOptions}. + * @param fieldPointer Field pointer for which we should resolve any present field-generic options. + * @return Set of specified general field options, or {@link Optional#empty()}. + */ + public static @Nonnull Optional fieldOpts(@Nonnull FieldPointer fieldPointer) { + return fieldOpts(fieldPointer.getField()); + } + + /** + * Given a resolved Protocol Buffer {@link Descriptors.FieldDescriptor} which is considered eligible for interaction + * with Spanner, resolve any present field-generic options and annotations via {@link FieldPersistenceOptions}. + * + *

    To adapt to other persistence engines, model fields may be annotated with {@link FieldPersistenceOptions}. In + * all cases, present {@link FieldPersistenceOptions} yield with regard to matchin Spanner-specific fields.

    + * + * @see #spannerOpts(Descriptors.FieldDescriptor) Equivalent method for Spanner options + * @see #columnOpts(Descriptors.FieldDescriptor) equivalent method for column generic options + * @param field Field descriptor for which we should resolve any present {@link FieldPersistenceOptions}. + * @return Any present field-generic field options or annotations, or {@link Optional#empty()}. + */ + public static @Nonnull Optional fieldOpts(@Nonnull Descriptors.FieldDescriptor field) { + // resolve field options + var fieldOpts = fieldAnnotation( + field, + Datamodel.field + ); + + if (fieldOpts.isPresent() && logging.isDebugEnabled()) + logging.debug("Found generic options for field '{}': \n{}", + field.getName(), + fieldOpts.toString()); + else if (fieldOpts.isEmpty() && logging.isDebugEnabled()) { + logging.debug("No generic opts for field '{}'. Using defaults.", + field.getName()); + } + return fieldOpts; + } + + /** + * Given a pre-resolved {@link FieldPointer}, resolve any present {@link SpannerFieldOptions}. + * + * @see #spannerOpts(Descriptors.FieldDescriptor) For the low-level version of this method, which includes a more + * detailed explanation of {@link SpannerFieldOptions}. + * @param fieldPointer Field pointer for which we should resolve any present column-generic options. + * @return Set of specified table field options, or {@link Optional#empty()}. + */ + public static @Nonnull Optional spannerOpts(@Nonnull FieldPointer fieldPointer) { + return spannerOpts(fieldPointer.getField()); + } + + /** + * Given a resolved Protocol Buffer {@link Descriptors.FieldDescriptor} which is considered eligible for interaction + * with Spanner, resolve any present Spanner-specific options and annotations via {@link SpannerFieldOptions}. + * + *

    To adapt to other columnar-style engines, model fields may be annotated with {@link TableFieldOptions}. In all + * cases, present {@link SpannerFieldOptions} override with regard to Spanner Driver behavior.

    + * + * @see #spannerOpts(FieldPointer) For a version of this method which operates on {@link FieldPointer}. + * @see #columnOpts(Descriptors.FieldDescriptor) For the equivalent version of this method that returns column- + * generic field options. + * @param field Field descriptor for which we should resolve any present {@link SpannerFieldOptions}. + * @return Any present column-generic field options or annotations, or {@link Optional#empty()}. + */ + public static @Nonnull Optional spannerOpts(@Nonnull Descriptors.FieldDescriptor field) { + // resolve spanner options, which override any default table options. + var spannerOpts = fieldAnnotation( + field, + Datamodel.spanner + ); + + if (spannerOpts.isPresent() && logging.isDebugEnabled()) + logging.debug("Found Spanner options for field '{}': \n{}", + field.getName(), + spannerOpts.toString()); + else if (spannerOpts.isEmpty() && logging.isDebugEnabled()) { + logging.debug("No Spanner opts for field '{}'. Using defaults.", + field.getName()); + } + return spannerOpts; + } +} diff --git a/java/gust/backend/driver/spanner/package-info.java b/java/gust/backend/driver/spanner/package-info.java new file mode 100644 index 000000000..3f824c91e --- /dev/null +++ b/java/gust/backend/driver/spanner/package-info.java @@ -0,0 +1,15 @@ +/* + * Copyright © 2020, The Gust Framework Authors. All rights reserved. + * + * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, + * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of + * this code in object or source form requires and implies consent and agreement to that license in principle and + * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of + * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to + * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected + * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, + * is strictly forbidden except in adherence with assigned license requirements. + */ + +/** Provides a {@link gust.backend.model.DatabaseDriver} implementation for integration with Google Cloud Spanner. */ +package gust.backend.driver.spanner; diff --git a/java/gust/backend/model/BUILD.bazel b/java/gust/backend/model/BUILD.bazel index d43907c85..1f0498749 100644 --- a/java/gust/backend/model/BUILD.bazel +++ b/java/gust/backend/model/BUILD.bazel @@ -118,6 +118,7 @@ java_library( srcs = ["DatabaseManager.java"], deps = [ ":DatabaseDriver", + ":DatabaseAdapter", ":PersistenceManager", ] + _COMMON_DEPS, ) diff --git a/java/gust/backend/model/CollapsedMessageCodec.java b/java/gust/backend/model/CollapsedMessageCodec.java index d1f035864..f7890073f 100644 --- a/java/gust/backend/model/CollapsedMessageCodec.java +++ b/java/gust/backend/model/CollapsedMessageCodec.java @@ -55,7 +55,7 @@ private CollapsedMessageCodec(@Nonnull Model instance, * Create a collapsed message codec which adapts the provided builder to {@link CollapsedMessage} and back. These * "collapsed" messages follow the framework-defined protocol for serializing hierarchical data. * - * @param Model type for which we will constructor or otherwise resolve a collapsed message codec. + * @param Model type for which we will construct or otherwise resolve a collapsed message codec. * @return Collapsed message codec bound to the provided message type. */ @Context @@ -114,7 +114,8 @@ public Message.Builder getBuilder() { } /** @return Default model instance. */ - public Model getInstance() { - return instance; + @Override + public @Nonnull Model instance() { + return this.instance; } } diff --git a/java/gust/backend/model/DatabaseManager.java b/java/gust/backend/model/DatabaseManager.java index 7d69211bd..029eb4ded 100644 --- a/java/gust/backend/model/DatabaseManager.java +++ b/java/gust/backend/model/DatabaseManager.java @@ -16,6 +16,7 @@ /** * */ -public interface DatabaseManager extends PersistenceManager { - /* Nothing yet. */ +@SuppressWarnings("rawtypes") +public interface DatabaseManager + extends PersistenceManager { } diff --git a/java/gust/backend/model/FetchOptions.java b/java/gust/backend/model/FetchOptions.java index 9f0903ed6..dc9da039d 100644 --- a/java/gust/backend/model/FetchOptions.java +++ b/java/gust/backend/model/FetchOptions.java @@ -53,9 +53,4 @@ enum MaskMode { default @Nonnull Optional snapshot() { return Optional.empty(); } - - /** @return Whether to run in a transaction. */ - default @Nonnull Optional transactional() { - return Optional.empty(); - } } diff --git a/java/gust/backend/model/ModelAdapter.java b/java/gust/backend/model/ModelAdapter.java index 9ea87aa6e..99f81cd9a 100644 --- a/java/gust/backend/model/ModelAdapter.java +++ b/java/gust/backend/model/ModelAdapter.java @@ -181,7 +181,6 @@ var record = engine().retrieve(key, options); return engine().persist(key, model, options); } - // -- Interface: Delete -- // /** {@inheritDoc} */ @Override default @Nonnull ReactiveFuture delete(@Nonnull Key key, diff --git a/java/gust/backend/model/ModelCodec.java b/java/gust/backend/model/ModelCodec.java index c138978f9..50183c02d 100644 --- a/java/gust/backend/model/ModelCodec.java +++ b/java/gust/backend/model/ModelCodec.java @@ -55,6 +55,14 @@ public interface ModelCodec deserializer(); + /** + * Retrieve the default instance stored with this codec. Each {@link Message} with a paired {@link ModelCodec} retains + * a reference to its corresponding default instance. + * + * @return Default model instance. + */ + @Nonnull Model instance(); + // -- Proxies -- // /** * Sugar shortcut to serialize a model through the current codec's installed {@link ModelSerializer}. diff --git a/java/gust/backend/model/ModelMetadata.java b/java/gust/backend/model/ModelMetadata.java index b5bfb2894..eb53a05c6 100644 --- a/java/gust/backend/model/ModelMetadata.java +++ b/java/gust/backend/model/ModelMetadata.java @@ -53,7 +53,7 @@ * runtime for Protobuf in Java does not include descriptors at all, which this class relies on).

    */ @ThreadSafe -@SuppressWarnings({"WeakerAccess", "unused"}) +@SuppressWarnings({"WeakerAccess", "unused", "OptionalUsedAsFieldOrParameterType"}) public final class ModelMetadata { private ModelMetadata() { /* Disallow construction. */ } @@ -115,6 +115,22 @@ public final static class FieldPointer implements Serializable, Comparable Builder spliceArbitraryField(@No @Nonnull String path, @Nonnull Optional value, @Nonnull String remaining) { + Objects.requireNonNull(original, "Cannot splice field into `null` original builder."); Objects.requireNonNull(builder, "Cannot splice field into `null` builder."); Objects.requireNonNull(path, "Cannot resolve field from `null` path."); - Objects.requireNonNull(remaining, "Recursive remaining stack should not be `null`."); Objects.requireNonNull(value, "Pass an empty optional, not `null`, for value."); + Objects.requireNonNull(remaining, "Recursive remaining stack should not be `null`."); + if (path.startsWith(".")) + throw new IllegalArgumentException(String.format( + "Cannot splice path that starts with `.` (got: '%s').", path)); + if (remaining.startsWith(".")) + throw new IllegalArgumentException(String.format( + "Cannot splice path that starts with `.` (got: '%s').", remaining)); var descriptor = builder.getDescriptorForType(); if (!remaining.isEmpty() && !remaining.contains(".")) { // thankfully, no need to recurse - var field = Objects.requireNonNull(descriptor.findFieldByName(remaining)); + var field = Objects.requireNonNull( + descriptor.findFieldByName(remaining), String.format("failed to locate field %s", remaining)); if (value.isPresent()) { try { builder.setField(field, value.get()); @@ -450,10 +474,18 @@ static Builder spliceArbitraryField(@No String newRemainder = remaining.substring(remaining.indexOf('.') + 1); return spliceArbitraryField( original, - builder.getFieldBuilder(Objects.requireNonNull(descriptor.findFieldByName(segment))), + builder.getFieldBuilder(Objects.requireNonNull( + descriptor.findFieldByName(segment), + String.format( + "Failed to locate sub-builder at path '%s' on model '%s'.", + segment, + builder.getDescriptorForType().getFullName() + ) + )), path, value, - newRemainder); + newRemainder + ); } } @@ -904,6 +936,23 @@ public static void enforceAnyRole(@Nonnull Descriptor descriptor, return resolveAnnotatedField(descriptor, ext, recursive, filter, ""); } + /** + * Retrieve a field-level annotation, from the provided field schema {@code descriptor}, structured by {@code ext}. If + * no instance of the requested field annotation can be found, {@link Optional#empty()} is returned. + * + * @param descriptor Schema descriptor for a field on a model type. + * @param ext Extension to fetch from the subject field. + * @param Generic type of extension we are looking for. + * @return Optional, either {@link Optional#empty()}, or wrapping the found extension data instance. + */ + public static @Nonnull Optional fieldAnnotation(@Nonnull FieldDescriptor descriptor, + @Nonnull GeneratedExtension ext) { + Objects.requireNonNull(descriptor, "Cannot resolve type for `null` field descriptor."); + if (descriptor.getOptions().hasExtension(ext)) + return Optional.of(descriptor.getOptions().getExtension(ext)); + return Optional.empty(); + } + // -- Metadata: ID Fields -- // /** @@ -1240,12 +1289,14 @@ public static void enforceAnyRole(@Nonnull Descriptor descriptor, @Nonnull Message.Builder builder, @Nonnull FieldPointer field, @Nonnull Optional val) { + var noPrefixPath = field.path.startsWith(".") ? field.path.substring(1) : field.path; return spliceArbitraryField( builder, builder, - field.path, + noPrefixPath, val, - field.path); + noPrefixPath + ); } // -- Metadata: ID/Key Splice -- // @@ -1460,6 +1511,101 @@ public static void enforceAnyRole(@Nonnull Descriptor descriptor, ).collect(Collectors.toUnmodifiableList()); } + /** + * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run + * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. This + * method variant runs each operation serially. + * + *

    This method variant does not allow the invoking user to crawl recursively.

    + * + * @see #streamFields(Descriptor) for the cleanest invocation of this method. + * + * @param descriptor Schema descriptor to crawl model definitions on. + * @param predicate Filter predicate function, if applicable. + * @return Stream of field descriptors, recursively, which match the `predicate`, if provided. + */ + public static @Nonnull Stream forEachField(@Nonnull Descriptor descriptor, + @Nonnull Optional> predicate) { + Objects.requireNonNull(descriptor); + Objects.requireNonNull(predicate); + + return streamFieldsRecursive( + descriptor, + descriptor, + predicate, + (field) -> false, + "", + false + ); + } + + /** + * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run + * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. This + * method variant runs each operation serially. + * + *

    This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search + * is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate + * is provided, the entire set of recursive effective fields is returned from the provided descriptor.

    + * + * @see #streamFields(Descriptor) for the cleanest invocation of this method. + * + * @param descriptor Schema descriptor to crawl model definitions on. + * @param predicate Filter predicate function, if applicable. + * @param recursive Whether to perform recursion down to sub-messages. + * @return Stream of field descriptors, recursively, which match the `predicate`, if provided. + */ + public static @Nonnull Stream forEachField(@Nonnull Descriptor descriptor, + @Nonnull Optional> predicate, + boolean recursive) { + Objects.requireNonNull(descriptor); + Objects.requireNonNull(predicate); + + return streamFieldsRecursive( + descriptor, + descriptor, + predicate, + (field) -> recursive, + "", + false + ); + } + + /** + * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run + * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. This + * method variant runs each operation serially. + * + *

    If a `MESSAGE` field is encountered and the algorithm needs to decide whether to recurse, this variant includes + * support for the `decider` function. `decider` is invoked to decide whether to recurse for opportunity to do so.

    + * + *

    This method variant allows the user to restrict recursive crawling. If recursion is active, a depth-first search + * is performed, with the `predicate` function invoked for every field encountered during the crawl. If no predicate + * is provided, the entire set of recursive effective fields is returned from the provided descriptor.

    + * + * @see #streamFields(Descriptor) for the cleanest invocation of this method. + * + * @param descriptor Schema descriptor to crawl model definitions on. + * @param predicate Filter predicate function, if applicable. + * @param decider Function that decides whether to recurse. + * @return Stream of field descriptors, recursively, which match the `predicate`, if provided. + */ + public static @Nonnull Stream forEachField(@Nonnull Descriptor descriptor, + @Nonnull Optional> predicate, + @Nonnull Predicate decider) { + Objects.requireNonNull(descriptor); + Objects.requireNonNull(predicate); + + return streamFieldsRecursive( + descriptor, + descriptor, + predicate, + decider, + "", + false + ); + } + /** * Crawl all fields, recursively, on the descriptor associated with the provided model instance, and return them in * a stream. @@ -1545,7 +1691,8 @@ public static void enforceAnyRole(@Nonnull Descriptor descriptor, /** * Crawl all fields, recursively, on the provided descriptor for a model instance. For each field encountered, run - * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. + * `predicate` to determine whether to include the field, filtering the returned stream of fields accordingly. By + * default, all field streaming methods run in parallel. * *

    If a `MESSAGE` field is encountered and the algorithm needs to decide whether to recurse, this variant includes * support for the `decider` function. `decider` is invoked to decide whether to recurse for opportunity to do so.

    @@ -1564,15 +1711,16 @@ public static void enforceAnyRole(@Nonnull Descriptor descriptor, public static @Nonnull Stream streamFields(@Nonnull Descriptor descriptor, @Nonnull Optional> predicate, @Nonnull Predicate decider) { - Objects.requireNonNull(descriptor, "cannot crawl fields on null descriptor"); - Objects.requireNonNull(predicate, "cannot pass `null` for optional predicate"); + Objects.requireNonNull(descriptor); + Objects.requireNonNull(predicate); return streamFieldsRecursive( descriptor, descriptor, predicate, decider, - "" + "", + true ); } @@ -1581,8 +1729,9 @@ public static void enforceAnyRole(@Nonnull Descriptor descriptor, @Nonnull Descriptor descriptor, @Nonnull Optional> predicate, @Nonnull Predicate decider, - @Nonnull String parent) { - return descriptor.getFields().parallelStream().flatMap((field) -> { + @Nonnull String parent, + @Nonnull Boolean parallel) { + return (parallel ? descriptor.getFields().parallelStream() : descriptor.getFields().stream()).flatMap((field) -> { var path = String.format("%s.%s", parent, field.getName()); var pointer = new FieldPointer( base, @@ -1600,7 +1749,8 @@ public static void enforceAnyRole(@Nonnull Descriptor descriptor, descriptor.findFieldByNumber(field.getNumber()).getMessageType(), predicate, decider, - path + path, + parallel )); } return branch; diff --git a/java/gust/backend/model/ObjectModelCodec.kt b/java/gust/backend/model/ObjectModelCodec.kt index 58e7b73b6..b33bca84f 100644 --- a/java/gust/backend/model/ObjectModelCodec.kt +++ b/java/gust/backend/model/ObjectModelCodec.kt @@ -14,7 +14,7 @@ import javax.annotation.concurrent.ThreadSafe */ @Immutable @ThreadSafe -class ObjectModelCodec private constructor(@Nonnull instance: Model) : +class ObjectModelCodec private constructor(@Nonnull private val instance: Model) : ModelCodec, Map> { /** * @return Builder for the model handled by this codec. @@ -29,6 +29,9 @@ class ObjectModelCodec private constructor(@Nonnull instance: Mo private val deserializer: ObjectModelDeserializer = ObjectModelDeserializer.defaultInstance(instance) // -- Components -- // + /** @inheritDoc */ + @Nonnull override fun instance(): Model = this.instance + /** * Acquire an instance of the [ModelSerializer] attached to this adapter. * diff --git a/java/gust/backend/model/OperationOptions.java b/java/gust/backend/model/OperationOptions.java index 061a5d561..e801f8c78 100644 --- a/java/gust/backend/model/OperationOptions.java +++ b/java/gust/backend/model/OperationOptions.java @@ -54,4 +54,9 @@ public interface OperationOptions { default @Nonnull Optional retries() { return Optional.empty(); } + + /** @return Whether to run in a transaction. */ + default @Nonnull Optional transactional() { + return Optional.empty(); + } } diff --git a/java/gust/backend/model/PersistenceManager.java b/java/gust/backend/model/PersistenceManager.java index b12494e09..732d51e87 100644 --- a/java/gust/backend/model/PersistenceManager.java +++ b/java/gust/backend/model/PersistenceManager.java @@ -16,6 +16,7 @@ /** * */ +@SuppressWarnings("rawtypes") public interface PersistenceManager { /* Nothing yet. */ } diff --git a/java/gust/backend/model/ProtoModelCodec.java b/java/gust/backend/model/ProtoModelCodec.java index dabd066dd..676590867 100644 --- a/java/gust/backend/model/ProtoModelCodec.java +++ b/java/gust/backend/model/ProtoModelCodec.java @@ -217,6 +217,12 @@ private final class ProtoMessageDeserializer implements ModelDeserializer> assetMap = new TreeMap<>(); + private static final @NonNull SortedMap> assetMap = new TreeMap<>(); /** Maps content blocks to their module names. */ - private static final @Nonnull Multimap modulesToTokens = MultimapBuilder + private static final @NonNull Multimap modulesToTokens = MultimapBuilder .hashKeys() .treeSetValues() .build(); /** Specifies a map of tokens to their content info. */ - private static final @Nonnull SortedMap tokenMap = new TreeMap<>(); + private static final @NonNull SortedMap tokenMap = new TreeMap<>(); /** Holds on to info related to a raw asset file. */ @Immutable static final class ContentInfo { /** Unique token for this asset. */ - final @Nonnull String token; + final @NonNull String token; /** Unique token for this asset. */ - final @Nonnull String module; + final @NonNull String module; /** Original filename for this asset. */ - final @Nonnull String filename; + final @NonNull String filename; /** Uncompressed data size. */ - final @Nonnull Long size; + final @NonNull Long size; /** Etag, calculated from the token and filename. */ - final @Nonnull String etag; + final @NonNull String etag; /** Smallest compression option. */ - final @Nonnull CompressionMode optimalCompression; + final @NonNull CompressionMode optimalCompression; /** Size of the optimally-compressed variant. */ - final @Nonnull Long compressedSize; + final @NonNull Long compressedSize; /** Count of variants held by this content info block. */ - final @Nonnull Integer variantCount; + final @NonNull Integer variantCount; /** Options that exist for pre-compressed variants of this content. */ - final @Nonnull EnumSet compressionOptions; + final @NonNull EnumSet compressionOptions; /** Pointer to the content record backing this object. */ - final @Nonnull AssetBundle.AssetContent content; + final @NonNull AssetBundle.AssetContent content; /** Raw constructor for content info metadata. */ - private ContentInfo(@Nonnull String token, - @Nonnull String module, - @Nonnull String filename, - @Nonnull Long size, - @Nonnull String etag, - @Nonnull CompressionMode optimalCompression, - @Nonnull Long compressedSize, - @Nonnull Integer variantCount, - @Nonnull EnumSet compressionOptions, - @Nonnull AssetBundle.AssetContent content) { + private ContentInfo(@NonNull String token, + @NonNull String module, + @NonNull String filename, + @NonNull Long size, + @NonNull String etag, + @NonNull CompressionMode optimalCompression, + @NonNull Long compressedSize, + @NonNull Integer variantCount, + @NonNull EnumSet compressionOptions, + @NonNull AssetBundle.AssetContent content) { this.token = token; this.module = module; this.filename = filename; @@ -154,7 +154,7 @@ private ContentInfo(@Nonnull String token, * @param algorithm Algorithm to use for etags. * @return Checked content info object. */ - static @Nonnull ContentInfo fromProto(@Nonnull AssetBundle.AssetContent content, @Nonnull String algorithm) { + static @NonNull ContentInfo fromProto(@NonNull AssetBundle.AssetContent content, @NonNull String algorithm) { try { MessageDigest digester = MessageDigest.getInstance(algorithm); digester.update(content.getModule().getBytes(StandardCharsets.UTF_8)); @@ -204,7 +204,7 @@ private ContentInfo(@Nonnull String token, * @param content Asset content protocol object. * @return Checked content info object. */ - static @Nonnull ContentInfo fromProto(AssetBundle.AssetContent content) { + static @NonNull ContentInfo fromProto(AssetBundle.AssetContent content) { return fromProto(content, ETAG_DIGEST_ALGORITHM); } } @@ -222,18 +222,18 @@ public enum ModuleType { @Immutable static final class ModuleMetadata { /** Name of this asset module. */ - final @Nonnull String name; + final @NonNull String name; /** Type of code/logic contained by this asset. */ - final @Nonnull ModuleType type; + final @NonNull ModuleType type; /** Raw asset records for this module. */ - final @Nonnull List assets; + final @NonNull List assets; /** Raw constructor for asset module metadata. */ - private ModuleMetadata(@Nonnull ModuleType type, - @Nonnull String name, - @Nonnull List assets) { + private ModuleMetadata(@NonNull ModuleType type, + @NonNull String name, + @NonNull List assets) { this.name = name; this.type = type; this.assets = assets; @@ -245,7 +245,7 @@ private ModuleMetadata(@Nonnull ModuleType type, * @param content Asset content protocol object. * @return Checked module info object. */ - static @Nonnull ModuleMetadata fromStyleProto(@Nonnull AssetBundle.StyleBundle content) { + static @NonNull ModuleMetadata fromStyleProto(@NonNull AssetBundle.StyleBundle content) { return new ModuleMetadata<>( ModuleType.CSS, content.getModule(), @@ -258,7 +258,7 @@ private ModuleMetadata(@Nonnull ModuleType type, * @param content Asset content protocol object. * @return Checked module info object. */ - static @Nonnull ModuleMetadata fromScriptProto(@Nonnull AssetBundle.ScriptBundle content) { + static @NonNull ModuleMetadata fromScriptProto(@NonNull AssetBundle.ScriptBundle content) { return new ModuleMetadata<>( ModuleType.JS, content.getModule(), @@ -271,10 +271,10 @@ private ModuleMetadata(@Nonnull ModuleType type, @SuppressWarnings("unused") public static final class ManagedAssetContent implements Comparable { /** Attached/encapsulated asset content and info. */ - private final @Nonnull ContentInfo content; + private final @NonNull ContentInfo content; /** Create a {@link ManagedAssetContent} object from scratch. */ - ManagedAssetContent(@Nonnull ContentInfo content) { + ManagedAssetContent(@NonNull ContentInfo content) { this.content = content; } @@ -293,63 +293,63 @@ public int hashCode() { } @Override - public int compareTo(@Nonnull ManagedAssetContent other) { + public int compareTo(@NonNull ManagedAssetContent other) { return this.content.token.compareTo(other.content.token); } /** @return Opaque token identifying this asset content. */ - public @Nonnull String getToken() { + public @NonNull String getToken() { return content.token; } /** @return Module name for this content chunk. */ - public @Nonnull String getModule() { + public @NonNull String getModule() { return content.module; } /** @return Pre-calculated ETag value for this asset. */ - public @Nonnull String getETag() { + public @NonNull String getETag() { return content.etag; } /** @return Original filename for the asset. */ - public @Nonnull String getFilename() { + public @NonNull String getFilename() { return content.filename; } /** @return Last-modified-timestamp for this asset. */ - public @Nonnull Timestamp getLastModified() { + public @NonNull Timestamp getLastModified() { return loadedBundle.getGenerated(); } /** @return Un-compressed size of the asset. */ - public @Nonnull Long getSize() { + public @NonNull Long getSize() { return content.size; } /** @return Optimal compression mode. */ - public @Nonnull CompressionMode getOptimalCompression() { + public @NonNull CompressionMode getOptimalCompression() { return content.optimalCompression; } /** @return Compressed size of the asset (optimal). */ @SuppressWarnings("WeakerAccess") - public @Nonnull Long getCompressedSize() { + public @NonNull Long getCompressedSize() { return content.compressedSize; } /** @return Count of variants that exist for this asset. */ - public @Nonnull Integer getVariantCount() { + public @NonNull Integer getVariantCount() { return content.variantCount; } /** @return Set of supported compression modes for this asset. */ - public @Nonnull EnumSet getCompressionOptions() { + public @NonNull EnumSet getCompressionOptions() { return content.compressionOptions; } /** Retrieve the content backing this info record. */ - public @Nonnull AssetBundle.AssetContent getContent() { + public @NonNull AssetBundle.AssetContent getContent() { return content.content; } } @@ -358,14 +358,14 @@ public int compareTo(@Nonnull ManagedAssetContent other) { @Immutable public static final class ManagedAsset implements Comparable { /** Resolved module metadata for this asset. */ - private final @Nonnull ModuleMetadata module; + private final @NonNull ModuleMetadata module; /** Logic references that constitute this managed asset, including dependencies, in reverse topological order. */ - private final @Nonnull Collection content; + private final @NonNull Collection content; /** Construct a new managed asset from scratch. */ - ManagedAsset(@Nonnull ModuleMetadata module, - @Nonnull Collection content) { + ManagedAsset(@NonNull ModuleMetadata module, + @NonNull Collection content) { this.module = module; this.content = content; } @@ -386,27 +386,27 @@ public int hashCode() { } @Override - public int compareTo(@Nonnull ManagedAsset other) { + public int compareTo(@NonNull ManagedAsset other) { return this.module.name.compareTo(other.module.name); } /** @return This module's assigned name. */ - public @Nonnull String getName() { + public @NonNull String getName() { return module.name; } /** @return This module's assigned type. */ - public @Nonnull ModuleType getType() { + public @NonNull ModuleType getType() { return module.type; } /** @return Collection of typed asset records constituting this bundle. */ - public @Nonnull Collection getAssets() { + public @NonNull Collection getAssets() { return module.assets; } /** @return Content configurations associated with this asset bundle. */ - public @Nonnull Collection getContent() { + public @NonNull Collection getContent() { return this.content; } } @@ -525,7 +525,7 @@ public static AssetManager acquire() throws IOException { * @param token Token uniquely identifying this asset (generated from the module name and content fingerprint). * @return Optional, either {@link Optional#empty()} if the asset could not be found, or wrapping the result. */ - public @Nonnull Optional assetDataByToken(@Nonnull String token) { + public @NonNull Optional assetDataByToken(@NonNull String token) { if (logging.isTraceEnabled()) logging.trace(format("Resolving asset by token '%s'.", token)); if (!tokenMap.containsKey(token)) { @@ -549,7 +549,7 @@ public static AssetManager acquire() throws IOException { * @return Optional, either {@link Optional#empty()} if the asset group could not be found, or wrapping the result. */ @SuppressWarnings("unused") - public @Nonnull Optional> assetMetadataByModule(@Nonnull String module) { + public @NonNull Optional> assetMetadataByModule(@NonNull String module) { if (logging.isTraceEnabled()) logging.trace(format("Resolving asset metadata at module '%s'.", module)); if (!assetMap.containsKey(module)) { diff --git a/java/gust/backend/transport/GoogleService.java b/java/gust/backend/transport/GoogleService.java index 1f8848ec8..d3a8c5d5a 100644 --- a/java/gust/backend/transport/GoogleService.java +++ b/java/gust/backend/transport/GoogleService.java @@ -26,7 +26,10 @@ public enum GoogleService { STORAGE("storage", null), /** Google Cloud Firestore (token:
    firestore
    ) */ - FIRESTORE("firestore", null); + FIRESTORE("firestore", null), + + /** Google Cloud Firestore (token:
    spanner
    ) */ + SPANNER("spanner", null); /** Prefix at which the specified service may be configured. */ private final @Nonnull String token; diff --git a/javatests/BUILD.bazel b/javatests/BUILD.bazel index a68d262ce..898bf6b15 100644 --- a/javatests/BUILD.bazel +++ b/javatests/BUILD.bazel @@ -46,6 +46,11 @@ test_suite( "//javatests/gust/backend/driver/inmemory:InMemoryCacheTest", "//javatests/gust/backend/driver/inmemory:InMemoryDriverTest", "//javatests/gust/backend/driver/firestore:FirestoreAdapterTest", + "//javatests/gust/backend/driver/spanner:SpannerAdapterTest", + "//javatests/gust/backend/driver/spanner:SpannerDDLTest", + "//javatests/gust/backend/driver/spanner:SpannerManagerTest", + "//javatests/gust/backend/driver/spanner:SpannerTemporalConverterTest", + "//javatests/gust/backend/driver/spanner:SpannerUtilTest", "//javatests/gust/util:HexText", "//javatests/gust/util:PairTest", ], diff --git a/javatests/gust/backend/driver/firestore/FirestoreAdapterTest.java b/javatests/gust/backend/driver/firestore/FirestoreAdapterTest.java index cda39c1f3..4444be604 100644 --- a/javatests/gust/backend/driver/firestore/FirestoreAdapterTest.java +++ b/javatests/gust/backend/driver/firestore/FirestoreAdapterTest.java @@ -25,12 +25,16 @@ import gust.backend.model.PersonRecord.PersonKey; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DynamicTest; import org.testcontainers.containers.FirestoreEmulatorContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; import javax.annotation.Nonnull; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -79,6 +83,7 @@ void initExecutor() { } @AfterAll + @SuppressWarnings("ResultOfMethodCallIgnored") static void shutdownExecutor() throws InterruptedException { executorService.shutdownNow(); executorService.awaitTermination(5, TimeUnit.SECONDS); @@ -97,4 +102,13 @@ static void shutdownExecutor() throws InterruptedException { protected void acquireDriver() { assertNotNull(personAdapter, "should not get `null` for adapter acquire"); } + + /** {@inheritDoc} */ + @Override + protected @Nonnull Optional> unsupportedDriverTests() { + return Optional.of(Arrays.asList( + "storeEntityUpdateNotFound", + "storeEntityCollission" + )); + } } diff --git a/javatests/gust/backend/driver/spanner/BUILD.bazel b/javatests/gust/backend/driver/spanner/BUILD.bazel new file mode 100644 index 000000000..fdc2baeba --- /dev/null +++ b/javatests/gust/backend/driver/spanner/BUILD.bazel @@ -0,0 +1,139 @@ +## +# Copyright © 2020, The Gust Framework Authors. All rights reserved. +# +# The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, +# are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of +# this code in object or source form requires and implies consent and agreement to that license in principle and +# practice. Source or object code not listing this header, or unless specified otherwise, remain the property of +# Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to +# Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected +# by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, +# is strictly forbidden except in adherence with assigned license requirements. +## + +package( + default_visibility = ["//visibility:public"], +) + +load( + "//defs/toolchain/java:testing.bzl", + java_test = "jdk_test", +) +load( + "//defs/toolchain:deps.bzl", + "javaproto", + "maven", +) + +SPANNER_EMULATOR_VERSION = "1.2.0" + +SPANNER_MODE = "EMULATOR" + +SPANNER_PROJECT = "elide-ai" + +SPANNER_INSTANCE = "testing" + +SPANNER_DATABASE = "tstdb" + +_COMMON_DEPS = [ + "//java/gust/backend/runtime:runtime", + maven("com.google.guava:guava"), + maven("com.google.protobuf:protobuf-java"), + maven("com.google.api.grpc:proto-google-common-protos"), + maven("com.google.cloud:google-cloud-spanner"), + maven("org.testcontainers:testcontainers"), + maven("org.testcontainers:junit-jupiter"), + maven("org.testcontainers:gcloud"), + maven("org.slf4j:slf4j-api"), + maven("com.google.truth:truth"), + maven("com.google.truth.extensions:truth-proto-extension"), + maven("com.google.truth.extensions:truth-java8-extension"), +] + +java_test( + name = "SpannerAdapterTest", + srcs = ["SpannerAdapterTest.java"], + classpath_resources = [ + "//javatests:logback.xml", + ], + jvm_flags = [ + "-De2e.spannerVersion=%s" % SPANNER_EMULATOR_VERSION, + "-De2e.spannerProject=%s" % SPANNER_PROJECT, + "-De2e.spannerInstance=%s" % SPANNER_INSTANCE, + "-De2e.spannerDatabase=%s" % SPANNER_DATABASE, + "-De2e.spannerMode=%s" % SPANNER_MODE, + ], + test_package = "gust.backend.driver.spanner", + deps = [ + "//java/gust/backend/driver/spanner:SpannerAdapter", + "//java/gust/backend/driver/spanner:SpannerDriver", + "//java/gust/backend/driver/spanner:SpannerGeneratedDDL", + "//java/gust/backend/driver/spanner:SpannerDriverSettings", + "//java/gust/backend/driver/inmemory:InMemoryCache", + "//java/gust/backend/model:PersistenceOperationFailed", + "//javatests/gust/backend/model:GenericPersistenceAdapterTest", + javaproto("//javatests/gust/backend/model:person"), + ] + _COMMON_DEPS, +) + +java_test( + name = "SpannerDDLTest", + srcs = ["SpannerDDLTest.java"], + classpath_resources = [ + "//javatests:logback.xml", + ], + test_package = "gust.backend.driver.spanner", + deps = [ + "//java/gust/backend/driver/spanner:SpannerGeneratedDDL", + "//java/gust/backend/driver/spanner:SpannerDriverSettings", + javaproto("//javatests/gust/backend/model:person"), + ] + _COMMON_DEPS, +) + +java_test( + name = "SpannerManagerTest", + srcs = ["SpannerManagerTest.java"], + classpath_resources = [ + "//javatests:logback.xml", + ], + test_package = "gust.backend.driver.spanner", + deps = [ + "//java/gust/backend/driver/spanner:SpannerManager", + "//java/gust/backend/driver/spanner:SpannerDriverSettings", + "//java/gust/backend/driver/spanner:SpannerTransportConfig", + "//java/gust/backend/driver/spanner:SpannerAdapter", + javaproto("//javatests/gust/backend/model:person"), + ] + _COMMON_DEPS, +) + +java_test( + name = "SpannerTemporalConverterTest", + srcs = ["SpannerTemporalConverterTest.java"], + classpath_resources = [ + "//javatests:logback.xml", + ], + test_package = "gust.backend.driver.spanner", + deps = [ + "//java/gust/backend/driver/spanner:SpannerTemporalConverter", + javaproto("//javatests/gust/backend/model:person"), + maven("com.google.cloud:google-cloud-core"), + maven("com.google.api.grpc:proto-google-common-protos"), + ] + _COMMON_DEPS, +) + +java_test( + name = "SpannerUtilTest", + srcs = ["SpannerUtilTest.java"], + classpath_resources = [ + "//javatests:logback.xml", + ], + test_package = "gust.backend.driver.spanner", + deps = [ + "//java/gust/backend/driver/spanner:SpannerGeneratedDDL", + "//java/gust/backend/driver/spanner:SpannerDriverSettings", + javaproto("//gust/core:datamodel"), + javaproto("//javatests/gust/backend/model:person"), + "//java/gust/backend/driver/spanner:SpannerUtil", + "//java/gust/backend/model:ModelMetadata", + ] + _COMMON_DEPS, +) diff --git a/javatests/gust/backend/driver/spanner/SpannerAdapterTest.java b/javatests/gust/backend/driver/spanner/SpannerAdapterTest.java new file mode 100644 index 000000000..8014d68b8 --- /dev/null +++ b/javatests/gust/backend/driver/spanner/SpannerAdapterTest.java @@ -0,0 +1,288 @@ +/* + * Copyright © 2020, The Gust Framework Authors. All rights reserved. + * + * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, + * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of + * this code in object or source form requires and implies consent and agreement to that license in principle and + * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of + * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to + * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected + * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, + * is strictly forbidden except in adherence with assigned license requirements. + */ +package gust.backend.driver.spanner; + +import com.google.api.gax.core.NoCredentialsProvider; +import com.google.api.gax.grpc.GrpcTransportChannel; +import com.google.api.gax.rpc.FixedTransportChannelProvider; +import com.google.api.gax.rpc.TransportChannelProvider; +import com.google.cloud.NoCredentials; +import com.google.cloud.grpc.GrpcTransportOptions; +import com.google.cloud.spanner.*; +import com.google.common.util.concurrent.ListeningScheduledExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import gust.backend.model.GenericPersistenceAdapterTest; +import gust.backend.model.PersistenceOperationFailed; +import gust.backend.model.PersonRecord; +import gust.backend.runtime.Logging; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.testcontainers.containers.*; +import org.testcontainers.containers.SpannerEmulatorContainer; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.utility.DockerImageName; + +import javax.annotation.Nonnull; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.*; + + +/** Tests for the {@link SpannerAdapter}. */ +@Testcontainers +@SuppressWarnings("UnstableApiUsage") +public final class SpannerAdapterTest extends GenericPersistenceAdapterTest< + SpannerAdapter> { + private static final Logger logging = Logging.logger(SpannerAdapterTest.class); + private static final String spannerVersion = System.getProperty("e2e.spannerVersion", "1.2.0"); + private static ListeningScheduledExecutorService executorService; + private static SpannerAdapter personAdapter; + private static SpannerAdapter sampleAdapter; + + private static final String SPANNER_MODE = System.getProperty("e2e.spannerMode", "EMULATOR"); + private static final String PROJECT_ID = System.getProperty("e2e.spannerProject", "elide-ai"); + private static final String INSTANCE_ID = System.getProperty("e2e.spannerInstance", "testing"); + private static final String DATABASE_ID = System.getProperty("e2e.spannerDatabase", "testdb"); + + private static final DatabaseId database = DatabaseId.of( + PROJECT_ID, + INSTANCE_ID, + DATABASE_ID + ); + + Network network = Network.newNetwork(); + + @Container + public SpannerEmulatorContainer spanner = new SpannerEmulatorContainer( + DockerImageName.parse("gcr.io/cloud-spanner-emulator/emulator:" + spannerVersion) + ).withNetwork(network).withNetworkAliases("spanner"); + + @BeforeEach + void initSpannerTests() throws InterruptedException, ExecutionException { + logging.info("Initializing executor service for Spanner tests..."); + executorService = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor()); + + TransportChannelProvider channelProvider; + SpannerOptions.Builder optionsBuilder; + SpannerOptions options; + Spanner client; + + // step one: setup all the connections and access we need, based on whether the simulator is active or not. + if ("EMULATOR".equals(SPANNER_MODE)) { + logging.info("Running in EMULATOR mode. Setting up Spanner emulator..."); + + optionsBuilder = SpannerOptions.newBuilder() + .setEmulatorHost(spanner.getEmulatorGrpcEndpoint()) + .setCredentials(NoCredentials.getInstance()) + .setProjectId(PROJECT_ID); + + ManagedChannel channel = ManagedChannelBuilder.forTarget(spanner.getEmulatorGrpcEndpoint()) + .usePlaintext() + .build(); + + channelProvider = FixedTransportChannelProvider.create( + GrpcTransportChannel.create(channel) + ); + optionsBuilder.setChannelProvider(channelProvider); + + optionsBuilder.getSpannerStubSettingsBuilder() + .setTransportChannelProvider(channelProvider) + .setCredentialsProvider(NoCredentialsProvider.create()); + + optionsBuilder.getDatabaseAdminStubSettingsBuilder() + .setTransportChannelProvider(channelProvider) + .setCredentialsProvider(NoCredentialsProvider.create()); + + optionsBuilder.getInstanceAdminStubSettingsBuilder() + .setTransportChannelProvider(channelProvider) + .setCredentialsProvider(NoCredentialsProvider.create()); + + options = optionsBuilder.build(); + client = options.getService(); + + // step one: initialize a new emulated instance + logging.info("Standing up emulated Spanner instance..."); + InstanceConfigId instanceConfig = InstanceConfigId.of(PROJECT_ID, "emulator-config"); + InstanceId instanceId = InstanceId.of(PROJECT_ID, INSTANCE_ID); + InstanceAdminClient insAdminClient = client.getInstanceAdminClient(); + Instance instance = insAdminClient.createInstance( + InstanceInfo.newBuilder(instanceId) + .setNodeCount(1) + .setDisplayName("Test instance") + .setInstanceConfigId(instanceConfig) + .build() + ).get(); + + logging.info("Verifying new instance..."); + var newInstance = insAdminClient.listInstances().getValues().iterator().next(); + assertNotNull(newInstance, "new instance should not be null"); + assertEquals(INSTANCE_ID, newInstance.getId().getInstance(), + "instance ID should be expected value"); + assertEquals(InstanceInfo.State.READY, newInstance.getState(), + "new instance should be ready immediately"); + logging.info("New instance is READY: \n{}", newInstance.toString()); + + var peopleTableDdlStatement = SpannerGeneratedDDL.generateTableDDL( + PersonRecord.Person.getDefaultInstance()).build().getGeneratedStatement().toString(); + + var typeExamplesDdlStatement = SpannerGeneratedDDL.generateTableDDL( + PersonRecord.TypeBuffet.getDefaultInstance()).build().getGeneratedStatement().toString(); + + // step two: initialize a new database in the instance + logging.info("Creating emulated test database `People` with DDL statement: \n{}", + peopleTableDdlStatement); + logging.info("Creating emulated test database `TypeExamples` with DDL statement: \n{}", + typeExamplesDdlStatement); + DatabaseAdminClient dbAdminClient = client.getDatabaseAdminClient(); + dbAdminClient.createDatabase( + INSTANCE_ID, + DATABASE_ID, + Arrays.asList(peopleTableDdlStatement, typeExamplesDdlStatement) + ).get(); + + logging.info("Verifying new database..."); + var newDatabase = instance.getDatabase(DATABASE_ID); + assertNotNull(newDatabase, "new database should not be null"); + assertEquals(DATABASE_ID, newDatabase.getId().getDatabase(), "database ID should be expected value"); + logging.info("New database is READY: \n{}", newDatabase.toString()); + + logging.info("Emulator ready. Setting up Spanner adapter..."); + personAdapter = SpannerAdapter.acquire( + options.toBuilder(), + database, + channelProvider, + Optional.of(NoCredentialsProvider.create()), + Optional.empty(), + GrpcTransportOptions.newBuilder().build(), + executorService, + PersonRecord.PersonKey.getDefaultInstance(), + PersonRecord.Person.getDefaultInstance(), + SpannerDriverSettings.DEFAULTS, + Optional.empty() + ); + + sampleAdapter = SpannerAdapter.acquire( + options.toBuilder(), + database, + channelProvider, + Optional.of(NoCredentialsProvider.create()), + Optional.empty(), + GrpcTransportOptions.newBuilder().build(), + executorService, + PersonRecord.TypeBuffet.SampleKey.getDefaultInstance(), + PersonRecord.TypeBuffet.getDefaultInstance(), + SpannerDriverSettings.DEFAULTS, + Optional.empty() + ); + } else if ("LIVE".equals(SPANNER_MODE)) { + logging.info("Running in LIVE mode. Setting up Spanner emulator..."); + + optionsBuilder = SpannerOptions.newBuilder() + .setProjectId(PROJECT_ID); + + // setup production adapter + personAdapter = SpannerAdapter.acquire( + PersonRecord.PersonKey.getDefaultInstance(), + PersonRecord.Person.getDefaultInstance(), + database, + optionsBuilder, + executorService + ); + + sampleAdapter = SpannerAdapter.acquire( + PersonRecord.TypeBuffet.SampleKey.getDefaultInstance(), + PersonRecord.TypeBuffet.getDefaultInstance(), + database, + optionsBuilder, + executorService + ); + + } else { + throw new IllegalArgumentException("Unrecognized Spanner test mode: '" + SPANNER_MODE + "'."); + } + } + + @AfterAll + @SuppressWarnings("ResultOfMethodCallIgnored") + static void shutdownExecutor() throws InterruptedException { + executorService.shutdownNow(); + executorService.awaitTermination(5, TimeUnit.SECONDS); + executorService = null; + personAdapter = null; + } + + // -- Driver Tests: Overrides -- // + + /** {@inheritDoc} */ + @Override + protected @Nonnull SpannerAdapter adapter() { + return personAdapter; + } + + /** {@inheritDoc} */ + @Override + protected void acquireDriver() { + SpannerAdapter personAdapter = SpannerAdapter.acquire( + PersonRecord.PersonKey.getDefaultInstance(), + PersonRecord.Person.getDefaultInstance(), + database, + executorService); + assertNotNull(personAdapter, "should not get `null` for adapter acquire"); + } + + /** {@inheritDoc} */ + @Override + protected @Nonnull Optional> unsupportedDriverTests() { + return Optional.of(Arrays.asList( + "storeEntityUpdateNotFound", + "storeEntityCollission" + )); + } + + // -- Concrete Tests -- // + + @SuppressWarnings("ConstantConditions") + @Test public void testNullchecks() { + assertNotNull(personAdapter, "should not get `null` for adapter acquire"); + assertThrows(PersistenceOperationFailed.class, () -> personAdapter.fetch(null)); + assertThrows(NullPointerException.class, () -> personAdapter.retrieve(null, null)); + assertThrows(NullPointerException.class, () -> personAdapter.retrieve( + PersonRecord.PersonKey.newBuilder().setId("test").build(), null)); + assertThrows(NullPointerException.class, () -> personAdapter.create(null)); + assertThrows(NullPointerException.class, () -> personAdapter.persist(null, null, null)); + assertThrows(NullPointerException.class, () -> personAdapter.persist(null, + PersonRecord.Person.newBuilder().build(), null)); + assertThrows(NullPointerException.class, () -> personAdapter.delete(null)); + assertThrows(NullPointerException.class, () -> personAdapter.delete(null, null)); + } + + @Test public void testMustDeclareTableName() { + assertNotNull(personAdapter, "should not get `null` for adapter acquire"); + assertThrows(IllegalArgumentException.class, () -> sampleAdapter.retrieve( + PersonRecord.TypeBuffet.SampleKey.newBuilder() + .setId(123L) + .build(), + SpannerDriver.SpannerFetchOptions.DEFAULTS + )); + } +} diff --git a/javatests/gust/backend/driver/spanner/SpannerDDLTest.java b/javatests/gust/backend/driver/spanner/SpannerDDLTest.java new file mode 100644 index 000000000..ffb723421 --- /dev/null +++ b/javatests/gust/backend/driver/spanner/SpannerDDLTest.java @@ -0,0 +1,152 @@ +/* + * Copyright © 2020, The Gust Framework Authors. All rights reserved. + * + * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, + * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of + * this code in object or source form requires and implies consent and agreement to that license in principle and + * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of + * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to + * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected + * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, + * is strictly forbidden except in adherence with assigned license requirements. + */ +package gust.backend.driver.spanner; + +import gust.backend.model.PersonRecord; +import org.junit.jupiter.api.Test; + +import javax.annotation.Nonnull; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static gust.backend.driver.spanner.SpannerGeneratedDDL.*; +import static com.google.common.truth.Truth.assertWithMessage; + + +/** Tests for DDL utilities related to Spanner. */ +public final class SpannerDDLTest { + private void generatorAssertions(@Nonnull SpannerGeneratedDDL generator, + @Nonnull String tableName) { + assertNotNull(generator, + "should be able to resolve a schema generator for an arbitrary model interleaved in a parent"); + assertEquals(tableName, generator.getTableName(), + "generated DDL statement table name should match expected value"); + assertNotNull(generator.getModel(), "should be able to get the model matching the generator"); + assertNotNull(generator.toString(), "`toString()` on generator should not return `null`"); + assertFalse(generator.getColumns().isEmpty(), + "generated DDL statement column set should not be empty"); + } + + @Test public void testGenerateDDL() { + var generator = SpannerGeneratedDDL.generateTableDDL( + PersonRecord.Person.getDefaultInstance(), + Optional.empty() + ); + + assertNotNull(generator, "should be able to resolve a schema generator for an arbitrary model"); + } + + @Test public void testGeneratePersonDDL() { + var generator = SpannerGeneratedDDL.generateTableDDL( + PersonRecord.Person.getDefaultInstance(), + Optional.empty() + ).build(); + + var expectedBasicCreate = ( + "CREATE TABLE People (" + + "ID STRING(240) NOT NULL, " + + "Name STRING(1024), " + + "ContactInfo STRING(2048)" + + ") PRIMARY KEY (ID ASC)" + ); + + generatorAssertions(generator, "People"); + assertWithMessage("generated DDL statement should match expected output") + .that(generator.getGeneratedStatement().toString()) + .isEqualTo(expectedBasicCreate); + } + + @Test public void testGeneratePersonDDLInterleaved() { + var generator = SpannerGeneratedDDL.generateTableDDL( + PersonRecord.Person.getDefaultInstance(), + Optional.empty()) + .setInterleaveTarget(Optional.of(InterleaveTarget + .forParent("ContactList"))) + .build(); + + var expectedInterleavedCreate = ( + "CREATE TABLE People (" + + "ID STRING(240) NOT NULL, " + + "Name STRING(1024), " + + "ContactInfo STRING(2048)" + + ") " + + "PRIMARY KEY (ID ASC), " + + "INTERLEAVE IN PARENT ContactList" + ); + + generatorAssertions(generator, "People"); + assertWithMessage("generated DDL statement should match expected output") + .that(generator.getGeneratedStatement().toString()) + .isEqualTo(expectedInterleavedCreate); + } + + @Test public void testGeneratePersonDescendingKey() { + var generator = SpannerGeneratedDDL.generateTableDDL( + PersonRecord.Person.getDefaultInstance(), + Optional.empty()) + .setKeySortDirection(SortDirection.DESC) + .build(); + + var expectedInterleavedCreate = ( + "CREATE TABLE People (" + + "ID STRING(240) NOT NULL, " + + "Name STRING(1024), " + + "ContactInfo STRING(2048)" + + ") " + + "PRIMARY KEY (ID DESC)" + ); + + generatorAssertions(generator, "People"); + assertWithMessage("generated DDL statement should match expected output") + .that(generator.getGeneratedStatement().toString()) + .isEqualTo(expectedInterleavedCreate); + } + + @Test public void testGenerateTypeBuffet() { + var generator = SpannerGeneratedDDL.generateTableDDL( + PersonRecord.TypeBuffet.getDefaultInstance(), + Optional.empty() + ).build(); + + var expectedBuffetTable = ( + "CREATE TABLE TypeExamples (" + + "ID INT64 NOT NULL, " + + "IntNormal INT64, " + + "IntDouble INT64, " + + "UintNormal INT64, " + + "UintDouble INT64, " + + "SintNormal INT64, " + + "SintDouble INT64, " + + "FixedNormal INT64, " + + "FixedDouble INT64, " + + "SfixedNormal INT64, " + + "SfixedDouble INT64, " + + "StringField STRING(2048), " + + "BoolField BOOL, " + + "BytesField BYTES(2048), " + + "FloatField FLOAT64, " + + "DoubleField FLOAT64, " + + "EnumField STRING(32), " + + "Labels ARRAY, " + + "SpannerNumericField NUMERIC, " + + "Timestamp TIMESTAMP, " + + "Date DATE" + + ") PRIMARY KEY (ID ASC)" + ); + + generatorAssertions(generator, "TypeExamples"); + assertWithMessage("generated DDL statement should match expected output") + .that(generator.getGeneratedStatement().toString()) + .isEqualTo(expectedBuffetTable); + } +} diff --git a/javatests/gust/backend/driver/spanner/SpannerManagerTest.java b/javatests/gust/backend/driver/spanner/SpannerManagerTest.java new file mode 100644 index 000000000..c44430710 --- /dev/null +++ b/javatests/gust/backend/driver/spanner/SpannerManagerTest.java @@ -0,0 +1,223 @@ +/* + * Copyright © 2020, The Gust Framework Authors. All rights reserved. + * + * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, + * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of + * this code in object or source form requires and implies consent and agreement to that license in principle and + * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of + * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to + * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected + * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, + * is strictly forbidden except in adherence with assigned license requirements. + */ +package gust.backend.driver.spanner; + +import com.google.cloud.spanner.DatabaseId; +import gust.backend.model.PersonRecord; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + + +/** Tests for the {@link SpannerManager}. */ +public class SpannerManagerTest { + @AfterAll + public static void shutDownAllManagers() { + SpannerManager.acquire().close(); + } + + @Test public void testAcquireSpannerManager() { + assertNotNull(SpannerManager.acquire(), + "should be able to acquire a `SpannerManager` with no args"); + assertSame(SpannerManager.acquire(), SpannerManager.acquire(), + "should get singleton on multiple calls to `acquire`"); + } + + @Test public void testConfigureSpannerManager() { + var manager = SpannerManager + .acquire() + .configureForDatabase(DatabaseId.of( + "sample-project", + "instance", + "database" + )).build(); + + assertNotNull(manager, "should not get `null` for configured manager"); + // note: intentionally not cleaning up so it gets caught by `AfterAll` + } + + @Test public void testGetAdapterFromManager() { + var manager = SpannerManager + .acquire() + .configureForDatabase(DatabaseId.of( + "sample-project", + "instance", + "database" + )).build(); + + assertNotNull(manager, "should not get `null` for configured manager"); + var adapter = manager.adapter( + PersonRecord.PersonKey.getDefaultInstance(), + PersonRecord.Person.getDefaultInstance() + ); + + assertNotNull(adapter, "should be able to acquire adapter from configured manager"); + assertDoesNotThrow(manager::close); + } + + @Test public void testGetCachedAdapterFromManager() { + var manager = SpannerManager + .acquire() + .configureForDatabase(DatabaseId.of( + "sample-project", + "instance", + "database" + )).build(); + + assertNotNull(manager, "should not get `null` for configured manager"); + var adapter = manager.adapter( + PersonRecord.PersonKey.getDefaultInstance(), + PersonRecord.Person.getDefaultInstance() + ); + + assertNotNull(adapter, "should be able to acquire adapter from configured manager"); + + var identical = manager.adapter( + PersonRecord.PersonKey.getDefaultInstance(), + PersonRecord.Person.getDefaultInstance() + ); + + assertNotNull(identical, "should be able to acquire adapter from configured manager"); + assertSame(adapter, identical, "cached adapters should be identical"); + assertDoesNotThrow(manager::close); + } + + @Test public void testNoCacheBleed() { + var manager1 = SpannerManager + .acquire() + .configureForDatabase(DatabaseId.of( + "sample-project", + "instance", + "database" + )).build(); + + var manager2 = SpannerManager + .acquire() + .configureForDatabase(DatabaseId.of( + "sample-project", + "instance", + "database" + )).build(); + + assertNotNull(manager1, "should not get `null` for configured manager"); + assertNotNull(manager2, "should not get `null` for configured manager"); + var adapter1 = manager1.adapter( + PersonRecord.PersonKey.getDefaultInstance(), + PersonRecord.Person.getDefaultInstance() + ); + var adapter2 = manager2.adapter( + PersonRecord.PersonKey.getDefaultInstance(), + PersonRecord.Person.getDefaultInstance() + ); + + assertNotNull(adapter1, "should be able to acquire adapter from configured manager"); + assertNotNull(adapter2, "should be able to acquire adapter from configured manager"); + + var identical1 = manager1.adapter( + PersonRecord.PersonKey.getDefaultInstance(), + PersonRecord.Person.getDefaultInstance() + ); + var identical2 = manager2.adapter( + PersonRecord.PersonKey.getDefaultInstance(), + PersonRecord.Person.getDefaultInstance() + ); + + assertNotNull(identical1, "should be able to acquire adapter from configured manager"); + assertNotNull(identical2, "should be able to acquire adapter from configured manager"); + assertSame(adapter2, identical2, "cached adapters should be identical"); + + assertNotSame(adapter1, identical2, "cross-manager adapters should never be cached together"); + assertNotSame(adapter2, identical1, "cross-manager adapters should never be cached together"); + assertDoesNotThrow(manager1::close); + assertDoesNotThrow(manager2::close); + } + + @Test public void testManagerRepeatedlyCallClose() { + var manager = SpannerManager + .acquire() + .configureForDatabase(DatabaseId.of( + "sample-project", + "instance", + "database" + )).build(); + assertDoesNotThrow(manager::close); + assertDoesNotThrow(manager::close); + assertDoesNotThrow(manager::close); + assertDoesNotThrow(manager::close); + } + + @Test public void testClosedManagersCannotSpawn() { + var manager = SpannerManager + .acquire() + .configureForDatabase(DatabaseId.of( + "sample-project", + "instance", + "database" + )).build(); + + var adapter = manager.adapter( + PersonRecord.PersonKey.getDefaultInstance(), + PersonRecord.Person.getDefaultInstance() + ); + + assertNotNull(adapter, "should be able to spawn regular adapter before close"); + assertDoesNotThrow(manager::close); + assertThrows(IllegalStateException.class, () -> manager.adapter( + PersonRecord.PersonKey.getDefaultInstance(), + PersonRecord.Person.getDefaultInstance() + ), "should not be able to spawn adapters from a closed manager"); + } + + @Test public void testAutoCloseManager() { + try (var manager = SpannerManager + .acquire() + .configureForDatabase(DatabaseId.of( + "sample-project", + "instance", + "database" + )).build()) { + assertNotNull(manager, "should be able to acquire auto-closeable manager"); + assertFalse(manager.getClosed(), "manager should not be closed while in auto-close"); + assertEquals("database", manager.getDatabase().getDatabase(), + "database should be expected value for bound manager"); + } + + var manager = SpannerManager + .acquire() + .configureForDatabase(DatabaseId.of( + "sample-project", + "instance", + "database" + )).build(); + try (manager) { + assertNotNull(manager, "should be able to acquire auto-closeable manager"); + assertFalse(manager.getClosed(), "manager should not be closed while in auto-close"); + } finally { + assertTrue(manager.getClosed(), "manager should auto-close after main block"); + } + } + + @Test public void testAllManagersShouldBeAccurate() { + var manager = SpannerManager + .acquire() + .configureForDatabase(DatabaseId.of( + "sample-project", + "instance", + "database" + )).build(); + assertFalse(SpannerManager.allManagers().isEmpty(), + "spawning a manager should be sufficient to show up in `allManagers`"); + manager.close(); + } +} diff --git a/javatests/gust/backend/driver/spanner/SpannerTemporalConverterTest.java b/javatests/gust/backend/driver/spanner/SpannerTemporalConverterTest.java new file mode 100644 index 000000000..dcc5613c3 --- /dev/null +++ b/javatests/gust/backend/driver/spanner/SpannerTemporalConverterTest.java @@ -0,0 +1,63 @@ +package gust.backend.driver.spanner; + +import com.google.cloud.Date; +import com.google.cloud.Timestamp; +import org.junit.jupiter.api.Test; + +import java.time.Instant; + +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; +import static org.junit.jupiter.api.Assertions.*; + + +/** Tests for the {@link SpannerTemporalConverter}. */ +public final class SpannerTemporalConverterTest { + private final Instant now = Instant.now(); + + // Well-formed PB timestamp. + private final com.google.protobuf.Timestamp pbTimestamp = com.google.protobuf.Timestamp.newBuilder() + .setSeconds(now.getEpochSecond()) + .setNanos(now.getNano()) + .build(); + + // Well-formed cloud timestamp. + private final Timestamp cloudTimestamp = Timestamp.fromProto(pbTimestamp); + + // Well-formed PB date. + private final com.google.type.Date pbDate = com.google.type.Date.newBuilder() + .setDay(29) + .setMonth(7) + .setYear(2021) + .build(); + + // Well-formed cloud date. + private final Date cloudDate = Date.fromYearMonthDay(2021, 7, 29); + + @Test public void testConvertTimestampToCloud() { + assertThat(pbTimestamp).hasAllRequiredFields(); + assertThat(SpannerTemporalConverter.cloudTimestampFromProto(pbTimestamp).toProto()) + .isEqualTo(cloudTimestamp.toProto()); + } + + @Test public void testConvertCloudToTimestamp() { + assertThat(cloudTimestamp.toProto()).hasAllRequiredFields(); + assertThat(SpannerTemporalConverter.protoTimestampFromCloud(cloudTimestamp)) + .isEqualTo(pbTimestamp); + } + + @Test public void testConvertDateToCloud() { + assertThat(pbDate).hasAllRequiredFields(); + + var cloudDate = SpannerTemporalConverter.cloudDateFromProto(pbDate); + assertNotNull(cloudDate, "should not get `null` when converting to cloud date from proto"); + assertEquals(pbDate.getYear(), cloudDate.getYear(), "year value should copy properly"); + assertEquals(pbDate.getMonth(), cloudDate.getMonth(), "month value should copy properly"); + assertEquals(pbDate.getDay(), cloudDate.getDayOfMonth(), "day value should copy properly"); + } + + @Test public void testConvertCloudToDate() { + var convertedPbDate = SpannerTemporalConverter.protoDateFromCloud(cloudDate); + assertNotNull(convertedPbDate, "should not get `null` when converting to PB date from cloud structure"); + assertThat(convertedPbDate).isEqualTo(pbDate); + } +} diff --git a/javatests/gust/backend/driver/spanner/SpannerUtilTest.java b/javatests/gust/backend/driver/spanner/SpannerUtilTest.java new file mode 100644 index 000000000..668b19c8b --- /dev/null +++ b/javatests/gust/backend/driver/spanner/SpannerUtilTest.java @@ -0,0 +1,351 @@ +/* + * Copyright © 2020, The Gust Framework Authors. All rights reserved. + * + * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted, + * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of + * this code in object or source form requires and implies consent and agreement to that license in principle and + * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of + * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to + * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected + * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form, + * is strictly forbidden except in adherence with assigned license requirements. + */ +package gust.backend.driver.spanner; + +import com.google.cloud.spanner.Type; +import com.google.protobuf.Descriptors; +import gust.backend.model.ModelMetadata; +import gust.backend.model.PersonRecord; +import org.junit.jupiter.api.Test; +import tools.elide.core.SpannerFieldOptions; +import tools.elide.core.SpannerOptions; +import tools.elide.core.TableFieldOptions; + +import javax.annotation.Nonnull; + +import java.util.Optional; + +import static gust.backend.model.ModelMetadata.*; +import static gust.backend.driver.spanner.SpannerUtil.*; +import static org.junit.jupiter.api.Assertions.*; + + +/** Spanner utility unit testing. */ +public final class SpannerUtilTest { + private void assertResolvedType(@Nonnull ModelMetadata.FieldPointer pointer, + @Nonnull Type.Code typeCode, + @Nonnull SpannerOptions.SpannerType spannerType, + boolean repeated) { + if (repeated) { + assertSame( + Type.Code.ARRAY, + resolveType(pointer, spannerType).getCode(), + "repeated type should present as ARRAY type in Spanner" + ); + assertSame( + typeCode, + resolveType(pointer, spannerType).getArrayElementType().getCode(), + String.format("repeated type should present as %s type in Spanner", spannerType.name()) + ); + } else { + assertEquals( + typeCode, + resolveType(pointer, spannerType).getCode(), + String.format("%s resolved type should match %s type", typeCode.name(), typeCode.name()) + ); + } + } + + @Test public void testResolveDefaultGroupTypeFail() { + var nameField = ModelMetadata.FieldPointer.fieldAtName( + PersonRecord.Person.getDescriptor(), + "name" + ); + + assertThrows(IllegalArgumentException.class, () -> + resolveDefaultType(nameField, Descriptors.FieldDescriptor.Type.GROUP, SpannerDriverSettings.DEFAULTS) + ); + } + + @Test public void testResolveEnumsAsNumbers() { + var nameField = ModelMetadata.FieldPointer.fieldAtName( + PersonRecord.Person.getDescriptor(), + "name" + ); + + var enumNumbers = resolveDefaultType( + nameField, + Descriptors.FieldDescriptor.Type.ENUM, + new SpannerDriverSettings() { + @Nonnull + @Override + public Boolean enumsAsNumbers() { + return true; + } + } + ); + + assertEquals( + Type.Code.INT64, + enumNumbers.getCode(), + "INT64 should be type of integer enums" + ); + } + + @Test public void testResolveEnumsAsStrings() { + var nameField = ModelMetadata.FieldPointer.fieldAtName( + PersonRecord.Person.getDescriptor(), + "name" + ); + + var enumStrings = resolveDefaultType( + nameField, + Descriptors.FieldDescriptor.Type.ENUM, + new SpannerDriverSettings() { + @Nonnull + @Override + public Boolean enumsAsNumbers() { + return false; + } + } + ); + + assertEquals( + Type.Code.STRING, + enumStrings.getCode(), + "STRING should be type of string enums" + ); + } + + @Test public void testSingularFieldWrap() { + var singularField = ModelMetadata.FieldPointer.fieldAtName( + PersonRecord.Person.getDescriptor(), + "name" + ); + + assertResolvedType(singularField, Type.Code.STRING, SpannerOptions.SpannerType.STRING, false); + assertResolvedType(singularField, Type.Code.NUMERIC, SpannerOptions.SpannerType.NUMERIC, false); + assertResolvedType(singularField, Type.Code.INT64, SpannerOptions.SpannerType.INT64, false); + assertResolvedType(singularField, Type.Code.FLOAT64, SpannerOptions.SpannerType.FLOAT64, false); + assertResolvedType(singularField, Type.Code.BYTES, SpannerOptions.SpannerType.BYTES, false); + assertResolvedType(singularField, Type.Code.BOOL, SpannerOptions.SpannerType.BOOL, false); + assertResolvedType(singularField, Type.Code.DATE, SpannerOptions.SpannerType.DATE, false); + assertResolvedType(singularField, Type.Code.TIMESTAMP, SpannerOptions.SpannerType.TIMESTAMP, false); + } + + @Test public void testFailStructTypeAsColumn() { + var singularField = ModelMetadata.FieldPointer.fieldAtName( + PersonRecord.Person.getDescriptor(), + "name" + ); + + assertThrows(IllegalArgumentException.class, () -> + assertResolvedType( + singularField, + Type.Code.STRUCT, + SpannerOptions.SpannerType.UNRECOGNIZED, + false + ) + ); + } + + @Test public void testMaybeWrapArray() { + var repeatedField = ModelMetadata.FieldPointer.fieldAtName( + PersonRecord.TypeBuffet.getDescriptor(), + "labels" + ); + + assertResolvedType( + repeatedField, + Type.Code.STRING, + SpannerOptions.SpannerType.STRING, + true + ); + } + + @Test public void testSchemaRequiresTableName() { + assertThrows(IllegalArgumentException.class, () -> resolveTableName( + PersonRecord.EnrollEvent.getDescriptor() + )); + } + + @Test public void testRestrictedKeyTypes() { + // strings can be keys + assertDoesNotThrow(() -> resolveKeyType(pluck( + PersonRecord.TypeBuffet.getDefaultInstance(), + "string_field" + ).getField())); + + // any 64-bit unsigned integer can be a key + assertDoesNotThrow(() -> resolveKeyType(pluck( + PersonRecord.TypeBuffet.getDefaultInstance(), + "uint_double" + ).getField())); + assertDoesNotThrow(() -> resolveKeyType(pluck( + PersonRecord.TypeBuffet.getDefaultInstance(), + "fixed_double" + ).getField())); + + // no other type can be a key + assertThrows(IllegalStateException.class, () -> resolveKeyType(pluck( + PersonRecord.TypeBuffet.getDefaultInstance(), + "int_normal" + ).getField())); + assertThrows(IllegalStateException.class, () -> resolveKeyType(pluck( + PersonRecord.TypeBuffet.getDefaultInstance(), + "uint_normal" + ).getField())); + assertThrows(IllegalStateException.class, () -> resolveKeyType(pluck( + PersonRecord.TypeBuffet.getDefaultInstance(), + "sint_normal" + ).getField())); + assertThrows(IllegalStateException.class, () -> resolveKeyType(pluck( + PersonRecord.TypeBuffet.getDefaultInstance(), + "sint_double" + ).getField())); + assertThrows(IllegalStateException.class, () -> resolveKeyType(pluck( + PersonRecord.TypeBuffet.getDefaultInstance(), + "fixed_normal" + ).getField())); + assertThrows(IllegalStateException.class, () -> resolveKeyType(pluck( + PersonRecord.TypeBuffet.getDefaultInstance(), + "sfixed_normal" + ).getField())); + assertThrows(IllegalStateException.class, () -> resolveKeyType(pluck( + PersonRecord.TypeBuffet.getDefaultInstance(), + "sfixed_double" + ).getField())); + assertThrows(IllegalStateException.class, () -> resolveKeyType(pluck( + PersonRecord.TypeBuffet.getDefaultInstance(), + "bool_field" + ).getField())); + assertThrows(IllegalStateException.class, () -> resolveKeyType(pluck( + PersonRecord.TypeBuffet.getDefaultInstance(), + "bytes_field" + ).getField())); + assertThrows(IllegalStateException.class, () -> resolveKeyType(pluck( + PersonRecord.TypeBuffet.getDefaultInstance(), + "float_field" + ).getField())); + assertThrows(IllegalStateException.class, () -> resolveKeyType(pluck( + PersonRecord.TypeBuffet.getDefaultInstance(), + "double_field" + ).getField())); + assertThrows(IllegalStateException.class, () -> resolveKeyType(pluck( + PersonRecord.TypeBuffet.getDefaultInstance(), + "enum_field" + ).getField())); + + // repeated types cannot be keys, even if they are typed eligibly otherwise + assertThrows(IllegalStateException.class, () -> resolveKeyType(pluck( + PersonRecord.TypeBuffet.getDefaultInstance(), + "labels" + ).getField())); + } + + @Test public void testColumnNamingBehavior() { + var singularField = ModelMetadata.FieldPointer.fieldAtName( + PersonRecord.TypeBuffet.getDescriptor(), + "double_field" + ); + + // any name specified in the `SpannerFieldOptions` should prevail + assertEquals( + "SomeOtherName", + resolveColumnName( + singularField, + Optional.of(SpannerFieldOptions.newBuilder() + .setColumn("SomeOtherName") + .build()), + Optional.of(TableFieldOptions.newBuilder() + .setName("NotThisName") + .build()), + SpannerDriverSettings.DEFAULTS + ), + "`SpannerFieldOptions` should always prevail" + ); + assertEquals( + "SomeOtherName", + resolveColumnName( + singularField, + Optional.of(SpannerFieldOptions.newBuilder() + .setColumn("SomeOtherName") + .build()), + Optional.empty(), + SpannerDriverSettings.DEFAULTS + ), + "`SpannerFieldOptions` should always prevail" + ); + + // any name specified in the `TableFieldOptions` should prevail over the calculated name + assertEquals( + "GenericTableFieldName", + resolveColumnName( + singularField, + Optional.empty(), + Optional.of(TableFieldOptions.newBuilder() + .setName("GenericTableFieldName") + .build()), + SpannerDriverSettings.DEFAULTS + ), + "`TableFieldOptions` should always prevail over calculated defaults" + ); + + // "preserve field names" should prevail over any calculated names + assertEquals( + "double_field", + resolveColumnName( + singularField, + Optional.empty(), + Optional.empty(), + new SpannerDriverSettings() { + @Nonnull + @Override + public Boolean preserveFieldNames() { + return true; + } + } + ), + "setting `preserveFieldNames` should prevail over any calculated names" + ); + + // calculated names should use capitalized JSON names by default (i.e. "Spanner-style names") + assertEquals( + "DoubleField", + resolveColumnName( + singularField, + Optional.empty(), + Optional.empty(), + SpannerDriverSettings.DEFAULTS + ), + "calculated names should use capitalized JSON names by default (i.e. 'Spanner-style names')" + ); + + // calculated names should be identical to ProtoJSON fields when capitalized naming is turned off + assertEquals( + "doubleField", + resolveColumnName( + singularField, + Optional.empty(), + Optional.empty(), + new SpannerDriverSettings() { + @Nonnull + @Override + public Boolean defaultCapitalizedNames() { + return false; + } + } + ), + "turning off `defaultCapitalizedNames` should result in standard ProtoJSON field naming" + ); + + // naked defaults should result in a completely default field name + assertEquals( + "DoubleField", + resolveColumnName( + singularField.getField() + ), + "completely default field name should be expected form" + ); + } +} diff --git a/javatests/gust/backend/model/BUILD.bazel b/javatests/gust/backend/model/BUILD.bazel index be7b14dbe..6d81c20f5 100644 --- a/javatests/gust/backend/model/BUILD.bazel +++ b/javatests/gust/backend/model/BUILD.bazel @@ -35,6 +35,10 @@ load( model( name = "person", srcs = ["person.proto"], + deps = [ + "@proto_common//:type_date", + "@com_google_protobuf//:timestamp_proto", + ], ) _COMMON_DEPS = [ @@ -116,6 +120,8 @@ java_library( javaproto(":person"), maven("org.junit.jupiter:junit-jupiter-api"), maven("com.google.guava:guava"), + maven("com.google.truth:truth"), + maven("com.google.truth.extensions:truth-proto-extension"), ] + _COMMON_DEPS, ) diff --git a/javatests/gust/backend/model/GenericPersistenceDriverTest.java b/javatests/gust/backend/model/GenericPersistenceDriverTest.java index 0d0d67954..889fafb84 100644 --- a/javatests/gust/backend/model/GenericPersistenceDriverTest.java +++ b/javatests/gust/backend/model/GenericPersistenceDriverTest.java @@ -30,6 +30,7 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; import static java.lang.String.format; import static org.junit.jupiter.api.Assertions.*; @@ -37,7 +38,7 @@ /** Abstract test factory, which is responsible for testing/enforcing {@link PersistenceDriver} surfaces. */ -@SuppressWarnings({"WeakerAccess", "DuplicatedCode", "CodeBlock2Expr", "unchecked"}) +@SuppressWarnings({"WeakerAccess", "DuplicatedCode", "CodeBlock2Expr", "unchecked", "rawtypes"}) public abstract class GenericPersistenceDriverTest { /** Describes data keys touched in this test case. */ protected final HashSet touchedKeys = new HashSet<>(); @@ -51,7 +52,27 @@ public abstract class GenericPersistenceDriverTest driverTests() { final String subcase = this.getClass().getSimpleName(); - List tests = Arrays.asList( + List tests = this.supportedDriverTests(); + Set unsupported = this.unsupportedDriverTests() + .orElse(Collections.emptyList()) + .stream() + .map((name) -> String.format("%s: `%s`", subcase, name)) + .collect(Collectors.toUnmodifiableSet()); + + tests.addAll(subclassTests().orElse(Collections.emptyList())); + + return tests.stream().filter((test) -> + // mark unsupported tests with ignore annotations + unsupported.isEmpty() || !unsupported.contains(test.getDisplayName()) + ).collect(Collectors.toUnmodifiableList()); + } + + /** + * @return Set of tests that are currently expected to be supported by this driver. + */ + protected @Nonnull List supportedDriverTests() { + final String subcase = this.getClass().getSimpleName(); + return Arrays.asList( dynamicTest(format("%s: `acquireDriver`", subcase), this::acquireDriver), dynamicTest(format("%s: `testDriverCodec`", subcase), this::testDriverCodec), dynamicTest(format("%s: `testDriverExecutor`", subcase), this::testDriverExecutor), @@ -63,15 +84,22 @@ protected final Iterable driverTests() { dynamicTest(format("%s: `createEntityThenUpdate`", subcase), this::createEntityThenUpdate), dynamicTest(format("%s: `createUpdateWithInvalidOptions`", subcase), this::createUpdateWithInvalidOptions), dynamicTest(format("%s: `createEntityThenDelete`", subcase), this::createEntityThenDelete), - dynamicTest(format("%s: `createEntityThenDeleteByRecord`", subcase), this::createEntityThenDeleteByRecord)//, -// dynamicTest(format("%s: `storeEntityUpdateNotFound`", subcase), this::storeEntityUpdateNotFound), -// dynamicTest(format("%s: `storeEntityCollission`", subcase), this::storeEntityCollission), + dynamicTest(format("%s: `createEntityThenDeleteByRecord`", subcase), this::createEntityThenDeleteByRecord), + dynamicTest(format("%s: `storeEntityUpdateNotFound`", subcase), this::storeEntityUpdateNotFound), + dynamicTest(format("%s: `storeEntityCollission`", subcase), this::storeEntityCollission) ); + } - tests.addAll(subclassTests().orElse(Collections.emptyList())); - return tests; + /** + * @return Set of tests that are not currently supported by this driver. + */ + protected @Nonnull Optional> unsupportedDriverTests() { + return Optional.empty(); } + /** + * @return Additional dynamic tests to add from the specific driver test implementation. + */ protected @Nonnull Optional> subclassTests() { return Optional.empty(); } @@ -246,6 +274,9 @@ protected void storeAndFetchEntity() throws TimeoutException, ExecutionException assertTrue(refetched2.isPresent(), "should find record we just stored"); assertEquals(keySpliced.toString(), refetched2.get().toString(), "fetched person record should match identically, but with key"); + + var id = ModelMetadata.id(refetched2.get()); + assertTrue(id.isPresent(), "ID should be decoded on fetched models"); } /** Create a simple entity, store it, and then fetch it, but with a field mask. */ @@ -277,6 +308,7 @@ protected void storeAndFetchEntityMasked() throws TimeoutException, ExecutionExc @Override public @Nonnull Optional fieldMask() { return Optional.of(FieldMask.newBuilder() + .addPaths("key.id") .addPaths("name") .addPaths("contact_info.email_address") .build()); @@ -298,6 +330,9 @@ protected void storeAndFetchEntityMasked() throws TimeoutException, ExecutionExc assertEquals("", fetchedPerson.getContactInfo().getPhoneE164(), "phone number should be empty"); assertFalse(fetchedPerson.getContactInfo().hasField(ContactInfo.getDescriptor().findFieldByName("phone_e164")), "phone should not be present on masked person"); + + var id = ModelMetadata.id(fetchedPerson); + assertTrue(id.isPresent(), "ID should be decoded on fetched models"); } /** Create a simple entity, store it, and then try to store it again. */ @@ -410,6 +445,9 @@ protected void storeEntityUpdate() throws TimeoutException, ExecutionException, assertNotNull(key2, "should get a key back from a persist operation"); assertFalse(op2.isCancelled(), "write future should not present as cancelled after completing"); touchedKeys.add(key2.get()); + + var id = ModelMetadata.id(record2); + assertTrue(id.isPresent(), "ID should be decoded on fetched models"); } /** Create a simple entity, store it, and then try to update an entity that does not exist. */ diff --git a/javatests/gust/backend/model/person.proto b/javatests/gust/backend/model/person.proto index 72fafeaea..454bd3da3 100644 --- a/javatests/gust/backend/model/person.proto +++ b/javatests/gust/backend/model/person.proto @@ -22,19 +22,31 @@ option java_outer_classname = "PersonRecord"; import "gust/core/datamodel.proto"; +import "google/protobuf/timestamp.proto"; +import "google/type/date.proto"; + // Proto for dependency testing. message Person { option (core.role) = OBJECT; + option (core.table).name = "People"; // Unique record ID. - PersonKey key = 1 [(core.field).type = KEY]; + PersonKey key = 1 [ + (core.field).type = KEY + ]; // Salutation name. - string name = 2; + string name = 2 [ + (core.column).name = "Name", + (core.column).size = 1024 + ]; // Person's contact info. - ContactInfo contact_info = 3; + ContactInfo contact_info = 3 [ + (core.column).name = "ContactInfo", + (core.column).sptype = JSON + ]; } @@ -43,9 +55,14 @@ message PersonKey { option (core.role) = OBJECT_KEY; option (core.db).path = "people"; option (core.db).mode = COLLECTION; + option (core.table).name = "People"; // Unique record ID. - string id = 1 [(core.field).type = ID]; + string id = 1 [ + (core.field).type = ID, + (core.spanner).column = "ID", + (core.spanner).size = 240 + ]; } @@ -64,13 +81,13 @@ message ContactInfo { option (core.db).mode = NESTED; // Person's email address. - string email_address = 1; + string email_address = 1 [(core.column).name = "EmailAddress"]; // Person's phone number in E164 format. - string phone_e164 = 2; + string phone_e164 = 2 [(core.spanner).column = "PhoneE164"]; // Person's address info. - PersonAddress address = 3; + PersonAddress address = 3 [(core.column).ignore = true]; } @@ -78,3 +95,58 @@ message ContactInfo { message EnrollEvent { option (core.role) = EVENT; } + + +// Sets up an example of every type. +message TypeBuffet { + option (core.table).name = "TypeExamples"; + + // Sample type key. + message SampleKey { + option (core.role) = OBJECT_KEY; + + // Unique record ID. + uint64 id = 1 [ + (core.field).type = ID, + (core.spanner).size = 240, + (core.spanner).type = INT64, + (core.spanner).column = "ID" + ]; + } + + enum SampleEnumeration { + DEFAULT_ENUM = 0; + ANOTHER_ENUM = 1; + } + + // Unique record ID. + SampleKey key = 1 [ + (core.field).type = KEY + ]; + + int32 int_normal = 2; + int64 int_double = 3; + uint32 uint_normal = 4; + uint64 uint_double = 5; + sint32 sint_normal = 6; + sint64 sint_double = 7; + fixed32 fixed_normal = 8; + fixed64 fixed_double = 9; + sfixed32 sfixed_normal = 10; + sfixed64 sfixed_double = 11; + string string_field = 12; + bool bool_field = 13; + bytes bytes_field = 14; + float float_field = 15; + double double_field = 16; + SampleEnumeration enum_field = 17; + repeated string labels = 18 [ + (core.spanner).size = 240 + ]; + string spanner_numeric_field = 19 [ + (core.spanner).type = NUMERIC + ]; + + google.protobuf.Timestamp timestamp = 20; + google.type.Date date = 21; +} diff --git a/javatests/logback.xml b/javatests/logback.xml index 592fdc4b7..cbad0790e 100644 --- a/javatests/logback.xml +++ b/javatests/logback.xml @@ -9,8 +9,31 @@ - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/maven_install.json b/maven_install.json index 3e3022c57..9602d9636 100644 --- a/maven_install.json +++ b/maven_install.json @@ -1,22 +1,24 @@ { "dependency_tree": { - "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": 755297460, + "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": -1891697825, "conflict_resolution": { - "com.google.guava:guava:28.0-jre": "com.google.guava:guava:30.0-android", - "io.netty:netty-buffer:4.1.51.Final": "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-codec-http2:4.1.51.Final": "io.netty:netty-codec-http2:4.1.53.Final", - "io.netty:netty-codec-http:4.1.51.Final": "io.netty:netty-codec-http:4.1.53.Final", - "io.netty:netty-codec-socks:4.1.51.Final": "io.netty:netty-codec-socks:4.1.53.Final", - "io.netty:netty-codec:4.1.51.Final": "io.netty:netty-codec:4.1.53.Final", - "io.netty:netty-common:4.1.51.Final": "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-handler-proxy:4.1.51.Final": "io.netty:netty-handler-proxy:4.1.53.Final", - "io.netty:netty-handler:4.1.51.Final": "io.netty:netty-handler:4.1.53.Final", - "io.netty:netty-resolver:4.1.51.Final": "io.netty:netty-resolver:4.1.53.Final", - "io.netty:netty-transport:4.1.51.Final": "io.netty:netty-transport:4.1.53.Final", + "com.google.api.grpc:proto-google-common-protos:2.3.2": "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.api:api-common:1.10.4": "com.google.api:api-common:2.0.2", + "com.google.auth:google-auth-library-credentials:0.25.2": "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.auto.value:auto-value-annotations:1.8.1": "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.cloud:google-cloud-core-grpc:1.95.4": "com.google.cloud:google-cloud-core-grpc:2.1.6", + "com.google.cloud:google-cloud-core:1.95.4": "com.google.cloud:google-cloud-core:2.1.6", + "com.google.code.gson:gson:2.8.6": "com.google.code.gson:gson:2.8.8", + "com.google.errorprone:error_prone_annotations:2.7.1": "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.guava:guava:28.0-jre": "com.google.guava:guava:30.1.1-android", "javax.validation:validation-api:2.0.0.Final": "javax.validation:validation-api:2.0.1.Final", - "org.apiguardian:apiguardian-api:1.0.0": "org.apiguardian:apiguardian-api:1.1.0", + "org.apiguardian:apiguardian-api:1.0.0": "org.apiguardian:apiguardian-api:1.1.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.19": "org.codehaus.mojo:animal-sniffer-annotations:1.20", "org.junit.jupiter:junit-jupiter-api:5.6.0": "org.junit.jupiter:junit-jupiter-api:5.7.0", - "org.junit.platform:junit-platform-commons:1.6.0": "org.junit.platform:junit-platform-commons:1.7.0", + "org.junit.platform:junit-platform-commons:1.6.0": "org.junit.platform:junit-platform-commons:1.8.0-M1", + "org.junit.platform:junit-platform-engine:1.6.0": "org.junit.platform:junit-platform-engine:1.8.0-M1", + "org.junit.platform:junit-platform-launcher:1.6.0": "org.junit.platform:junit-platform-launcher:1.8.0-M1", + "org.junit.platform:junit-platform-suite-api:1.6.0": "org.junit.platform:junit-platform-suite-api:1.8.0-M1", "org.opentest4j:opentest4j:1.1.1": "org.opentest4j:opentest4j:1.2.0" }, "dependencies": [ @@ -24,11 +26,11 @@ "coord": "ch.qos.logback:logback-classic:1.2.3", "dependencies": [ "ch.qos.logback:logback-core:1.2.3", - "org.slf4j:slf4j-api:1.7.30" + "org.slf4j:slf4j-api:1.7.31" ], "directDependencies": [ "ch.qos.logback:logback-core:1.2.3", - "org.slf4j:slf4j-api:1.7.30" + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", @@ -47,12 +49,12 @@ { "coord": "ch.qos.logback:logback-classic:jar:sources:1.2.3", "dependencies": [ - "org.slf4j:slf4j-api:jar:sources:1.7.30", + "org.slf4j:slf4j-api:jar:sources:1.7.31", "ch.qos.logback:logback-core:jar:sources:1.2.3" ], "directDependencies": [ "ch.qos.logback:logback-core:jar:sources:1.2.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30" + "org.slf4j:slf4j-api:jar:sources:1.7.31" ], "exclusions": [ "com.google.template:soy", @@ -105,254 +107,246 @@ "url": "https://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3-sources.jar" }, { - "coord": "com.fasterxml.jackson.core:jackson-annotations:2.11.2", + "coord": "com.fasterxml.jackson.core:jackson-annotations:2.12.2", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.11.2/jackson-annotations-2.11.2.jar", + "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.12.2/jackson-annotations-2.12.2.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.11.2/jackson-annotations-2.11.2.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/core/jackson-annotations/2.11.2/jackson-annotations-2.11.2.jar", - "https://jcenter.bintray.com/com/fasterxml/jackson/core/jackson-annotations/2.11.2/jackson-annotations-2.11.2.jar", - "https://maven.google.com/com/fasterxml/jackson/core/jackson-annotations/2.11.2/jackson-annotations-2.11.2.jar" + "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.12.2/jackson-annotations-2.12.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/core/jackson-annotations/2.12.2/jackson-annotations-2.12.2.jar", + "https://jcenter.bintray.com/com/fasterxml/jackson/core/jackson-annotations/2.12.2/jackson-annotations-2.12.2.jar", + "https://maven.google.com/com/fasterxml/jackson/core/jackson-annotations/2.12.2/jackson-annotations-2.12.2.jar" ], - "sha256": "90d602d1955df509b1569618cff869994caf9483cb82a3ccb39782a5cda54126", - "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.11.2/jackson-annotations-2.11.2.jar" + "sha256": "558561786c071af202e849b6ae3d39c87ed417ecc83d45e398c12eb3bffa557b", + "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.12.2/jackson-annotations-2.12.2.jar" }, { - "coord": "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", + "coord": "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.11.2/jackson-annotations-2.11.2-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.12.2/jackson-annotations-2.12.2-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.11.2/jackson-annotations-2.11.2-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/core/jackson-annotations/2.11.2/jackson-annotations-2.11.2-sources.jar", - "https://jcenter.bintray.com/com/fasterxml/jackson/core/jackson-annotations/2.11.2/jackson-annotations-2.11.2-sources.jar", - "https://maven.google.com/com/fasterxml/jackson/core/jackson-annotations/2.11.2/jackson-annotations-2.11.2-sources.jar" + "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.12.2/jackson-annotations-2.12.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/core/jackson-annotations/2.12.2/jackson-annotations-2.12.2-sources.jar", + "https://jcenter.bintray.com/com/fasterxml/jackson/core/jackson-annotations/2.12.2/jackson-annotations-2.12.2-sources.jar", + "https://maven.google.com/com/fasterxml/jackson/core/jackson-annotations/2.12.2/jackson-annotations-2.12.2-sources.jar" ], - "sha256": "4390a732d8097237eae8716546b8b40edd429a13b4681bb46f578f9b4e8cb6c8", - "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.11.2/jackson-annotations-2.11.2-sources.jar" + "sha256": "8309e94ec38371a1ef9b69b030d3beb043fc27b4b0352eb2fa891029bd938e40", + "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.12.2/jackson-annotations-2.12.2-sources.jar" }, { - "coord": "com.fasterxml.jackson.core:jackson-core:2.11.3", + "coord": "com.fasterxml.jackson.core:jackson-core:2.12.3", "dependencies": [], "directDependencies": [], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.11.3/jackson-core-2.11.3.jar", + "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.12.3/jackson-core-2.12.3.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.11.3/jackson-core-2.11.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/core/jackson-core/2.11.3/jackson-core-2.11.3.jar", - "https://jcenter.bintray.com/com/fasterxml/jackson/core/jackson-core/2.11.3/jackson-core-2.11.3.jar", - "https://maven.google.com/com/fasterxml/jackson/core/jackson-core/2.11.3/jackson-core-2.11.3.jar" + "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.12.3/jackson-core-2.12.3.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/core/jackson-core/2.12.3/jackson-core-2.12.3.jar", + "https://jcenter.bintray.com/com/fasterxml/jackson/core/jackson-core/2.12.3/jackson-core-2.12.3.jar", + "https://maven.google.com/com/fasterxml/jackson/core/jackson-core/2.12.3/jackson-core-2.12.3.jar" ], - "sha256": "78cd0a6b936232e06dd3e38da8a0345348a09cd1ff9c4d844c6ee72c75cfc402", - "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.11.3/jackson-core-2.11.3.jar" + "sha256": "baef34fbce041d54f3af3ff4fc917ed8b43ed2a6fa30e0a6abfd9a2b2c3f71e0", + "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.12.3/jackson-core-2.12.3.jar" }, { - "coord": "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", + "coord": "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.11.3/jackson-core-2.11.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.12.3/jackson-core-2.12.3-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.11.3/jackson-core-2.11.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/core/jackson-core/2.11.3/jackson-core-2.11.3-sources.jar", - "https://jcenter.bintray.com/com/fasterxml/jackson/core/jackson-core/2.11.3/jackson-core-2.11.3-sources.jar", - "https://maven.google.com/com/fasterxml/jackson/core/jackson-core/2.11.3/jackson-core-2.11.3-sources.jar" + "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.12.3/jackson-core-2.12.3-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/core/jackson-core/2.12.3/jackson-core-2.12.3-sources.jar", + "https://jcenter.bintray.com/com/fasterxml/jackson/core/jackson-core/2.12.3/jackson-core-2.12.3-sources.jar", + "https://maven.google.com/com/fasterxml/jackson/core/jackson-core/2.12.3/jackson-core-2.12.3-sources.jar" ], - "sha256": "16f99b9d65d6e1c2e922b2badcde1e3c01b1c888414e3712299cfcb1041b902e", - "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.11.3/jackson-core-2.11.3-sources.jar" + "sha256": "05eea74b39c545f495fcf8fc683276f8c1c2642dc628d2c72074a1275a47e1c0", + "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.12.3/jackson-core-2.12.3-sources.jar" }, { - "coord": "com.fasterxml.jackson.core:jackson-databind:2.11.2", + "coord": "com.fasterxml.jackson.core:jackson-databind:2.12.2", "dependencies": [ - "com.fasterxml.jackson.core:jackson-core:2.11.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2" + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "com.fasterxml.jackson.core:jackson-core:2.12.3" ], "directDependencies": [ - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "com.fasterxml.jackson.core:jackson-core:2.12.3" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.11.2/jackson-databind-2.11.2.jar", + "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.12.2/jackson-databind-2.12.2.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.11.2/jackson-databind-2.11.2.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/core/jackson-databind/2.11.2/jackson-databind-2.11.2.jar", - "https://jcenter.bintray.com/com/fasterxml/jackson/core/jackson-databind/2.11.2/jackson-databind-2.11.2.jar", - "https://maven.google.com/com/fasterxml/jackson/core/jackson-databind/2.11.2/jackson-databind-2.11.2.jar" + "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.12.2/jackson-databind-2.12.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/core/jackson-databind/2.12.2/jackson-databind-2.12.2.jar", + "https://jcenter.bintray.com/com/fasterxml/jackson/core/jackson-databind/2.12.2/jackson-databind-2.12.2.jar", + "https://maven.google.com/com/fasterxml/jackson/core/jackson-databind/2.12.2/jackson-databind-2.12.2.jar" ], - "sha256": "cb890b4aad8ed21a7b57e3c8f7924dbdca1aeff9ddd27cb0ff37243037ae1342", - "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.11.2/jackson-databind-2.11.2.jar" + "sha256": "c4002f861d8d33f3202bf8effabb53acc320c5276cc50c1bfaae73c36ce8db32", + "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.12.2/jackson-databind-2.12.2.jar" }, { - "coord": "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", + "coord": "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.2", "dependencies": [ - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3" + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3" ], "directDependencies": [ - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3" + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.11.2/jackson-databind-2.11.2-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.12.2/jackson-databind-2.12.2-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.11.2/jackson-databind-2.11.2-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/core/jackson-databind/2.11.2/jackson-databind-2.11.2-sources.jar", - "https://jcenter.bintray.com/com/fasterxml/jackson/core/jackson-databind/2.11.2/jackson-databind-2.11.2-sources.jar", - "https://maven.google.com/com/fasterxml/jackson/core/jackson-databind/2.11.2/jackson-databind-2.11.2-sources.jar" + "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.12.2/jackson-databind-2.12.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/core/jackson-databind/2.12.2/jackson-databind-2.12.2-sources.jar", + "https://jcenter.bintray.com/com/fasterxml/jackson/core/jackson-databind/2.12.2/jackson-databind-2.12.2-sources.jar", + "https://maven.google.com/com/fasterxml/jackson/core/jackson-databind/2.12.2/jackson-databind-2.12.2-sources.jar" ], - "sha256": "be4fb5df7a43cbb51334ee433521a35e27acfc816c77005c628021368a08c252", - "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.11.2/jackson-databind-2.11.2-sources.jar" + "sha256": "b588848605f5c49c804360fc049a0ad048ec7494cb2b9b6e95ede38e0132be7e", + "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.12.2/jackson-databind-2.12.2-sources.jar" }, { - "coord": "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", + "coord": "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.2", "dependencies": [ - "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "com.fasterxml.jackson.core:jackson-core:2.11.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2" + "com.fasterxml.jackson.core:jackson-databind:2.12.2", + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "com.fasterxml.jackson.core:jackson-core:2.12.3" ], "directDependencies": [ - "com.fasterxml.jackson.core:jackson-core:2.11.3", - "com.fasterxml.jackson.core:jackson-databind:2.11.2" + "com.fasterxml.jackson.core:jackson-core:2.12.3", + "com.fasterxml.jackson.core:jackson-databind:2.12.2" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.11.2/jackson-datatype-jdk8-2.11.2.jar", + "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.12.2/jackson-datatype-jdk8-2.12.2.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.11.2/jackson-datatype-jdk8-2.11.2.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.11.2/jackson-datatype-jdk8-2.11.2.jar", - "https://jcenter.bintray.com/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.11.2/jackson-datatype-jdk8-2.11.2.jar", - "https://maven.google.com/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.11.2/jackson-datatype-jdk8-2.11.2.jar" + "https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.12.2/jackson-datatype-jdk8-2.12.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.12.2/jackson-datatype-jdk8-2.12.2.jar", + "https://jcenter.bintray.com/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.12.2/jackson-datatype-jdk8-2.12.2.jar", + "https://maven.google.com/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.12.2/jackson-datatype-jdk8-2.12.2.jar" ], - "sha256": "fa585ff4aed2b250538dd42d53d263fc96c9b1c720e836214e443e4cf28af61f", - "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.11.2/jackson-datatype-jdk8-2.11.2.jar" + "sha256": "ebd595dd8913c3996096bf03a93873baedd8862f0596db33cbe6d0955e334995", + "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.12.2/jackson-datatype-jdk8-2.12.2.jar" }, { - "coord": "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", + "coord": "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.12.2", "dependencies": [ - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3" + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3", + "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.2" ], "directDependencies": [ - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2" + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3", + "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.2" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.11.2/jackson-datatype-jdk8-2.11.2-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.12.2/jackson-datatype-jdk8-2.12.2-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.11.2/jackson-datatype-jdk8-2.11.2-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.11.2/jackson-datatype-jdk8-2.11.2-sources.jar", - "https://jcenter.bintray.com/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.11.2/jackson-datatype-jdk8-2.11.2-sources.jar", - "https://maven.google.com/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.11.2/jackson-datatype-jdk8-2.11.2-sources.jar" + "https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.12.2/jackson-datatype-jdk8-2.12.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.12.2/jackson-datatype-jdk8-2.12.2-sources.jar", + "https://jcenter.bintray.com/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.12.2/jackson-datatype-jdk8-2.12.2-sources.jar", + "https://maven.google.com/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.12.2/jackson-datatype-jdk8-2.12.2-sources.jar" ], - "sha256": "39181c663752c136c965846d8cab142d5ba87b7c00e6af6f26e175e5f562eab2", - "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.11.2/jackson-datatype-jdk8-2.11.2-sources.jar" + "sha256": "f64bfe80ca39d13132cc73f871e720787abe254a82719716cca42929d76a9472", + "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.12.2/jackson-datatype-jdk8-2.12.2-sources.jar" }, { - "coord": "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", + "coord": "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.2", "dependencies": [ - "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "com.fasterxml.jackson.core:jackson-core:2.11.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2" + "com.fasterxml.jackson.core:jackson-databind:2.12.2", + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "com.fasterxml.jackson.core:jackson-core:2.12.3" ], "directDependencies": [ - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "com.fasterxml.jackson.core:jackson-core:2.11.3", - "com.fasterxml.jackson.core:jackson-databind:2.11.2" + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "com.fasterxml.jackson.core:jackson-core:2.12.3", + "com.fasterxml.jackson.core:jackson-databind:2.12.2" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.11.2/jackson-datatype-jsr310-2.11.2.jar", + "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.12.2/jackson-datatype-jsr310-2.12.2.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.11.2/jackson-datatype-jsr310-2.11.2.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.11.2/jackson-datatype-jsr310-2.11.2.jar", - "https://jcenter.bintray.com/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.11.2/jackson-datatype-jsr310-2.11.2.jar", - "https://maven.google.com/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.11.2/jackson-datatype-jsr310-2.11.2.jar" + "https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.12.2/jackson-datatype-jsr310-2.12.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.12.2/jackson-datatype-jsr310-2.12.2.jar", + "https://jcenter.bintray.com/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.12.2/jackson-datatype-jsr310-2.12.2.jar", + "https://maven.google.com/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.12.2/jackson-datatype-jsr310-2.12.2.jar" ], - "sha256": "c8f7155c405cf1c521fb7f1cde610a0c488aad794b3c4ca7637a199dbc40850f", - "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.11.2/jackson-datatype-jsr310-2.11.2.jar" + "sha256": "914dd4e9a859d0317ee15a223fe5064c1dbadc5a09621289fc37c13d8deb06cb", + "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.12.2/jackson-datatype-jsr310-2.12.2.jar" }, { - "coord": "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", + "coord": "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.12.2", "dependencies": [ - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3" + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3", + "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.2" ], "directDependencies": [ - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2" + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3", + "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.2" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.11.2/jackson-datatype-jsr310-2.11.2-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.12.2/jackson-datatype-jsr310-2.12.2-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.11.2/jackson-datatype-jsr310-2.11.2-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.11.2/jackson-datatype-jsr310-2.11.2-sources.jar", - "https://jcenter.bintray.com/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.11.2/jackson-datatype-jsr310-2.11.2-sources.jar", - "https://maven.google.com/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.11.2/jackson-datatype-jsr310-2.11.2-sources.jar" + "https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.12.2/jackson-datatype-jsr310-2.12.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.12.2/jackson-datatype-jsr310-2.12.2-sources.jar", + "https://jcenter.bintray.com/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.12.2/jackson-datatype-jsr310-2.12.2-sources.jar", + "https://maven.google.com/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.12.2/jackson-datatype-jsr310-2.12.2-sources.jar" ], - "sha256": "23d4df012facacd9fa10f4823e616b6eba9dfb2ba039666abe86671c6f639bdc", - "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.11.2/jackson-datatype-jsr310-2.11.2-sources.jar" + "sha256": "430ced5ca0d1b82ad8b666f5cfc69a03a95415d4b3e71c03c430f26c203b5733", + "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.12.2/jackson-datatype-jsr310-2.12.2-sources.jar" }, { "coord": "com.github.ajalt:colormath:1.2.0", "dependencies": [ - "org.jetbrains:annotations:13.0", - "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib:1.4.10" ], "directDependencies": [ @@ -375,8 +369,6 @@ { "coord": "com.github.ajalt:colormath:jar:sources:1.2.0", "dependencies": [ - "org.jetbrains:annotations:jar:sources:13.0", - "org.jetbrains.kotlin:kotlin-stdlib-common:jar:sources:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.4.10" ], "directDependencies": [ @@ -400,8 +392,6 @@ "coord": "com.github.ajalt:mordant:1.2.1", "dependencies": [ "com.github.ajalt:colormath:1.2.0", - "org.jetbrains:annotations:13.0", - "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib:1.4.10" ], "directDependencies": [ @@ -426,8 +416,6 @@ "coord": "com.github.ajalt:mordant:jar:sources:1.2.1", "dependencies": [ "com.github.ajalt:colormath:jar:sources:1.2.0", - "org.jetbrains:annotations:jar:sources:13.0", - "org.jetbrains.kotlin:kotlin-stdlib-common:jar:sources:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.4.10" ], "directDependencies": [ @@ -449,75 +437,221 @@ "url": "https://repo1.maven.org/maven2/com/github/ajalt/mordant/1.2.1/mordant-1.2.1-sources.jar" }, { - "coord": "com.github.ben-manes.caffeine:caffeine:2.8.5", + "coord": "com.github.ben-manes.caffeine:caffeine:2.8.8", "dependencies": [ - "org.checkerframework:checker-qual:3.4.1", - "com.google.errorprone:error_prone_annotations:2.4.0" + "com.google.errorprone:error_prone_annotations:2.9.0", + "org.checkerframework:checker-qual:3.13.0" ], "directDependencies": [ - "com.google.errorprone:error_prone_annotations:2.4.0", - "org.checkerframework:checker-qual:3.4.1" + "com.google.errorprone:error_prone_annotations:2.9.0", + "org.checkerframework:checker-qual:3.13.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/github/ben-manes/caffeine/caffeine/2.8.5/caffeine-2.8.5.jar", + "file": "v1/https/repo1.maven.org/maven2/com/github/ben-manes/caffeine/caffeine/2.8.8/caffeine-2.8.8.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/github/ben-manes/caffeine/caffeine/2.8.5/caffeine-2.8.5.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/github/ben-manes/caffeine/caffeine/2.8.5/caffeine-2.8.5.jar", - "https://jcenter.bintray.com/com/github/ben-manes/caffeine/caffeine/2.8.5/caffeine-2.8.5.jar", - "https://maven.google.com/com/github/ben-manes/caffeine/caffeine/2.8.5/caffeine-2.8.5.jar" + "https://repo1.maven.org/maven2/com/github/ben-manes/caffeine/caffeine/2.8.8/caffeine-2.8.8.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/github/ben-manes/caffeine/caffeine/2.8.8/caffeine-2.8.8.jar", + "https://jcenter.bintray.com/com/github/ben-manes/caffeine/caffeine/2.8.8/caffeine-2.8.8.jar", + "https://maven.google.com/com/github/ben-manes/caffeine/caffeine/2.8.8/caffeine-2.8.8.jar" ], - "sha256": "814b15a9bf598e0fa854dd70ba9f6e03a413a97979de0c3f49317295e4352bc8", - "url": "https://repo1.maven.org/maven2/com/github/ben-manes/caffeine/caffeine/2.8.5/caffeine-2.8.5.jar" + "sha256": "7b7b72c6ce3e47e774e29060ceba19e83e8259bd475986e04b4f3272d4a58f73", + "url": "https://repo1.maven.org/maven2/com/github/ben-manes/caffeine/caffeine/2.8.8/caffeine-2.8.8.jar" }, { - "coord": "com.github.ben-manes.caffeine:caffeine:jar:sources:2.8.5", + "coord": "com.github.ben-manes.caffeine:caffeine:jar:sources:2.8.8", "dependencies": [ - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "org.checkerframework:checker-qual:jar:sources:3.4.1" + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "org.checkerframework:checker-qual:jar:sources:3.13.0" ], "directDependencies": [ - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "org.checkerframework:checker-qual:jar:sources:3.4.1" + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "org.checkerframework:checker-qual:jar:sources:3.13.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" + ], + "file": "v1/https/repo1.maven.org/maven2/com/github/ben-manes/caffeine/caffeine/2.8.8/caffeine-2.8.8-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/github/ben-manes/caffeine/caffeine/2.8.8/caffeine-2.8.8-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/github/ben-manes/caffeine/caffeine/2.8.8/caffeine-2.8.8-sources.jar", + "https://jcenter.bintray.com/com/github/ben-manes/caffeine/caffeine/2.8.8/caffeine-2.8.8-sources.jar", + "https://maven.google.com/com/github/ben-manes/caffeine/caffeine/2.8.8/caffeine-2.8.8-sources.jar" + ], + "sha256": "dd2043cea899ab473969c360d65d848eaaaa0ba45629eaba109176976d3e002d", + "url": "https://repo1.maven.org/maven2/com/github/ben-manes/caffeine/caffeine/2.8.8/caffeine-2.8.8-sources.jar" + }, + { + "coord": "com.github.docker-java:docker-java-api:3.2.8", + "dependencies": [ + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "org.slf4j:slf4j-api:1.7.31" + ], + "directDependencies": [ + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "org.slf4j:slf4j-api:1.7.31" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/com/github/docker-java/docker-java-api/3.2.8/docker-java-api-3.2.8.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/github/docker-java/docker-java-api/3.2.8/docker-java-api-3.2.8.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/github/docker-java/docker-java-api/3.2.8/docker-java-api-3.2.8.jar", + "https://jcenter.bintray.com/com/github/docker-java/docker-java-api/3.2.8/docker-java-api-3.2.8.jar", + "https://maven.google.com/com/github/docker-java/docker-java-api/3.2.8/docker-java-api-3.2.8.jar" + ], + "sha256": "5a9f801ea1fc51fa06ab97a9856ceb95c8cbca53a9b359bac46b6d65cc38a3cf", + "url": "https://repo1.maven.org/maven2/com/github/docker-java/docker-java-api/3.2.8/docker-java-api-3.2.8.jar" + }, + { + "coord": "com.github.docker-java:docker-java-api:jar:sources:3.2.8", + "dependencies": [ + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "org.slf4j:slf4j-api:jar:sources:1.7.31" + ], + "directDependencies": [ + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "org.slf4j:slf4j-api:jar:sources:1.7.31" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/com/github/docker-java/docker-java-api/3.2.8/docker-java-api-3.2.8-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/github/docker-java/docker-java-api/3.2.8/docker-java-api-3.2.8-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/github/docker-java/docker-java-api/3.2.8/docker-java-api-3.2.8-sources.jar", + "https://jcenter.bintray.com/com/github/docker-java/docker-java-api/3.2.8/docker-java-api-3.2.8-sources.jar", + "https://maven.google.com/com/github/docker-java/docker-java-api/3.2.8/docker-java-api-3.2.8-sources.jar" + ], + "sha256": "fe1dbf0a1e2392cb5874179c2a72f3857215973b19969f7753a50cdd3acac838", + "url": "https://repo1.maven.org/maven2/com/github/docker-java/docker-java-api/3.2.8/docker-java-api-3.2.8-sources.jar" + }, + { + "coord": "com.github.docker-java:docker-java-transport-zerodep:3.2.8", + "dependencies": [ + "com.github.docker-java:docker-java-transport:3.2.8", + "org.slf4j:slf4j-api:1.7.31", + "net.java.dev.jna:jna:5.8.0" + ], + "directDependencies": [ + "com.github.docker-java:docker-java-transport:3.2.8", + "net.java.dev.jna:jna:5.8.0", + "org.slf4j:slf4j-api:1.7.31" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/com/github/docker-java/docker-java-transport-zerodep/3.2.8/docker-java-transport-zerodep-3.2.8.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/github/docker-java/docker-java-transport-zerodep/3.2.8/docker-java-transport-zerodep-3.2.8.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/github/docker-java/docker-java-transport-zerodep/3.2.8/docker-java-transport-zerodep-3.2.8.jar", + "https://jcenter.bintray.com/com/github/docker-java/docker-java-transport-zerodep/3.2.8/docker-java-transport-zerodep-3.2.8.jar", + "https://maven.google.com/com/github/docker-java/docker-java-transport-zerodep/3.2.8/docker-java-transport-zerodep-3.2.8.jar" + ], + "sha256": "f0ce261803b459e00de8b549d88713822d53ff727aa2fa4125fc44af5c637c5e", + "url": "https://repo1.maven.org/maven2/com/github/docker-java/docker-java-transport-zerodep/3.2.8/docker-java-transport-zerodep-3.2.8.jar" + }, + { + "coord": "com.github.docker-java:docker-java-transport-zerodep:jar:sources:3.2.8", + "dependencies": [ + "com.github.docker-java:docker-java-transport:jar:sources:3.2.8", + "net.java.dev.jna:jna:jar:sources:5.8.0", + "org.slf4j:slf4j-api:jar:sources:1.7.31" + ], + "directDependencies": [ + "com.github.docker-java:docker-java-transport:jar:sources:3.2.8", + "net.java.dev.jna:jna:jar:sources:5.8.0", + "org.slf4j:slf4j-api:jar:sources:1.7.31" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/com/github/docker-java/docker-java-transport-zerodep/3.2.8/docker-java-transport-zerodep-3.2.8-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/github/docker-java/docker-java-transport-zerodep/3.2.8/docker-java-transport-zerodep-3.2.8-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/github/docker-java/docker-java-transport-zerodep/3.2.8/docker-java-transport-zerodep-3.2.8-sources.jar", + "https://jcenter.bintray.com/com/github/docker-java/docker-java-transport-zerodep/3.2.8/docker-java-transport-zerodep-3.2.8-sources.jar", + "https://maven.google.com/com/github/docker-java/docker-java-transport-zerodep/3.2.8/docker-java-transport-zerodep-3.2.8-sources.jar" + ], + "sha256": "3ef44eb76332a015781564ec20ae15a917d2be292c14e287ba87d5dddd443077", + "url": "https://repo1.maven.org/maven2/com/github/docker-java/docker-java-transport-zerodep/3.2.8/docker-java-transport-zerodep-3.2.8-sources.jar" + }, + { + "coord": "com.github.docker-java:docker-java-transport:3.2.8", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/com/github/docker-java/docker-java-transport/3.2.8/docker-java-transport-3.2.8.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/github/docker-java/docker-java-transport/3.2.8/docker-java-transport-3.2.8.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/github/docker-java/docker-java-transport/3.2.8/docker-java-transport-3.2.8.jar", + "https://jcenter.bintray.com/com/github/docker-java/docker-java-transport/3.2.8/docker-java-transport-3.2.8.jar", + "https://maven.google.com/com/github/docker-java/docker-java-transport/3.2.8/docker-java-transport-3.2.8.jar" + ], + "sha256": "1d3eb23fcf6c0b8097808e131394dfdcaf913b35b6b249945bd1762cc6423e0b", + "url": "https://repo1.maven.org/maven2/com/github/docker-java/docker-java-transport/3.2.8/docker-java-transport-3.2.8.jar" + }, + { + "coord": "com.github.docker-java:docker-java-transport:jar:sources:3.2.8", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/github/ben-manes/caffeine/caffeine/2.8.5/caffeine-2.8.5-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/github/docker-java/docker-java-transport/3.2.8/docker-java-transport-3.2.8-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/github/ben-manes/caffeine/caffeine/2.8.5/caffeine-2.8.5-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/github/ben-manes/caffeine/caffeine/2.8.5/caffeine-2.8.5-sources.jar", - "https://jcenter.bintray.com/com/github/ben-manes/caffeine/caffeine/2.8.5/caffeine-2.8.5-sources.jar", - "https://maven.google.com/com/github/ben-manes/caffeine/caffeine/2.8.5/caffeine-2.8.5-sources.jar" + "https://repo1.maven.org/maven2/com/github/docker-java/docker-java-transport/3.2.8/docker-java-transport-3.2.8-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/github/docker-java/docker-java-transport/3.2.8/docker-java-transport-3.2.8-sources.jar", + "https://jcenter.bintray.com/com/github/docker-java/docker-java-transport/3.2.8/docker-java-transport-3.2.8-sources.jar", + "https://maven.google.com/com/github/docker-java/docker-java-transport/3.2.8/docker-java-transport-3.2.8-sources.jar" ], - "sha256": "52657e78e76bb93117bff23ddc3a614b52e4d53a884f4bc7ca2de12ad97225c4", - "url": "https://repo1.maven.org/maven2/com/github/ben-manes/caffeine/caffeine/2.8.5/caffeine-2.8.5-sources.jar" + "sha256": "afb0289594aa172b604b0dad6fcaf122c4907b502636cfb3ca44e30d2cbf51af", + "url": "https://repo1.maven.org/maven2/com/github/docker-java/docker-java-transport/3.2.8/docker-java-transport-3.2.8-sources.jar" }, { - "coord": "com.github.spotbugs:spotbugs-annotations:4.0.6", + "coord": "com.github.spotbugs:spotbugs-annotations:4.0.3", "dependencies": [ "com.google.code.findbugs:jsr305:3.0.2" ], @@ -528,18 +662,18 @@ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.0.6/spotbugs-annotations-4.0.6.jar", + "file": "v1/https/repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.0.3/spotbugs-annotations-4.0.3.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.0.6/spotbugs-annotations-4.0.6.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/github/spotbugs/spotbugs-annotations/4.0.6/spotbugs-annotations-4.0.6.jar", - "https://jcenter.bintray.com/com/github/spotbugs/spotbugs-annotations/4.0.6/spotbugs-annotations-4.0.6.jar", - "https://maven.google.com/com/github/spotbugs/spotbugs-annotations/4.0.6/spotbugs-annotations-4.0.6.jar" + "https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.0.3/spotbugs-annotations-4.0.3.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/github/spotbugs/spotbugs-annotations/4.0.3/spotbugs-annotations-4.0.3.jar", + "https://jcenter.bintray.com/com/github/spotbugs/spotbugs-annotations/4.0.3/spotbugs-annotations-4.0.3.jar", + "https://maven.google.com/com/github/spotbugs/spotbugs-annotations/4.0.3/spotbugs-annotations-4.0.3.jar" ], - "sha256": "7b42ae56ce79dce6fa02d2f8422547c79b9e4f984320db967361400322bc3fd3", - "url": "https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.0.6/spotbugs-annotations-4.0.6.jar" + "sha256": "b131a0e7d6d2a148c4acb7f93c38f2060ec51c2da2477bfccbd83b0472a4f6d1", + "url": "https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.0.3/spotbugs-annotations-4.0.3.jar" }, { - "coord": "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", + "coord": "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", "dependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2" ], @@ -550,15 +684,15 @@ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.0.6/spotbugs-annotations-4.0.6-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.0.3/spotbugs-annotations-4.0.3-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.0.6/spotbugs-annotations-4.0.6-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/github/spotbugs/spotbugs-annotations/4.0.6/spotbugs-annotations-4.0.6-sources.jar", - "https://jcenter.bintray.com/com/github/spotbugs/spotbugs-annotations/4.0.6/spotbugs-annotations-4.0.6-sources.jar", - "https://maven.google.com/com/github/spotbugs/spotbugs-annotations/4.0.6/spotbugs-annotations-4.0.6-sources.jar" + "https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.0.3/spotbugs-annotations-4.0.3-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/github/spotbugs/spotbugs-annotations/4.0.3/spotbugs-annotations-4.0.3-sources.jar", + "https://jcenter.bintray.com/com/github/spotbugs/spotbugs-annotations/4.0.3/spotbugs-annotations-4.0.3-sources.jar", + "https://maven.google.com/com/github/spotbugs/spotbugs-annotations/4.0.3/spotbugs-annotations-4.0.3-sources.jar" ], "sha256": "b338136e3e82d585348cde58a8fe3a678e16f51a35c31c1463e05fefef557aad", - "url": "https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.0.6/spotbugs-annotations-4.0.6-sources.jar" + "url": "https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.0.3/spotbugs-annotations-4.0.3-sources.jar" }, { "coord": "com.github.wumpz:diffutils:2.2", @@ -641,612 +775,680 @@ "url": "https://repo1.maven.org/maven2/com/google/android/annotations/4.1.1.4/annotations-4.1.1.4-sources.jar" }, { - "coord": "com.google.api-client:google-api-client:1.30.11", + "coord": "com.google.api-client:google-api-client:1.32.1", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", + "com.google.http-client:google-http-client-gson", "io.grpc:grpc-context", "io.grpc:grpc-services", - "commons-codec:commons-codec", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "org.apache.httpcomponents:httpcore", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", - "com.google.http-client:google-http-client-jackson2", + "com.google.http-client:google-http-client-apache-v2", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", - "com.google.oauth-client:google-oauth-client" + "com.google.api:gax-grpc", + "com.google.oauth-client:google-oauth-client", + "com.google.http-client:google-http-client", + "org.apache.httpcomponents:httpclient" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api-client/google-api-client/1.30.11/google-api-client-1.30.11.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api-client/google-api-client/1.32.1/google-api-client-1.32.1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api-client/google-api-client/1.30.11/google-api-client-1.30.11.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api-client/google-api-client/1.30.11/google-api-client-1.30.11.jar", - "https://jcenter.bintray.com/com/google/api-client/google-api-client/1.30.11/google-api-client-1.30.11.jar", - "https://maven.google.com/com/google/api-client/google-api-client/1.30.11/google-api-client-1.30.11.jar" + "https://repo1.maven.org/maven2/com/google/api-client/google-api-client/1.32.1/google-api-client-1.32.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api-client/google-api-client/1.32.1/google-api-client-1.32.1.jar", + "https://jcenter.bintray.com/com/google/api-client/google-api-client/1.32.1/google-api-client-1.32.1.jar", + "https://maven.google.com/com/google/api-client/google-api-client/1.32.1/google-api-client-1.32.1.jar" ], - "sha256": "ee6f97865cc7de6c7c80955c3f37372cf3887bd75e4fc06f1058a6b4cd9bf4da", - "url": "https://repo1.maven.org/maven2/com/google/api-client/google-api-client/1.30.11/google-api-client-1.30.11.jar" + "sha256": "77adc2aeface4fc92a698bafa0f8bab716ab051bb21cb410600f5de2a7e6b30e", + "url": "https://repo1.maven.org/maven2/com/google/api-client/google-api-client/1.32.1/google-api-client-1.32.1.jar" }, { - "coord": "com.google.api-client:google-api-client:jar:sources:1.30.11", + "coord": "com.google.api-client:google-api-client:jar:sources:1.32.1", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", + "com.google.http-client:google-http-client-gson", "io.grpc:grpc-context", "io.grpc:grpc-services", - "commons-codec:commons-codec", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "org.apache.httpcomponents:httpcore", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", - "com.google.http-client:google-http-client-jackson2", + "com.google.http-client:google-http-client-apache-v2", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", - "com.google.oauth-client:google-oauth-client" + "com.google.api:gax-grpc", + "com.google.oauth-client:google-oauth-client", + "com.google.http-client:google-http-client", + "org.apache.httpcomponents:httpclient" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api-client/google-api-client/1.30.11/google-api-client-1.30.11-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api-client/google-api-client/1.32.1/google-api-client-1.32.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api-client/google-api-client/1.30.11/google-api-client-1.30.11-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api-client/google-api-client/1.30.11/google-api-client-1.30.11-sources.jar", - "https://jcenter.bintray.com/com/google/api-client/google-api-client/1.30.11/google-api-client-1.30.11-sources.jar", - "https://maven.google.com/com/google/api-client/google-api-client/1.30.11/google-api-client-1.30.11-sources.jar" + "https://repo1.maven.org/maven2/com/google/api-client/google-api-client/1.32.1/google-api-client-1.32.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api-client/google-api-client/1.32.1/google-api-client-1.32.1-sources.jar", + "https://jcenter.bintray.com/com/google/api-client/google-api-client/1.32.1/google-api-client-1.32.1-sources.jar", + "https://maven.google.com/com/google/api-client/google-api-client/1.32.1/google-api-client-1.32.1-sources.jar" ], - "sha256": "2f1cdbe08e236eeff9c33e48a72bd1b915bfafa298783ee816ea0d7e860fbc58", - "url": "https://repo1.maven.org/maven2/com/google/api-client/google-api-client/1.30.11/google-api-client-1.30.11-sources.jar" + "sha256": "95304dc92c7e283158bd63135085dc8a3c2dadaf06447ffe99148dee320a67f0", + "url": "https://repo1.maven.org/maven2/com/google/api-client/google-api-client/1.32.1/google-api-client-1.32.1-sources.jar" }, { - "coord": "com.google.api.grpc:grpc-google-common-protos:2.0.1", + "coord": "com.google.api.grpc:grpc-google-cloud-spanner-admin-database-v1:6.13.0", "dependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "com.google.guava:guava:30.0-android", - "io.grpc:grpc-protobuf-lite:1.33.1", + "org.checkerframework:checker-qual:3.13.0", + "com.google.errorprone:error_prone_annotations:2.9.0", "javax.annotation:javax.annotation-api:1.3.2", - "com.google.guava:failureaccess:1.0.1", - "com.google.protobuf:protobuf-java:3.13.0", - "io.grpc:grpc-stub:1.33.1", - "io.grpc:grpc-context:1.33.1", - "io.grpc:grpc-api:1.33.1", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "io.grpc:grpc-protobuf:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "org.checkerframework:checker-compat-qual:2.5.5" + "com.google.guava:failureaccess:1.0.1" ], "directDependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "com.google.guava:guava:30.0-android", - "io.grpc:grpc-protobuf-lite:1.33.1", + "org.checkerframework:checker-qual:3.13.0", + "com.google.errorprone:error_prone_annotations:2.9.0", "javax.annotation:javax.annotation-api:1.3.2", - "com.google.guava:failureaccess:1.0.1", - "com.google.protobuf:protobuf-java:3.13.0", - "io.grpc:grpc-stub:1.33.1", - "io.grpc:grpc-context:1.33.1", - "io.grpc:grpc-api:1.33.1", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "io.grpc:grpc-protobuf:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "org.checkerframework:checker-compat-qual:2.5.5" + "com.google.guava:failureaccess:1.0.1" ], "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", "com.google.template:soy", - "com.google.common.html.types:types" + "com.google.api.grpc:proto-google-iam-v1", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "com.google.api.grpc:proto-google-cloud-spanner-admin-database-v1", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "com.google.auto.value:auto-value-annotations", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.api.grpc:proto-google-common-protos" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/grpc-google-common-protos/2.0.1/grpc-google-common-protos-2.0.1.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-admin-database-v1/6.13.0/grpc-google-cloud-spanner-admin-database-v1-6.13.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-common-protos/2.0.1/grpc-google-common-protos-2.0.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/grpc-google-common-protos/2.0.1/grpc-google-common-protos-2.0.1.jar", - "https://jcenter.bintray.com/com/google/api/grpc/grpc-google-common-protos/2.0.1/grpc-google-common-protos-2.0.1.jar", - "https://maven.google.com/com/google/api/grpc/grpc-google-common-protos/2.0.1/grpc-google-common-protos-2.0.1.jar" + "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-admin-database-v1/6.13.0/grpc-google-cloud-spanner-admin-database-v1-6.13.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/grpc-google-cloud-spanner-admin-database-v1/6.13.0/grpc-google-cloud-spanner-admin-database-v1-6.13.0.jar", + "https://jcenter.bintray.com/com/google/api/grpc/grpc-google-cloud-spanner-admin-database-v1/6.13.0/grpc-google-cloud-spanner-admin-database-v1-6.13.0.jar", + "https://maven.google.com/com/google/api/grpc/grpc-google-cloud-spanner-admin-database-v1/6.13.0/grpc-google-cloud-spanner-admin-database-v1-6.13.0.jar" ], - "sha256": "37ca361d9d3b89bdae16d783f804d8861ea0ed3348830ce4ab640d77564f10dd", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-common-protos/2.0.1/grpc-google-common-protos-2.0.1.jar" + "sha256": "5634768fe17679aa07bb1501a586fbee0839ad33710d9ee65333a1f5c7a61fca", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-admin-database-v1/6.13.0/grpc-google-cloud-spanner-admin-database-v1-6.13.0.jar" }, { - "coord": "com.google.api.grpc:grpc-google-common-protos:jar:sources:2.0.1", + "coord": "com.google.api.grpc:grpc-google-cloud-spanner-admin-database-v1:jar:sources:6.13.0", "dependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.grpc:grpc-api:jar:sources:1.33.1", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-context:jar:sources:1.33.1", - "io.grpc:grpc-protobuf:jar:sources:1.33.1", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "io.grpc:grpc-stub:jar:sources:1.33.1", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "com.google.api:api-common:jar:sources:2.0.2", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.guava:guava:jar:sources:30.0-android", "com.google.guava:failureaccess:jar:sources:1.0.1", - "io.grpc:grpc-protobuf-lite:jar:sources:1.33.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.grpc:grpc-api:jar:sources:1.33.1", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-context:jar:sources:1.33.1", - "io.grpc:grpc-protobuf:jar:sources:1.33.1", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "io.grpc:grpc-stub:jar:sources:1.33.1", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "com.google.api:api-common:jar:sources:2.0.2", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.guava:guava:jar:sources:30.0-android", "com.google.guava:failureaccess:jar:sources:1.0.1", - "io.grpc:grpc-protobuf-lite:jar:sources:1.33.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", "com.google.template:soy", - "com.google.common.html.types:types" + "com.google.api.grpc:proto-google-iam-v1", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "com.google.api.grpc:proto-google-cloud-spanner-admin-database-v1", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "com.google.auto.value:auto-value-annotations", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.api.grpc:proto-google-common-protos" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/grpc-google-common-protos/2.0.1/grpc-google-common-protos-2.0.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-admin-database-v1/6.13.0/grpc-google-cloud-spanner-admin-database-v1-6.13.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-common-protos/2.0.1/grpc-google-common-protos-2.0.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/grpc-google-common-protos/2.0.1/grpc-google-common-protos-2.0.1-sources.jar", - "https://jcenter.bintray.com/com/google/api/grpc/grpc-google-common-protos/2.0.1/grpc-google-common-protos-2.0.1-sources.jar", - "https://maven.google.com/com/google/api/grpc/grpc-google-common-protos/2.0.1/grpc-google-common-protos-2.0.1-sources.jar" + "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-admin-database-v1/6.13.0/grpc-google-cloud-spanner-admin-database-v1-6.13.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/grpc-google-cloud-spanner-admin-database-v1/6.13.0/grpc-google-cloud-spanner-admin-database-v1-6.13.0-sources.jar", + "https://jcenter.bintray.com/com/google/api/grpc/grpc-google-cloud-spanner-admin-database-v1/6.13.0/grpc-google-cloud-spanner-admin-database-v1-6.13.0-sources.jar", + "https://maven.google.com/com/google/api/grpc/grpc-google-cloud-spanner-admin-database-v1/6.13.0/grpc-google-cloud-spanner-admin-database-v1-6.13.0-sources.jar" ], - "sha256": "2f1864a6686955f5fd046f88f099c72f0cffd233910b4479c33f5b434cef1849", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-common-protos/2.0.1/grpc-google-common-protos-2.0.1-sources.jar" + "sha256": "b8799e1c9acd524c47b069b3330cbc0b72f761e736715eaac322c8954bc319a3", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-admin-database-v1/6.13.0/grpc-google-cloud-spanner-admin-database-v1-6.13.0-sources.jar" }, { - "coord": "com.google.api.grpc:proto-google-cloud-firestore-v1:2.1.0", + "coord": "com.google.api.grpc:grpc-google-cloud-spanner-admin-instance-v1:6.13.0", "dependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.api:api-common:1.10.1", - "com.google.auto.value:auto-value-annotations:1.7.4", + "org.checkerframework:checker-qual:3.13.0", + "com.google.errorprone:error_prone_annotations:2.9.0", "javax.annotation:javax.annotation-api:1.3.2", - "com.google.guava:failureaccess:1.0.1", - "com.google.protobuf:protobuf-java:3.13.0", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "org.checkerframework:checker-compat-qual:2.5.5" + "com.google.guava:failureaccess:1.0.1" ], "directDependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.api:api-common:1.10.1", - "com.google.auto.value:auto-value-annotations:1.7.4", + "org.checkerframework:checker-qual:3.13.0", + "com.google.errorprone:error_prone_annotations:2.9.0", "javax.annotation:javax.annotation-api:1.3.2", - "com.google.guava:failureaccess:1.0.1", - "com.google.protobuf:protobuf-java:3.13.0", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "org.checkerframework:checker-compat-qual:2.5.5" + "com.google.guava:failureaccess:1.0.1" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", + "com.google.api.grpc:proto-google-iam-v1", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", + "com.google.auto.value:auto-value-annotations", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.api.grpc:proto-google-common-protos" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-firestore-v1/2.1.0/proto-google-cloud-firestore-v1-2.1.0.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-admin-instance-v1/6.13.0/grpc-google-cloud-spanner-admin-instance-v1-6.13.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-firestore-v1/2.1.0/proto-google-cloud-firestore-v1-2.1.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-firestore-v1/2.1.0/proto-google-cloud-firestore-v1-2.1.0.jar", - "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-firestore-v1/2.1.0/proto-google-cloud-firestore-v1-2.1.0.jar", - "https://maven.google.com/com/google/api/grpc/proto-google-cloud-firestore-v1/2.1.0/proto-google-cloud-firestore-v1-2.1.0.jar" + "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-admin-instance-v1/6.13.0/grpc-google-cloud-spanner-admin-instance-v1-6.13.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/grpc-google-cloud-spanner-admin-instance-v1/6.13.0/grpc-google-cloud-spanner-admin-instance-v1-6.13.0.jar", + "https://jcenter.bintray.com/com/google/api/grpc/grpc-google-cloud-spanner-admin-instance-v1/6.13.0/grpc-google-cloud-spanner-admin-instance-v1-6.13.0.jar", + "https://maven.google.com/com/google/api/grpc/grpc-google-cloud-spanner-admin-instance-v1/6.13.0/grpc-google-cloud-spanner-admin-instance-v1-6.13.0.jar" ], - "sha256": "bb29be647417dc3394062531da0da287273b8a980256abc737bd0535ec9901f6", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-firestore-v1/2.1.0/proto-google-cloud-firestore-v1-2.1.0.jar" + "sha256": "ba5765401dbc9b5c525a0bfede7a65856f5e0afb665b810c1a5484e071de54a6", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-admin-instance-v1/6.13.0/grpc-google-cloud-spanner-admin-instance-v1-6.13.0.jar" }, { - "coord": "com.google.api.grpc:proto-google-cloud-firestore-v1:jar:sources:2.1.0", + "coord": "com.google.api.grpc:grpc-google-cloud-spanner-admin-instance-v1:jar:sources:6.13.0", "dependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "com.google.api:api-common:jar:sources:1.10.1", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "com.google.api:api-common:jar:sources:2.0.2", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "com.google.api:api-common:jar:sources:1.10.1", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "com.google.api:api-common:jar:sources:2.0.2", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", + "com.google.api.grpc:proto-google-iam-v1", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", + "com.google.auto.value:auto-value-annotations", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.api.grpc:proto-google-common-protos" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-firestore-v1/2.1.0/proto-google-cloud-firestore-v1-2.1.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-admin-instance-v1/6.13.0/grpc-google-cloud-spanner-admin-instance-v1-6.13.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-firestore-v1/2.1.0/proto-google-cloud-firestore-v1-2.1.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-firestore-v1/2.1.0/proto-google-cloud-firestore-v1-2.1.0-sources.jar", - "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-firestore-v1/2.1.0/proto-google-cloud-firestore-v1-2.1.0-sources.jar", - "https://maven.google.com/com/google/api/grpc/proto-google-cloud-firestore-v1/2.1.0/proto-google-cloud-firestore-v1-2.1.0-sources.jar" + "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-admin-instance-v1/6.13.0/grpc-google-cloud-spanner-admin-instance-v1-6.13.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/grpc-google-cloud-spanner-admin-instance-v1/6.13.0/grpc-google-cloud-spanner-admin-instance-v1-6.13.0-sources.jar", + "https://jcenter.bintray.com/com/google/api/grpc/grpc-google-cloud-spanner-admin-instance-v1/6.13.0/grpc-google-cloud-spanner-admin-instance-v1-6.13.0-sources.jar", + "https://maven.google.com/com/google/api/grpc/grpc-google-cloud-spanner-admin-instance-v1/6.13.0/grpc-google-cloud-spanner-admin-instance-v1-6.13.0-sources.jar" ], - "sha256": "e293e803d889557e173de2a402eb175771d8813e26233b1329cdc13b6244f4e2", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-firestore-v1/2.1.0/proto-google-cloud-firestore-v1-2.1.0-sources.jar" + "sha256": "e0b5896f9bc99f8822b1f6ff6c4aae9542aea367b7928916a15d66b90dc20039", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-admin-instance-v1/6.13.0/grpc-google-cloud-spanner-admin-instance-v1-6.13.0-sources.jar" }, { - "coord": "com.google.api.grpc:proto-google-cloud-monitoring-v3:2.0.7", + "coord": "com.google.api.grpc:grpc-google-cloud-spanner-v1:6.13.0", "dependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auto.value:auto-value-annotations:1.7.4", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "org.checkerframework:checker-qual:3.13.0", + "com.google.errorprone:error_prone_annotations:2.9.0", "javax.annotation:javax.annotation-api:1.3.2", - "com.google.guava:failureaccess:1.0.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "org.checkerframework:checker-compat-qual:2.5.5" + "com.google.guava:failureaccess:1.0.1" ], "directDependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auto.value:auto-value-annotations:1.7.4", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "org.checkerframework:checker-qual:3.13.0", + "com.google.errorprone:error_prone_annotations:2.9.0", "javax.annotation:javax.annotation-api:1.3.2", - "com.google.guava:failureaccess:1.0.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "org.checkerframework:checker-compat-qual:2.5.5" + "com.google.guava:failureaccess:1.0.1" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", + "com.google.auto.value:auto-value-annotations", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "com.google.protobuf:protobuf-java", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", - "com.google.api:api-common", "com.google.common.html.types:types", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", - "com.google.api.grpc:proto-google-common-protos" + "com.google.api.grpc:proto-google-cloud-spanner-v1", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.0.7/proto-google-cloud-monitoring-v3-2.0.7.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-v1/6.13.0/grpc-google-cloud-spanner-v1-6.13.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.0.7/proto-google-cloud-monitoring-v3-2.0.7.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.0.7/proto-google-cloud-monitoring-v3-2.0.7.jar", - "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.0.7/proto-google-cloud-monitoring-v3-2.0.7.jar", - "https://maven.google.com/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.0.7/proto-google-cloud-monitoring-v3-2.0.7.jar" + "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-v1/6.13.0/grpc-google-cloud-spanner-v1-6.13.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/grpc-google-cloud-spanner-v1/6.13.0/grpc-google-cloud-spanner-v1-6.13.0.jar", + "https://jcenter.bintray.com/com/google/api/grpc/grpc-google-cloud-spanner-v1/6.13.0/grpc-google-cloud-spanner-v1-6.13.0.jar", + "https://maven.google.com/com/google/api/grpc/grpc-google-cloud-spanner-v1/6.13.0/grpc-google-cloud-spanner-v1-6.13.0.jar" ], - "sha256": "f77978796c88e25ca72bb5f0aefdb0aaa9454432c699ccfa9008dc4d44039650", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.0.7/proto-google-cloud-monitoring-v3-2.0.7.jar" + "sha256": "38e746d6c92d74f629155eb91a4d2c2b6827e13dfde489870738d7d76a5fb293", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-v1/6.13.0/grpc-google-cloud-spanner-v1-6.13.0.jar" }, { - "coord": "com.google.api.grpc:proto-google-cloud-monitoring-v3:jar:sources:2.0.7", + "coord": "com.google.api.grpc:grpc-google-cloud-spanner-v1:jar:sources:6.13.0", "dependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "com.google.api:api-common:jar:sources:2.0.2", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "com.google.api:api-common:jar:sources:2.0.2", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", + "com.google.auto.value:auto-value-annotations", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "com.google.protobuf:protobuf-java", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", - "com.google.api:api-common", "com.google.common.html.types:types", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", - "com.google.api.grpc:proto-google-common-protos" + "com.google.api.grpc:proto-google-cloud-spanner-v1", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.0.7/proto-google-cloud-monitoring-v3-2.0.7-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-v1/6.13.0/grpc-google-cloud-spanner-v1-6.13.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.0.7/proto-google-cloud-monitoring-v3-2.0.7-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.0.7/proto-google-cloud-monitoring-v3-2.0.7-sources.jar", - "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.0.7/proto-google-cloud-monitoring-v3-2.0.7-sources.jar", - "https://maven.google.com/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.0.7/proto-google-cloud-monitoring-v3-2.0.7-sources.jar" + "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-v1/6.13.0/grpc-google-cloud-spanner-v1-6.13.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/grpc-google-cloud-spanner-v1/6.13.0/grpc-google-cloud-spanner-v1-6.13.0-sources.jar", + "https://jcenter.bintray.com/com/google/api/grpc/grpc-google-cloud-spanner-v1/6.13.0/grpc-google-cloud-spanner-v1-6.13.0-sources.jar", + "https://maven.google.com/com/google/api/grpc/grpc-google-cloud-spanner-v1/6.13.0/grpc-google-cloud-spanner-v1-6.13.0-sources.jar" ], - "sha256": "e5e6404257c485a8775328790e721c4b40799410455be2b2062a99ceebf8acdd", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.0.7/proto-google-cloud-monitoring-v3-2.0.7-sources.jar" + "sha256": "036400fc1eb9c7187417d1a40ff2d1b0d85591647b346b3988b2a99016b9f338", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-spanner-v1/6.13.0/grpc-google-cloud-spanner-v1-6.13.0-sources.jar" }, { - "coord": "com.google.api.grpc:proto-google-cloud-pubsub-v1:1.90.7", + "coord": "com.google.api.grpc:grpc-google-common-protos:2.3.2", "dependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auto.value:auto-value-annotations:1.7.4", + "com.google.guava:guava:30.1.1-android", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "io.grpc:grpc-protobuf-lite:1.38.1", + "io.grpc:grpc-protobuf:1.38.1", + "io.grpc:grpc-context:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "io.grpc:grpc-api:1.38.1", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.errorprone:error_prone_annotations:2.4.0", + "io.grpc:grpc-stub:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", "org.checkerframework:checker-compat-qual:2.5.5" ], "directDependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auto.value:auto-value-annotations:1.7.4", + "com.google.guava:guava:30.1.1-android", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "io.grpc:grpc-protobuf-lite:1.38.1", + "io.grpc:grpc-protobuf:1.38.1", + "io.grpc:grpc-context:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "io.grpc:grpc-api:1.38.1", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.errorprone:error_prone_annotations:2.4.0", + "io.grpc:grpc-stub:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", "org.checkerframework:checker-compat-qual:2.5.5" ], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "com.google.protobuf:protobuf-java", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.api:api-common", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core", - "com.google.api.grpc:proto-google-common-protos" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.90.7/proto-google-cloud-pubsub-v1-1.90.7.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/grpc-google-common-protos/2.3.2/grpc-google-common-protos-2.3.2.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.90.7/proto-google-cloud-pubsub-v1-1.90.7.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.90.7/proto-google-cloud-pubsub-v1-1.90.7.jar", - "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.90.7/proto-google-cloud-pubsub-v1-1.90.7.jar", - "https://maven.google.com/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.90.7/proto-google-cloud-pubsub-v1-1.90.7.jar" + "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-common-protos/2.3.2/grpc-google-common-protos-2.3.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/grpc-google-common-protos/2.3.2/grpc-google-common-protos-2.3.2.jar", + "https://jcenter.bintray.com/com/google/api/grpc/grpc-google-common-protos/2.3.2/grpc-google-common-protos-2.3.2.jar", + "https://maven.google.com/com/google/api/grpc/grpc-google-common-protos/2.3.2/grpc-google-common-protos-2.3.2.jar" ], - "sha256": "fc87c55dacdd3383f4e77a5cb8d5def6c0a38578be635c2e12a1cc4305c99e2d", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.90.7/proto-google-cloud-pubsub-v1-1.90.7.jar" + "sha256": "d36edc544aa67c5b5fc34afb63c7af76c61cfcd113d45367586aad1af6be9e7f", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-common-protos/2.3.2/grpc-google-common-protos-2.3.2.jar" }, { - "coord": "com.google.api.grpc:proto-google-cloud-pubsub-v1:jar:sources:1.90.7", + "coord": "com.google.api.grpc:grpc-google-common-protos:jar:sources:2.3.2", "dependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "io.grpc:grpc-stub:jar:sources:1.38.1", + "io.grpc:grpc-api:jar:sources:1.38.1", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "io.grpc:grpc-protobuf-lite:jar:sources:1.38.1", "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-protobuf:jar:sources:1.38.1", + "io.grpc:grpc-context:jar:sources:1.38.1", "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "io.grpc:grpc-stub:jar:sources:1.38.1", + "io.grpc:grpc-api:jar:sources:1.38.1", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "io.grpc:grpc-protobuf-lite:jar:sources:1.38.1", "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-protobuf:jar:sources:1.38.1", + "io.grpc:grpc-context:jar:sources:1.38.1", "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "com.google.protobuf:protobuf-java", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.api:api-common", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core", - "com.google.api.grpc:proto-google-common-protos" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.90.7/proto-google-cloud-pubsub-v1-1.90.7-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/grpc-google-common-protos/2.3.2/grpc-google-common-protos-2.3.2-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.90.7/proto-google-cloud-pubsub-v1-1.90.7-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.90.7/proto-google-cloud-pubsub-v1-1.90.7-sources.jar", - "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.90.7/proto-google-cloud-pubsub-v1-1.90.7-sources.jar", - "https://maven.google.com/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.90.7/proto-google-cloud-pubsub-v1-1.90.7-sources.jar" + "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-common-protos/2.3.2/grpc-google-common-protos-2.3.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/grpc-google-common-protos/2.3.2/grpc-google-common-protos-2.3.2-sources.jar", + "https://jcenter.bintray.com/com/google/api/grpc/grpc-google-common-protos/2.3.2/grpc-google-common-protos-2.3.2-sources.jar", + "https://maven.google.com/com/google/api/grpc/grpc-google-common-protos/2.3.2/grpc-google-common-protos-2.3.2-sources.jar" ], - "sha256": "2d7f455128355a31c7e572aa2a4f7b4e2567a14c4507575b4ad6299745fb2641", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.90.7/proto-google-cloud-pubsub-v1-1.90.7-sources.jar" + "sha256": "c49cbafb95eab0afd7aef164569f39bbd76748fead72bf2a6fb5ee3cf642ad74", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-common-protos/2.3.2/grpc-google-common-protos-2.3.2-sources.jar" }, { - "coord": "com.google.api.grpc:proto-google-cloud-tasks-v2:1.30.7", + "coord": "com.google.api.grpc:proto-google-cloud-firestore-v1:2.6.2", "dependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auto.value:auto-value-annotations:1.7.4", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.errorprone:error_prone_annotations:2.4.0", "org.checkerframework:checker-compat-qual:2.5.5" ], "directDependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auto.value:auto-value-annotations:1.7.4", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.errorprone:error_prone_annotations:2.4.0", "org.checkerframework:checker-compat-qual:2.5.5" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", - "com.google.api.grpc:proto-google-iam-v1", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", - "com.google.protobuf:protobuf-java", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", - "com.google.api:api-common", "com.google.common.html.types:types", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", - "com.google.api.grpc:proto-google-common-protos" + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2/1.30.7/proto-google-cloud-tasks-v2-1.30.7.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-firestore-v1/2.6.2/proto-google-cloud-firestore-v1-2.6.2.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2/1.30.7/proto-google-cloud-tasks-v2-1.30.7.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-tasks-v2/1.30.7/proto-google-cloud-tasks-v2-1.30.7.jar", - "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-tasks-v2/1.30.7/proto-google-cloud-tasks-v2-1.30.7.jar", - "https://maven.google.com/com/google/api/grpc/proto-google-cloud-tasks-v2/1.30.7/proto-google-cloud-tasks-v2-1.30.7.jar" + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-firestore-v1/2.6.2/proto-google-cloud-firestore-v1-2.6.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-firestore-v1/2.6.2/proto-google-cloud-firestore-v1-2.6.2.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-firestore-v1/2.6.2/proto-google-cloud-firestore-v1-2.6.2.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-firestore-v1/2.6.2/proto-google-cloud-firestore-v1-2.6.2.jar" ], - "sha256": "eae24171605fc4e30e14fea858a836e39a16a314dc5e5334558f472b798ebe97", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2/1.30.7/proto-google-cloud-tasks-v2-1.30.7.jar" + "sha256": "82322303803e9e2da5165bc7640cf912f94f2ae98e59d18eff42534f97ec8785", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-firestore-v1/2.6.2/proto-google-cloud-firestore-v1-2.6.2.jar" }, { - "coord": "com.google.api.grpc:proto-google-cloud-tasks-v2:jar:sources:1.30.7", + "coord": "com.google.api.grpc:proto-google-cloud-firestore-v1:jar:sources:2.6.2", "dependencies": [ + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "com.google.api:api-common:jar:sources:2.0.2", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "com.google.api:api-common:jar:sources:2.0.2", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", - "com.google.api.grpc:proto-google-iam-v1", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", - "com.google.protobuf:protobuf-java", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", - "com.google.api:api-common", "com.google.common.html.types:types", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", - "com.google.api.grpc:proto-google-common-protos" + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2/1.30.7/proto-google-cloud-tasks-v2-1.30.7-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-firestore-v1/2.6.2/proto-google-cloud-firestore-v1-2.6.2-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2/1.30.7/proto-google-cloud-tasks-v2-1.30.7-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-tasks-v2/1.30.7/proto-google-cloud-tasks-v2-1.30.7-sources.jar", - "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-tasks-v2/1.30.7/proto-google-cloud-tasks-v2-1.30.7-sources.jar", - "https://maven.google.com/com/google/api/grpc/proto-google-cloud-tasks-v2/1.30.7/proto-google-cloud-tasks-v2-1.30.7-sources.jar" + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-firestore-v1/2.6.2/proto-google-cloud-firestore-v1-2.6.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-firestore-v1/2.6.2/proto-google-cloud-firestore-v1-2.6.2-sources.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-firestore-v1/2.6.2/proto-google-cloud-firestore-v1-2.6.2-sources.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-firestore-v1/2.6.2/proto-google-cloud-firestore-v1-2.6.2-sources.jar" ], - "sha256": "2419f8a08fae206e7bdc1bc6c3749ad960f961460ff8c83c1da293381e4b5cc4", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2/1.30.7/proto-google-cloud-tasks-v2-1.30.7-sources.jar" + "sha256": "bcc91371990019e68e54c9750bd020a99e6d6c5c33f2ee0275cf8913a27583b6", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-firestore-v1/2.6.2/proto-google-cloud-firestore-v1-2.6.2-sources.jar" }, { - "coord": "com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.86.7", + "coord": "com.google.api.grpc:proto-google-cloud-monitoring-v3:2.3.4", "dependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auto.value:auto-value-annotations:1.7.4", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.errorprone:error_prone_annotations:2.9.0", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.errorprone:error_prone_annotations:2.4.0", "org.checkerframework:checker-compat-qual:2.5.5" ], "directDependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auto.value:auto-value-annotations:1.7.4", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.errorprone:error_prone_annotations:2.9.0", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.errorprone:error_prone_annotations:2.4.0", "org.checkerframework:checker-compat-qual:2.5.5" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", - "com.google.api.grpc:proto-google-iam-v1", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "com.google.protobuf:protobuf-java", "io.grpc:grpc-protobuf", @@ -1254,50 +1456,54 @@ "com.google.api:api-common", "com.google.common.html.types:types", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", + "com.google.api:gax-grpc", "com.google.api.grpc:proto-google-common-protos" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.86.7/proto-google-cloud-tasks-v2beta2-0.86.7.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.3.4/proto-google-cloud-monitoring-v3-2.3.4.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.86.7/proto-google-cloud-tasks-v2beta2-0.86.7.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.86.7/proto-google-cloud-tasks-v2beta2-0.86.7.jar", - "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.86.7/proto-google-cloud-tasks-v2beta2-0.86.7.jar", - "https://maven.google.com/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.86.7/proto-google-cloud-tasks-v2beta2-0.86.7.jar" + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.3.4/proto-google-cloud-monitoring-v3-2.3.4.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.3.4/proto-google-cloud-monitoring-v3-2.3.4.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.3.4/proto-google-cloud-monitoring-v3-2.3.4.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.3.4/proto-google-cloud-monitoring-v3-2.3.4.jar" ], - "sha256": "01934acbf800858a738587a27c18116acaf5544b5da9ad6db408660042bca69b", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.86.7/proto-google-cloud-tasks-v2beta2-0.86.7.jar" + "sha256": "5c1a4d80cd9433ed45e8ad9e9cff4f9f5406e81f91f8aaf3b58cbeebc4f37dec", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.3.4/proto-google-cloud-monitoring-v3-2.3.4.jar" }, { - "coord": "com.google.api.grpc:proto-google-cloud-tasks-v2beta2:jar:sources:0.86.7", + "coord": "com.google.api.grpc:proto-google-cloud-monitoring-v3:jar:sources:2.3.4", "dependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", - "com.google.api.grpc:proto-google-iam-v1", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "com.google.protobuf:protobuf-java", "io.grpc:grpc-protobuf", @@ -1305,50 +1511,54 @@ "com.google.api:api-common", "com.google.common.html.types:types", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", + "com.google.api:gax-grpc", "com.google.api.grpc:proto-google-common-protos" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.86.7/proto-google-cloud-tasks-v2beta2-0.86.7-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.3.4/proto-google-cloud-monitoring-v3-2.3.4-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.86.7/proto-google-cloud-tasks-v2beta2-0.86.7-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.86.7/proto-google-cloud-tasks-v2beta2-0.86.7-sources.jar", - "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.86.7/proto-google-cloud-tasks-v2beta2-0.86.7-sources.jar", - "https://maven.google.com/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.86.7/proto-google-cloud-tasks-v2beta2-0.86.7-sources.jar" + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.3.4/proto-google-cloud-monitoring-v3-2.3.4-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.3.4/proto-google-cloud-monitoring-v3-2.3.4-sources.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.3.4/proto-google-cloud-monitoring-v3-2.3.4-sources.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.3.4/proto-google-cloud-monitoring-v3-2.3.4-sources.jar" ], - "sha256": "943a1d97d0037b1e2b0d25407cd026ddea25593b5922e7b2ff6d59a11a83359c", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.86.7/proto-google-cloud-tasks-v2beta2-0.86.7-sources.jar" + "sha256": "c204cf8749a3482f99b39de602c128dcd6457e12ebfe5a747d278852f0f7e789", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-monitoring-v3/2.3.4/proto-google-cloud-monitoring-v3-2.3.4-sources.jar" }, { - "coord": "com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.86.7", + "coord": "com.google.api.grpc:proto-google-cloud-pubsub-v1:1.95.5", "dependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auto.value:auto-value-annotations:1.7.4", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.errorprone:error_prone_annotations:2.9.0", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.errorprone:error_prone_annotations:2.4.0", "org.checkerframework:checker-compat-qual:2.5.5" ], "directDependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auto.value:auto-value-annotations:1.7.4", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.errorprone:error_prone_annotations:2.9.0", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.errorprone:error_prone_annotations:2.4.0", "org.checkerframework:checker-compat-qual:2.5.5" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", - "com.google.api.grpc:proto-google-iam-v1", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "com.google.protobuf:protobuf-java", "io.grpc:grpc-protobuf", @@ -1356,50 +1566,54 @@ "com.google.api:api-common", "com.google.common.html.types:types", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", + "com.google.api:gax-grpc", "com.google.api.grpc:proto-google-common-protos" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.86.7/proto-google-cloud-tasks-v2beta3-0.86.7.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.95.5/proto-google-cloud-pubsub-v1-1.95.5.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.86.7/proto-google-cloud-tasks-v2beta3-0.86.7.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.86.7/proto-google-cloud-tasks-v2beta3-0.86.7.jar", - "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.86.7/proto-google-cloud-tasks-v2beta3-0.86.7.jar", - "https://maven.google.com/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.86.7/proto-google-cloud-tasks-v2beta3-0.86.7.jar" + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.95.5/proto-google-cloud-pubsub-v1-1.95.5.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.95.5/proto-google-cloud-pubsub-v1-1.95.5.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.95.5/proto-google-cloud-pubsub-v1-1.95.5.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.95.5/proto-google-cloud-pubsub-v1-1.95.5.jar" ], - "sha256": "c06378be15093eb9669b95ce6eebe79f937bb9c86e6dcb2d7801d67154cc409a", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.86.7/proto-google-cloud-tasks-v2beta3-0.86.7.jar" + "sha256": "72fc5527bf7a4f37a6710bc711ebab9f4db8f16bd267cd12e5503c176d410d8d", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.95.5/proto-google-cloud-pubsub-v1-1.95.5.jar" }, { - "coord": "com.google.api.grpc:proto-google-cloud-tasks-v2beta3:jar:sources:0.86.7", + "coord": "com.google.api.grpc:proto-google-cloud-pubsub-v1:jar:sources:1.95.5", "dependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", - "com.google.api.grpc:proto-google-iam-v1", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "com.google.protobuf:protobuf-java", "io.grpc:grpc-protobuf", @@ -1407,354 +1621,1013 @@ "com.google.api:api-common", "com.google.common.html.types:types", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", + "com.google.api:gax-grpc", "com.google.api.grpc:proto-google-common-protos" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.86.7/proto-google-cloud-tasks-v2beta3-0.86.7-sources.jar", - "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.86.7/proto-google-cloud-tasks-v2beta3-0.86.7-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.86.7/proto-google-cloud-tasks-v2beta3-0.86.7-sources.jar", - "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.86.7/proto-google-cloud-tasks-v2beta3-0.86.7-sources.jar", - "https://maven.google.com/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.86.7/proto-google-cloud-tasks-v2beta3-0.86.7-sources.jar" - ], - "sha256": "a541a33e308efea75f0ed87e54db15c14c6e1ada0c6256b2fb6596f144807d42", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.86.7/proto-google-cloud-tasks-v2beta3-0.86.7-sources.jar" - }, - { - "coord": "com.google.api.grpc:proto-google-common-protos:2.0.1", - "dependencies": [ - "com.google.protobuf:protobuf-java:3.13.0" - ], - "directDependencies": [ - "com.google.protobuf:protobuf-java:3.13.0" - ], - "exclusions": [ - "com.google.template:soy", - "com.google.common.html.types:types" - ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-common-protos/2.0.1/proto-google-common-protos-2.0.1.jar", - "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-common-protos/2.0.1/proto-google-common-protos-2.0.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-common-protos/2.0.1/proto-google-common-protos-2.0.1.jar", - "https://jcenter.bintray.com/com/google/api/grpc/proto-google-common-protos/2.0.1/proto-google-common-protos-2.0.1.jar", - "https://maven.google.com/com/google/api/grpc/proto-google-common-protos/2.0.1/proto-google-common-protos-2.0.1.jar" - ], - "sha256": "5ce71656118618731e34a5d4c61aa3a031be23446dc7de8b5a5e77b66ebcd6ef", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-common-protos/2.0.1/proto-google-common-protos-2.0.1.jar" - }, - { - "coord": "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "dependencies": [ - "com.google.protobuf:protobuf-java:jar:sources:3.13.0" - ], - "directDependencies": [ - "com.google.protobuf:protobuf-java:jar:sources:3.13.0" - ], - "exclusions": [ - "com.google.template:soy", - "com.google.common.html.types:types" - ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-common-protos/2.0.1/proto-google-common-protos-2.0.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.95.5/proto-google-cloud-pubsub-v1-1.95.5-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-common-protos/2.0.1/proto-google-common-protos-2.0.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-common-protos/2.0.1/proto-google-common-protos-2.0.1-sources.jar", - "https://jcenter.bintray.com/com/google/api/grpc/proto-google-common-protos/2.0.1/proto-google-common-protos-2.0.1-sources.jar", - "https://maven.google.com/com/google/api/grpc/proto-google-common-protos/2.0.1/proto-google-common-protos-2.0.1-sources.jar" + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.95.5/proto-google-cloud-pubsub-v1-1.95.5-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.95.5/proto-google-cloud-pubsub-v1-1.95.5-sources.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.95.5/proto-google-cloud-pubsub-v1-1.95.5-sources.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.95.5/proto-google-cloud-pubsub-v1-1.95.5-sources.jar" ], - "sha256": "e5caddf23bcb7224ade6d3809e073f02715b76779540db58c5a76b994b7a07e7", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-common-protos/2.0.1/proto-google-common-protos-2.0.1-sources.jar" + "sha256": "db882d04df60659c693229543b63b4b16e9d6c5d16ad755e797c6fffd4ce638b", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-pubsub-v1/1.95.5/proto-google-cloud-pubsub-v1-1.95.5-sources.jar" }, { - "coord": "com.google.api.grpc:proto-google-iam-v1:1.0.2", + "coord": "com.google.api.grpc:proto-google-cloud-spanner-admin-database-v1:6.13.0", "dependencies": [ - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.protobuf:protobuf-java:3.13.0" + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "org.checkerframework:checker-qual:3.13.0", + "com.google.errorprone:error_prone_annotations:2.9.0", + "javax.annotation:javax.annotation-api:1.3.2", + "com.google.guava:failureaccess:1.0.1" ], "directDependencies": [ - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.protobuf:protobuf-java:3.13.0" + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "org.checkerframework:checker-qual:3.13.0", + "com.google.errorprone:error_prone_annotations:2.9.0", + "javax.annotation:javax.annotation-api:1.3.2", + "com.google.guava:failureaccess:1.0.1" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", + "com.google.api.grpc:proto-google-iam-v1", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", + "com.google.api:api-common", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.api.grpc:proto-google-common-protos" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-iam-v1/1.0.2/proto-google-iam-v1-1.0.2.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-admin-database-v1/6.13.0/proto-google-cloud-spanner-admin-database-v1-6.13.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-iam-v1/1.0.2/proto-google-iam-v1-1.0.2.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-iam-v1/1.0.2/proto-google-iam-v1-1.0.2.jar", - "https://jcenter.bintray.com/com/google/api/grpc/proto-google-iam-v1/1.0.2/proto-google-iam-v1-1.0.2.jar", - "https://maven.google.com/com/google/api/grpc/proto-google-iam-v1/1.0.2/proto-google-iam-v1-1.0.2.jar" + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-admin-database-v1/6.13.0/proto-google-cloud-spanner-admin-database-v1-6.13.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-spanner-admin-database-v1/6.13.0/proto-google-cloud-spanner-admin-database-v1-6.13.0.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-spanner-admin-database-v1/6.13.0/proto-google-cloud-spanner-admin-database-v1-6.13.0.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-spanner-admin-database-v1/6.13.0/proto-google-cloud-spanner-admin-database-v1-6.13.0.jar" ], - "sha256": "edd482f63438cc574bb3c3667c9014b03bbdd26bbefa5915742e11f216fa8938", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-iam-v1/1.0.2/proto-google-iam-v1-1.0.2.jar" + "sha256": "fa00225bac268b5cfc92c9e2b73027dfd6f52c51823c1772f1d06d93e00685c3", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-admin-database-v1/6.13.0/proto-google-cloud-spanner-admin-database-v1-6.13.0.jar" }, { - "coord": "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.0.2", + "coord": "com.google.api.grpc:proto-google-cloud-spanner-admin-database-v1:jar:sources:6.13.0", "dependencies": [ - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0" + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0" + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", + "com.google.api.grpc:proto-google-iam-v1", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", + "com.google.api:api-common", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.api.grpc:proto-google-common-protos" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-iam-v1/1.0.2/proto-google-iam-v1-1.0.2-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-admin-database-v1/6.13.0/proto-google-cloud-spanner-admin-database-v1-6.13.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-iam-v1/1.0.2/proto-google-iam-v1-1.0.2-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-iam-v1/1.0.2/proto-google-iam-v1-1.0.2-sources.jar", - "https://jcenter.bintray.com/com/google/api/grpc/proto-google-iam-v1/1.0.2/proto-google-iam-v1-1.0.2-sources.jar", - "https://maven.google.com/com/google/api/grpc/proto-google-iam-v1/1.0.2/proto-google-iam-v1-1.0.2-sources.jar" + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-admin-database-v1/6.13.0/proto-google-cloud-spanner-admin-database-v1-6.13.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-spanner-admin-database-v1/6.13.0/proto-google-cloud-spanner-admin-database-v1-6.13.0-sources.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-spanner-admin-database-v1/6.13.0/proto-google-cloud-spanner-admin-database-v1-6.13.0-sources.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-spanner-admin-database-v1/6.13.0/proto-google-cloud-spanner-admin-database-v1-6.13.0-sources.jar" ], - "sha256": "9505fd87275f5344af588404137d59eaef5c449944315b9da8ef65dd13b0e7b7", - "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-iam-v1/1.0.2/proto-google-iam-v1-1.0.2-sources.jar" + "sha256": "18a772c3307187f4752b1088a9cce8090e950d4e9966039b37e46b217925a4b8", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-admin-database-v1/6.13.0/proto-google-cloud-spanner-admin-database-v1-6.13.0-sources.jar" }, { - "coord": "com.google.api:api-common:1.10.1", + "coord": "com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1:6.13.0", "dependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "org.checkerframework:checker-qual:3.13.0", + "com.google.errorprone:error_prone_annotations:2.9.0", "javax.annotation:javax.annotation-api:1.3.2", - "com.google.auto.value:auto-value-annotations:1.7.4", + "com.google.guava:failureaccess:1.0.1" + ], + "directDependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "org.checkerframework:checker-qual:3.13.0", + "com.google.errorprone:error_prone_annotations:2.9.0", + "javax.annotation:javax.annotation-api:1.3.2", + "com.google.guava:failureaccess:1.0.1" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "com.google.api.grpc:proto-google-iam-v1", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.api:api-common", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.api.grpc:proto-google-common-protos" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-admin-instance-v1/6.13.0/proto-google-cloud-spanner-admin-instance-v1-6.13.0.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-admin-instance-v1/6.13.0/proto-google-cloud-spanner-admin-instance-v1-6.13.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-spanner-admin-instance-v1/6.13.0/proto-google-cloud-spanner-admin-instance-v1-6.13.0.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-spanner-admin-instance-v1/6.13.0/proto-google-cloud-spanner-admin-instance-v1-6.13.0.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-spanner-admin-instance-v1/6.13.0/proto-google-cloud-spanner-admin-instance-v1-6.13.0.jar" + ], + "sha256": "02ac056b6a694f136544477bce2340c4bb2599ca8eb9a9ac51e8dde23aabcd3e", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-admin-instance-v1/6.13.0/proto-google-cloud-spanner-admin-instance-v1-6.13.0.jar" + }, + { + "coord": "com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1:jar:sources:6.13.0", + "dependencies": [ + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" + ], + "directDependencies": [ + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "com.google.api.grpc:proto-google-iam-v1", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.api:api-common", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.api.grpc:proto-google-common-protos" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-admin-instance-v1/6.13.0/proto-google-cloud-spanner-admin-instance-v1-6.13.0-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-admin-instance-v1/6.13.0/proto-google-cloud-spanner-admin-instance-v1-6.13.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-spanner-admin-instance-v1/6.13.0/proto-google-cloud-spanner-admin-instance-v1-6.13.0-sources.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-spanner-admin-instance-v1/6.13.0/proto-google-cloud-spanner-admin-instance-v1-6.13.0-sources.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-spanner-admin-instance-v1/6.13.0/proto-google-cloud-spanner-admin-instance-v1-6.13.0-sources.jar" + ], + "sha256": "3e2eeb2cfa9ed2c927c4d265652d33e7130f0a264dd60275142a39c8cedfe78a", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-admin-instance-v1/6.13.0/proto-google-cloud-spanner-admin-instance-v1-6.13.0-sources.jar" + }, + { + "coord": "com.google.api.grpc:proto-google-cloud-spanner-v1:6.13.0", + "dependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "org.checkerframework:checker-qual:3.13.0", + "com.google.errorprone:error_prone_annotations:2.9.0", + "javax.annotation:javax.annotation-api:1.3.2", + "com.google.guava:failureaccess:1.0.1" + ], + "directDependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "org.checkerframework:checker-qual:3.13.0", + "com.google.errorprone:error_prone_annotations:2.9.0", + "javax.annotation:javax.annotation-api:1.3.2", + "com.google.guava:failureaccess:1.0.1" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.api:api-common", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.api.grpc:proto-google-common-protos" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-v1/6.13.0/proto-google-cloud-spanner-v1-6.13.0.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-v1/6.13.0/proto-google-cloud-spanner-v1-6.13.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-spanner-v1/6.13.0/proto-google-cloud-spanner-v1-6.13.0.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-spanner-v1/6.13.0/proto-google-cloud-spanner-v1-6.13.0.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-spanner-v1/6.13.0/proto-google-cloud-spanner-v1-6.13.0.jar" + ], + "sha256": "76554799b07bba9ec682cedfe9d4fb916d80317aab3d2753e644d86df6156ecb", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-v1/6.13.0/proto-google-cloud-spanner-v1-6.13.0.jar" + }, + { + "coord": "com.google.api.grpc:proto-google-cloud-spanner-v1:jar:sources:6.13.0", + "dependencies": [ + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" + ], + "directDependencies": [ + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.api:api-common", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.api.grpc:proto-google-common-protos" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-v1/6.13.0/proto-google-cloud-spanner-v1-6.13.0-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-v1/6.13.0/proto-google-cloud-spanner-v1-6.13.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-spanner-v1/6.13.0/proto-google-cloud-spanner-v1-6.13.0-sources.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-spanner-v1/6.13.0/proto-google-cloud-spanner-v1-6.13.0-sources.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-spanner-v1/6.13.0/proto-google-cloud-spanner-v1-6.13.0-sources.jar" + ], + "sha256": "724e1e6d3e233190694eb1839187d1b36b85de182df81dac2cc8a1af4d7febf0", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-spanner-v1/6.13.0/proto-google-cloud-spanner-v1-6.13.0-sources.jar" + }, + { + "coord": "com.google.api.grpc:proto-google-cloud-tasks-v2:1.33.2", + "dependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.errorprone:error_prone_annotations:2.9.0", + "javax.annotation:javax.annotation-api:1.3.2", + "com.google.guava:failureaccess:1.0.1", + "org.checkerframework:checker-compat-qual:2.5.5" + ], + "directDependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.errorprone:error_prone_annotations:2.9.0", + "javax.annotation:javax.annotation-api:1.3.2", + "com.google.guava:failureaccess:1.0.1", + "org.checkerframework:checker-compat-qual:2.5.5" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "com.google.api.grpc:proto-google-iam-v1", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.api:api-common", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.api.grpc:proto-google-common-protos" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2/1.33.2/proto-google-cloud-tasks-v2-1.33.2.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2/1.33.2/proto-google-cloud-tasks-v2-1.33.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-tasks-v2/1.33.2/proto-google-cloud-tasks-v2-1.33.2.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-tasks-v2/1.33.2/proto-google-cloud-tasks-v2-1.33.2.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-tasks-v2/1.33.2/proto-google-cloud-tasks-v2-1.33.2.jar" + ], + "sha256": "d4d215f9f54cb5167899613571c6fa36393f9fd9bf9668065d42c6fdc6714b8f", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2/1.33.2/proto-google-cloud-tasks-v2-1.33.2.jar" + }, + { + "coord": "com.google.api.grpc:proto-google-cloud-tasks-v2:jar:sources:1.33.2", + "dependencies": [ + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" + ], + "directDependencies": [ + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "com.google.api.grpc:proto-google-iam-v1", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.api:api-common", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.api.grpc:proto-google-common-protos" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2/1.33.2/proto-google-cloud-tasks-v2-1.33.2-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2/1.33.2/proto-google-cloud-tasks-v2-1.33.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-tasks-v2/1.33.2/proto-google-cloud-tasks-v2-1.33.2-sources.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-tasks-v2/1.33.2/proto-google-cloud-tasks-v2-1.33.2-sources.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-tasks-v2/1.33.2/proto-google-cloud-tasks-v2-1.33.2-sources.jar" + ], + "sha256": "64326ec5f6066eee51a6acec833d2ba90ad942bf22f6ed738d12d55d73ca3ec8", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2/1.33.2/proto-google-cloud-tasks-v2-1.33.2-sources.jar" + }, + { + "coord": "com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.89.2", + "dependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.errorprone:error_prone_annotations:2.9.0", + "javax.annotation:javax.annotation-api:1.3.2", + "com.google.guava:failureaccess:1.0.1", + "org.checkerframework:checker-compat-qual:2.5.5" + ], + "directDependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.errorprone:error_prone_annotations:2.9.0", + "javax.annotation:javax.annotation-api:1.3.2", + "com.google.guava:failureaccess:1.0.1", + "org.checkerframework:checker-compat-qual:2.5.5" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "com.google.api.grpc:proto-google-iam-v1", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.api:api-common", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.api.grpc:proto-google-common-protos" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.89.2/proto-google-cloud-tasks-v2beta2-0.89.2.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.89.2/proto-google-cloud-tasks-v2beta2-0.89.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.89.2/proto-google-cloud-tasks-v2beta2-0.89.2.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.89.2/proto-google-cloud-tasks-v2beta2-0.89.2.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.89.2/proto-google-cloud-tasks-v2beta2-0.89.2.jar" + ], + "sha256": "fbd6c8832e9ef6fda33f160a100c6d25097da217db1d961008b026035ab1308b", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.89.2/proto-google-cloud-tasks-v2beta2-0.89.2.jar" + }, + { + "coord": "com.google.api.grpc:proto-google-cloud-tasks-v2beta2:jar:sources:0.89.2", + "dependencies": [ + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" + ], + "directDependencies": [ + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "com.google.api.grpc:proto-google-iam-v1", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.api:api-common", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.api.grpc:proto-google-common-protos" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.89.2/proto-google-cloud-tasks-v2beta2-0.89.2-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.89.2/proto-google-cloud-tasks-v2beta2-0.89.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.89.2/proto-google-cloud-tasks-v2beta2-0.89.2-sources.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.89.2/proto-google-cloud-tasks-v2beta2-0.89.2-sources.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.89.2/proto-google-cloud-tasks-v2beta2-0.89.2-sources.jar" + ], + "sha256": "01788e81c28f7f2e1bafbb8655f1f43d8300c21219a5bfcc20ac31d18c7f9c58", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta2/0.89.2/proto-google-cloud-tasks-v2beta2-0.89.2-sources.jar" + }, + { + "coord": "com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.89.2", + "dependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.errorprone:error_prone_annotations:2.9.0", + "javax.annotation:javax.annotation-api:1.3.2", + "com.google.guava:failureaccess:1.0.1", + "org.checkerframework:checker-compat-qual:2.5.5" + ], + "directDependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.errorprone:error_prone_annotations:2.9.0", + "javax.annotation:javax.annotation-api:1.3.2", + "com.google.guava:failureaccess:1.0.1", + "org.checkerframework:checker-compat-qual:2.5.5" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "com.google.api.grpc:proto-google-iam-v1", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.api:api-common", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.api.grpc:proto-google-common-protos" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.89.2/proto-google-cloud-tasks-v2beta3-0.89.2.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.89.2/proto-google-cloud-tasks-v2beta3-0.89.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.89.2/proto-google-cloud-tasks-v2beta3-0.89.2.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.89.2/proto-google-cloud-tasks-v2beta3-0.89.2.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.89.2/proto-google-cloud-tasks-v2beta3-0.89.2.jar" + ], + "sha256": "2024e5a0afa6774bfb404d991bdf02d5156792e5dd3cc15048a75ba56fe37e88", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.89.2/proto-google-cloud-tasks-v2beta3-0.89.2.jar" + }, + { + "coord": "com.google.api.grpc:proto-google-cloud-tasks-v2beta3:jar:sources:0.89.2", + "dependencies": [ + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" + ], + "directDependencies": [ + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "com.google.api.grpc:proto-google-iam-v1", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.api:api-common", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.api.grpc:proto-google-common-protos" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.89.2/proto-google-cloud-tasks-v2beta3-0.89.2-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.89.2/proto-google-cloud-tasks-v2beta3-0.89.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.89.2/proto-google-cloud-tasks-v2beta3-0.89.2-sources.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.89.2/proto-google-cloud-tasks-v2beta3-0.89.2-sources.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.89.2/proto-google-cloud-tasks-v2beta3-0.89.2-sources.jar" + ], + "sha256": "db3092ed88afbf40d6e517a0fd334e11e6462319d74c98786e31719c7f63c524", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-tasks-v2beta3/0.89.2/proto-google-cloud-tasks-v2beta3-0.89.2-sources.jar" + }, + { + "coord": "com.google.api.grpc:proto-google-common-protos:2.5.0", + "dependencies": [ + "com.google.protobuf:protobuf-java:3.17.3" + ], + "directDependencies": [ + "com.google.protobuf:protobuf-java:3.17.3" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-common-protos/2.5.0/proto-google-common-protos-2.5.0.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-common-protos/2.5.0/proto-google-common-protos-2.5.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-common-protos/2.5.0/proto-google-common-protos-2.5.0.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-common-protos/2.5.0/proto-google-common-protos-2.5.0.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-common-protos/2.5.0/proto-google-common-protos-2.5.0.jar" + ], + "sha256": "f539166a810a83bea91ef86e7d0208bf9b0bf477ebe937eacdd59e8e3e6bc74e", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-common-protos/2.5.0/proto-google-common-protos-2.5.0.jar" + }, + { + "coord": "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "dependencies": [ + "com.google.protobuf:protobuf-java:jar:sources:3.17.3" + ], + "directDependencies": [ + "com.google.protobuf:protobuf-java:jar:sources:3.17.3" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-common-protos/2.5.0/proto-google-common-protos-2.5.0-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-common-protos/2.5.0/proto-google-common-protos-2.5.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-common-protos/2.5.0/proto-google-common-protos-2.5.0-sources.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-common-protos/2.5.0/proto-google-common-protos-2.5.0-sources.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-common-protos/2.5.0/proto-google-common-protos-2.5.0-sources.jar" + ], + "sha256": "eadf41797a6def52079cae189821a8abc8b463749f70aa69ffdbcb9d458d9d7a", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-common-protos/2.5.0/proto-google-common-protos-2.5.0-sources.jar" + }, + { + "coord": "com.google.api.grpc:proto-google-iam-v1:1.1.2", + "dependencies": [ + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.protobuf:protobuf-java:3.17.3" + ], + "directDependencies": [ + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.protobuf:protobuf-java:3.17.3" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-iam-v1/1.1.2/proto-google-iam-v1-1.1.2.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-iam-v1/1.1.2/proto-google-iam-v1-1.1.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-iam-v1/1.1.2/proto-google-iam-v1-1.1.2.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-iam-v1/1.1.2/proto-google-iam-v1-1.1.2.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-iam-v1/1.1.2/proto-google-iam-v1-1.1.2.jar" + ], + "sha256": "afdd8e355f23a6c39ba202d62de46a8959276d55577f4b0fc15c8d3f22f44595", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-iam-v1/1.1.2/proto-google-iam-v1-1.1.2.jar" + }, + { + "coord": "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.1.2", + "dependencies": [ + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0" + ], + "directDependencies": [ + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/api/grpc/proto-google-iam-v1/1.1.2/proto-google-iam-v1-1.1.2-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-iam-v1/1.1.2/proto-google-iam-v1-1.1.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/grpc/proto-google-iam-v1/1.1.2/proto-google-iam-v1-1.1.2-sources.jar", + "https://jcenter.bintray.com/com/google/api/grpc/proto-google-iam-v1/1.1.2/proto-google-iam-v1-1.1.2-sources.jar", + "https://maven.google.com/com/google/api/grpc/proto-google-iam-v1/1.1.2/proto-google-iam-v1-1.1.2-sources.jar" + ], + "sha256": "3862bac9710b0f67bf39aee61f968fcd12c7ea0becc7d36f82813603becb999f", + "url": "https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-iam-v1/1.1.2/proto-google-iam-v1-1.1.2-sources.jar" + }, + { + "coord": "com.google.api:api-common:2.0.2", + "dependencies": [ + "javax.annotation:javax.annotation-api:1.3.2", + "com.google.auto.value:auto-value-annotations:1.8.2", "com.google.code.findbugs:jsr305:3.0.2" ], "directDependencies": [ - "com.google.auto.value:auto-value-annotations:1.7.4", + "com.google.auto.value:auto-value-annotations:1.8.2", "com.google.code.findbugs:jsr305:3.0.2", "javax.annotation:javax.annotation-api:1.3.2" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/api-common/1.10.1/api-common-1.10.1.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/api-common/2.0.2/api-common-2.0.2.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/api-common/1.10.1/api-common-1.10.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/api-common/1.10.1/api-common-1.10.1.jar", - "https://jcenter.bintray.com/com/google/api/api-common/1.10.1/api-common-1.10.1.jar", - "https://maven.google.com/com/google/api/api-common/1.10.1/api-common-1.10.1.jar" + "https://repo1.maven.org/maven2/com/google/api/api-common/2.0.2/api-common-2.0.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/api-common/2.0.2/api-common-2.0.2.jar", + "https://jcenter.bintray.com/com/google/api/api-common/2.0.2/api-common-2.0.2.jar", + "https://maven.google.com/com/google/api/api-common/2.0.2/api-common-2.0.2.jar" ], - "sha256": "2a033f24bb620383eda440ad307cb8077cfec1c7eadc684d65216123a1b9613a", - "url": "https://repo1.maven.org/maven2/com/google/api/api-common/1.10.1/api-common-1.10.1.jar" + "sha256": "d3c6632e3695a4cd5d0b25314f4585ce1a81743b2df345728d85e16208805340", + "url": "https://repo1.maven.org/maven2/com/google/api/api-common/2.0.2/api-common-2.0.2.jar" }, { - "coord": "com.google.api:api-common:jar:sources:1.10.1", + "coord": "com.google.api:api-common:jar:sources:2.0.2", "dependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2", "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4" + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2" ], "directDependencies": [ - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/api-common/1.10.1/api-common-1.10.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/api-common/2.0.2/api-common-2.0.2-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/api-common/1.10.1/api-common-1.10.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/api-common/1.10.1/api-common-1.10.1-sources.jar", - "https://jcenter.bintray.com/com/google/api/api-common/1.10.1/api-common-1.10.1-sources.jar", - "https://maven.google.com/com/google/api/api-common/1.10.1/api-common-1.10.1-sources.jar" + "https://repo1.maven.org/maven2/com/google/api/api-common/2.0.2/api-common-2.0.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/api-common/2.0.2/api-common-2.0.2-sources.jar", + "https://jcenter.bintray.com/com/google/api/api-common/2.0.2/api-common-2.0.2-sources.jar", + "https://maven.google.com/com/google/api/api-common/2.0.2/api-common-2.0.2-sources.jar" ], - "sha256": "26dded52e7a7ddd97fc7052abcdc12a2bfc26c8338ef8510103bb5590525fb49", - "url": "https://repo1.maven.org/maven2/com/google/api/api-common/1.10.1/api-common-1.10.1-sources.jar" + "sha256": "3c24a53cf6221dfac7ee92e51f6e384116efb5dc14f31eb6d806e7f02a47bdf2", + "url": "https://repo1.maven.org/maven2/com/google/api/api-common/2.0.2/api-common-2.0.2-sources.jar" }, { - "coord": "com.google.api:gax-grpc:1.60.0", + "coord": "com.google.api:gax-grpc:2.5.0", "dependencies": [ - "org.conscrypt:conscrypt-openjdk-uber:2.5.1", - "com.google.j2objc:j2objc-annotations:1.3", - "commons-logging:commons-logging:1.2", - "io.opencensus:opencensus-contrib-http-util:0.24.0", + "com.google.api:api-common:2.0.2", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "com.google.api:api-common:1.10.1", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "commons-codec:commons-codec:1.11", - "io.opencensus:opencensus-api:0.24.0", - "com.google.code.gson:gson:2.8.6", - "org.threeten:threetenbp:1.5.0", - "io.grpc:grpc-alts:1.33.1", - "org.apache.httpcomponents:httpcore:4.4.13", - "com.google.auto.value:auto-value-annotations:1.7.4", - "com.google.api:gax:1.60.0", - "com.google.protobuf:protobuf-java-util:3.13.0", - "io.grpc:grpc-netty-shaded:1.33.1", - "org.apache.commons:commons-lang3:3.5", - "javax.annotation:javax.annotation-api:1.3.2", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", - "com.google.protobuf:protobuf-java:3.13.0", - "org.apache.httpcomponents:httpclient:4.5.13", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.http-client:google-http-client:1.37.0", - "io.grpc:grpc-grpclb:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.api:gax:2.5.0", + "org.threeten:threetenbp:1.5.1", + "io.opencensus:opencensus-api:0.28.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0" ], "directDependencies": [ + "com.google.api:api-common:2.0.2", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "com.google.api:api-common:1.10.1", - "org.threeten:threetenbp:1.5.0", - "io.grpc:grpc-alts:1.33.1", - "com.google.api:gax:1.60.0", - "io.grpc:grpc-netty-shaded:1.33.1", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.api.grpc:proto-google-common-protos:2.0.1" + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.api:gax:2.5.0", + "org.threeten:threetenbp:1.5.1", + "com.google.auth:google-auth-library-oauth2-http:1.1.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/gax-grpc/1.60.0/gax-grpc-1.60.0.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/gax-grpc/2.5.0/gax-grpc-2.5.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/gax-grpc/1.60.0/gax-grpc-1.60.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/gax-grpc/1.60.0/gax-grpc-1.60.0.jar", - "https://jcenter.bintray.com/com/google/api/gax-grpc/1.60.0/gax-grpc-1.60.0.jar", - "https://maven.google.com/com/google/api/gax-grpc/1.60.0/gax-grpc-1.60.0.jar" + "https://repo1.maven.org/maven2/com/google/api/gax-grpc/2.5.0/gax-grpc-2.5.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/gax-grpc/2.5.0/gax-grpc-2.5.0.jar", + "https://jcenter.bintray.com/com/google/api/gax-grpc/2.5.0/gax-grpc-2.5.0.jar", + "https://maven.google.com/com/google/api/gax-grpc/2.5.0/gax-grpc-2.5.0.jar" ], - "sha256": "5eec36a31211de13402aff95f653cb951da3d317a7d2a531fc98e32c55c66143", - "url": "https://repo1.maven.org/maven2/com/google/api/gax-grpc/1.60.0/gax-grpc-1.60.0.jar" + "sha256": "2348e2fe7c657dc5a02f34a5e6a6455f69bae56928219f92cf00b33bf2077497", + "url": "https://repo1.maven.org/maven2/com/google/api/gax-grpc/2.5.0/gax-grpc-2.5.0.jar" }, { - "coord": "com.google.api:gax-grpc:jar:sources:1.60.0", + "coord": "com.google.api:gax-grpc:jar:sources:2.5.0", "dependencies": [ - "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.api:gax:jar:sources:1.60.0", - "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-grpclb:jar:sources:1.33.1", - "commons-logging:commons-logging:jar:sources:1.2", - "com.google.api:api-common:jar:sources:1.10.1", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "com.google.code.gson:gson:jar:sources:2.8.6", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "org.apache.commons:commons-lang3:jar:sources:3.5", - "io.grpc:grpc-netty-shaded:jar:sources:1.33.1", - "io.grpc:grpc-alts:jar:sources:1.33.1", - "commons-codec:commons-codec:jar:sources:1.11", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", - "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "org.threeten:threetenbp:jar:sources:1.5.0" + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "com.google.api:gax:jar:sources:2.5.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.api:api-common:jar:sources:2.0.2", + "org.threeten:threetenbp:jar:sources:1.5.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0" ], "directDependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.api:gax:jar:sources:1.60.0", - "com.google.api:api-common:jar:sources:1.10.1", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "io.grpc:grpc-netty-shaded:jar:sources:1.33.1", - "io.grpc:grpc-alts:jar:sources:1.33.1", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "org.threeten:threetenbp:jar:sources:1.5.0" + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "com.google.api:gax:jar:sources:2.5.0", + "com.google.api:api-common:jar:sources:2.0.2", + "org.threeten:threetenbp:jar:sources:1.5.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/gax-grpc/1.60.0/gax-grpc-1.60.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/gax-grpc/2.5.0/gax-grpc-2.5.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/gax-grpc/1.60.0/gax-grpc-1.60.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/gax-grpc/1.60.0/gax-grpc-1.60.0-sources.jar", - "https://jcenter.bintray.com/com/google/api/gax-grpc/1.60.0/gax-grpc-1.60.0-sources.jar", - "https://maven.google.com/com/google/api/gax-grpc/1.60.0/gax-grpc-1.60.0-sources.jar" + "https://repo1.maven.org/maven2/com/google/api/gax-grpc/2.5.0/gax-grpc-2.5.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/gax-grpc/2.5.0/gax-grpc-2.5.0-sources.jar", + "https://jcenter.bintray.com/com/google/api/gax-grpc/2.5.0/gax-grpc-2.5.0-sources.jar", + "https://maven.google.com/com/google/api/gax-grpc/2.5.0/gax-grpc-2.5.0-sources.jar" ], - "sha256": "14d743332adc81f3ef660128a40d667d44ef4557e79abeb17b7869000157d85e", - "url": "https://repo1.maven.org/maven2/com/google/api/gax-grpc/1.60.0/gax-grpc-1.60.0-sources.jar" + "sha256": "0a1e97ad1019ff48c4d4201c8ccb30975ea85fbfcb773fd669c43f1260f78de4", + "url": "https://repo1.maven.org/maven2/com/google/api/gax-grpc/2.5.0/gax-grpc-2.5.0-sources.jar" }, { - "coord": "com.google.api:gax-httpjson:0.77.0", + "coord": "com.google.api:gax-httpjson:0.83.0", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", + "com.google.http-client:google-http-client-gson", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "com.google.code.findbugs:jsr305", "io.grpc:grpc-protobuf-lite", @@ -1763,6 +2636,7 @@ "com.google.auth:google-auth-library-oauth2-http", "com.google.protobuf:protobuf-java-util", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "com.google.protobuf:protobuf-java", "io.grpc:grpc-protobuf", @@ -1771,29 +2645,34 @@ "com.google.common.html.types:types", "com.google.code.gson:gson", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", "com.google.auth:google-auth-library-credentials", + "com.google.api:gax-grpc", "com.google.http-client:google-http-client" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/gax-httpjson/0.77.0/gax-httpjson-0.77.0.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/gax-httpjson/0.83.0/gax-httpjson-0.83.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/gax-httpjson/0.77.0/gax-httpjson-0.77.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/gax-httpjson/0.77.0/gax-httpjson-0.77.0.jar", - "https://jcenter.bintray.com/com/google/api/gax-httpjson/0.77.0/gax-httpjson-0.77.0.jar", - "https://maven.google.com/com/google/api/gax-httpjson/0.77.0/gax-httpjson-0.77.0.jar" + "https://repo1.maven.org/maven2/com/google/api/gax-httpjson/0.83.0/gax-httpjson-0.83.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/gax-httpjson/0.83.0/gax-httpjson-0.83.0.jar", + "https://jcenter.bintray.com/com/google/api/gax-httpjson/0.83.0/gax-httpjson-0.83.0.jar", + "https://maven.google.com/com/google/api/gax-httpjson/0.83.0/gax-httpjson-0.83.0.jar" ], - "sha256": "fd4dae47fa016d3b26e8d90b67ddc6c23c4c06e8bcdf085c70310ab7ef324bd6", - "url": "https://repo1.maven.org/maven2/com/google/api/gax-httpjson/0.77.0/gax-httpjson-0.77.0.jar" + "sha256": "b351f88d861b5e0364713dcd1ff9e825967a2a66d4660f454a57c94b6253ae3d", + "url": "https://repo1.maven.org/maven2/com/google/api/gax-httpjson/0.83.0/gax-httpjson-0.83.0.jar" }, { - "coord": "com.google.api:gax-httpjson:jar:sources:0.77.0", + "coord": "com.google.api:gax-httpjson:jar:sources:0.83.0", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", + "com.google.http-client:google-http-client-gson", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "com.google.code.findbugs:jsr305", "io.grpc:grpc-protobuf-lite", @@ -1802,6 +2681,7 @@ "com.google.auth:google-auth-library-oauth2-http", "com.google.protobuf:protobuf-java-util", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "com.google.protobuf:protobuf-java", "io.grpc:grpc-protobuf", @@ -1810,1767 +2690,2179 @@ "com.google.common.html.types:types", "com.google.code.gson:gson", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", "com.google.auth:google-auth-library-credentials", + "com.google.api:gax-grpc", "com.google.http-client:google-http-client" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/gax-httpjson/0.77.0/gax-httpjson-0.77.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/gax-httpjson/0.83.0/gax-httpjson-0.83.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/gax-httpjson/0.77.0/gax-httpjson-0.77.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/gax-httpjson/0.77.0/gax-httpjson-0.77.0-sources.jar", - "https://jcenter.bintray.com/com/google/api/gax-httpjson/0.77.0/gax-httpjson-0.77.0-sources.jar", - "https://maven.google.com/com/google/api/gax-httpjson/0.77.0/gax-httpjson-0.77.0-sources.jar" + "https://repo1.maven.org/maven2/com/google/api/gax-httpjson/0.83.0/gax-httpjson-0.83.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/gax-httpjson/0.83.0/gax-httpjson-0.83.0-sources.jar", + "https://jcenter.bintray.com/com/google/api/gax-httpjson/0.83.0/gax-httpjson-0.83.0-sources.jar", + "https://maven.google.com/com/google/api/gax-httpjson/0.83.0/gax-httpjson-0.83.0-sources.jar" ], - "sha256": "519565c8cd1eea6cd04b2cafbfbfdc5d736b2f52d59ff57c7e538da455b74d51", - "url": "https://repo1.maven.org/maven2/com/google/api/gax-httpjson/0.77.0/gax-httpjson-0.77.0-sources.jar" + "sha256": "64b36827e7a84b5f276c90cce9280006d07777a6271bcdad6508d32dd514da45", + "url": "https://repo1.maven.org/maven2/com/google/api/gax-httpjson/0.83.0/gax-httpjson-0.83.0-sources.jar" }, { - "coord": "com.google.api:gax:1.60.0", + "coord": "com.google.api:gax:2.5.0", "dependencies": [ - "com.google.j2objc:j2objc-annotations:1.3", - "commons-logging:commons-logging:1.2", - "io.opencensus:opencensus-contrib-http-util:0.24.0", + "com.google.api:api-common:2.0.2", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "com.google.api:api-common:1.10.1", - "commons-codec:commons-codec:1.11", - "io.opencensus:opencensus-api:0.24.0", - "org.threeten:threetenbp:1.5.0", - "org.apache.httpcomponents:httpcore:4.4.13", - "com.google.auto.value:auto-value-annotations:1.7.4", - "javax.annotation:javax.annotation-api:1.3.2", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", - "org.apache.httpcomponents:httpclient:4.5.13", - "com.google.http-client:google-http-client:1.37.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "org.threeten:threetenbp:1.5.1", + "io.opencensus:opencensus-api:0.28.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0" ], "directDependencies": [ + "com.google.api:api-common:2.0.2", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "com.google.api:api-common:1.10.1", - "io.opencensus:opencensus-api:0.24.0", - "org.threeten:threetenbp:1.5.0" + "org.threeten:threetenbp:1.5.1", + "io.opencensus:opencensus-api:0.28.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/gax/1.60.0/gax-1.60.0.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/gax/2.5.0/gax-2.5.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/gax/1.60.0/gax-1.60.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/gax/1.60.0/gax-1.60.0.jar", - "https://jcenter.bintray.com/com/google/api/gax/1.60.0/gax-1.60.0.jar", - "https://maven.google.com/com/google/api/gax/1.60.0/gax-1.60.0.jar" + "https://repo1.maven.org/maven2/com/google/api/gax/2.5.0/gax-2.5.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/gax/2.5.0/gax-2.5.0.jar", + "https://jcenter.bintray.com/com/google/api/gax/2.5.0/gax-2.5.0.jar", + "https://maven.google.com/com/google/api/gax/2.5.0/gax-2.5.0.jar" ], - "sha256": "02f37d4ff1a7b8d71dff8064cf9568aa4f4b61bcc4485085d16130f32afa5a79", - "url": "https://repo1.maven.org/maven2/com/google/api/gax/1.60.0/gax-1.60.0.jar" + "sha256": "cefcc4e5866061afafa41dc6c970236c77823ab1c4fca91208354f97165065af", + "url": "https://repo1.maven.org/maven2/com/google/api/gax/2.5.0/gax-2.5.0.jar" }, { - "coord": "com.google.api:gax:jar:sources:1.60.0", + "coord": "com.google.api:gax:jar:sources:2.5.0", "dependencies": [ - "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "commons-logging:commons-logging:jar:sources:1.2", - "com.google.api:api-common:jar:sources:1.10.1", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "commons-codec:commons-codec:jar:sources:1.11", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "org.threeten:threetenbp:jar:sources:1.5.0" + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.api:api-common:jar:sources:2.0.2", + "org.threeten:threetenbp:jar:sources:1.5.1" ], "directDependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.api:api-common:jar:sources:1.10.1", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "org.threeten:threetenbp:jar:sources:1.5.0" + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.api:api-common:jar:sources:2.0.2", + "org.threeten:threetenbp:jar:sources:1.5.1" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/api/gax/1.60.0/gax-1.60.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/api/gax/2.5.0/gax-2.5.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/api/gax/1.60.0/gax-1.60.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/gax/1.60.0/gax-1.60.0-sources.jar", - "https://jcenter.bintray.com/com/google/api/gax/1.60.0/gax-1.60.0-sources.jar", - "https://maven.google.com/com/google/api/gax/1.60.0/gax-1.60.0-sources.jar" + "https://repo1.maven.org/maven2/com/google/api/gax/2.5.0/gax-2.5.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/api/gax/2.5.0/gax-2.5.0-sources.jar", + "https://jcenter.bintray.com/com/google/api/gax/2.5.0/gax-2.5.0-sources.jar", + "https://maven.google.com/com/google/api/gax/2.5.0/gax-2.5.0-sources.jar" ], - "sha256": "8ce4c5fb69794702468875bc6cd9ad231079954a57d29b533952871a669bbfa3", - "url": "https://repo1.maven.org/maven2/com/google/api/gax/1.60.0/gax-1.60.0-sources.jar" + "sha256": "e47b49fd20df4b726201d675808a8197c68117eb5f16d1af5939040a38cbbae0", + "url": "https://repo1.maven.org/maven2/com/google/api/gax/2.5.0/gax-2.5.0-sources.jar" }, { - "coord": "com.google.apis:google-api-services-storage:jar:sources:v1-rev20200927-1.30.10", + "coord": "com.google.apis:google-api-services-storage:jar:sources:v1-rev20210127-1.32.1", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "com.google.api-client:google-api-client", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/apis/google-api-services-storage/v1-rev20200927-1.30.10/google-api-services-storage-v1-rev20200927-1.30.10-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/apis/google-api-services-storage/v1-rev20210127-1.32.1/google-api-services-storage-v1-rev20210127-1.32.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/apis/google-api-services-storage/v1-rev20200927-1.30.10/google-api-services-storage-v1-rev20200927-1.30.10-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/apis/google-api-services-storage/v1-rev20200927-1.30.10/google-api-services-storage-v1-rev20200927-1.30.10-sources.jar", - "https://jcenter.bintray.com/com/google/apis/google-api-services-storage/v1-rev20200927-1.30.10/google-api-services-storage-v1-rev20200927-1.30.10-sources.jar", - "https://maven.google.com/com/google/apis/google-api-services-storage/v1-rev20200927-1.30.10/google-api-services-storage-v1-rev20200927-1.30.10-sources.jar" + "https://repo1.maven.org/maven2/com/google/apis/google-api-services-storage/v1-rev20210127-1.32.1/google-api-services-storage-v1-rev20210127-1.32.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/apis/google-api-services-storage/v1-rev20210127-1.32.1/google-api-services-storage-v1-rev20210127-1.32.1-sources.jar", + "https://jcenter.bintray.com/com/google/apis/google-api-services-storage/v1-rev20210127-1.32.1/google-api-services-storage-v1-rev20210127-1.32.1-sources.jar", + "https://maven.google.com/com/google/apis/google-api-services-storage/v1-rev20210127-1.32.1/google-api-services-storage-v1-rev20210127-1.32.1-sources.jar" ], - "sha256": "59490865b8534ad4859aa0a8ba2c4f7f2bd9b31a2a398a8e8389b368bd0a7e65", - "url": "https://repo1.maven.org/maven2/com/google/apis/google-api-services-storage/v1-rev20200927-1.30.10/google-api-services-storage-v1-rev20200927-1.30.10-sources.jar" + "sha256": "bda3ee1c5de698897ac0deaf4a769ec43ce67a54c69ab315829b4f3209d9a215", + "url": "https://repo1.maven.org/maven2/com/google/apis/google-api-services-storage/v1-rev20210127-1.32.1/google-api-services-storage-v1-rev20210127-1.32.1-sources.jar" }, { - "coord": "com.google.apis:google-api-services-storage:v1-rev20200927-1.30.10", + "coord": "com.google.apis:google-api-services-storage:v1-rev20210127-1.32.1", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "com.google.api-client:google-api-client", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/apis/google-api-services-storage/v1-rev20210127-1.32.1/google-api-services-storage-v1-rev20210127-1.32.1.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/apis/google-api-services-storage/v1-rev20210127-1.32.1/google-api-services-storage-v1-rev20210127-1.32.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/apis/google-api-services-storage/v1-rev20210127-1.32.1/google-api-services-storage-v1-rev20210127-1.32.1.jar", + "https://jcenter.bintray.com/com/google/apis/google-api-services-storage/v1-rev20210127-1.32.1/google-api-services-storage-v1-rev20210127-1.32.1.jar", + "https://maven.google.com/com/google/apis/google-api-services-storage/v1-rev20210127-1.32.1/google-api-services-storage-v1-rev20210127-1.32.1.jar" + ], + "sha256": "c23beb05bb842abed14c8fd75a3e6b7a2f51b0d3ece1903a9c5fd3ab770ad93e", + "url": "https://repo1.maven.org/maven2/com/google/apis/google-api-services-storage/v1-rev20210127-1.32.1/google-api-services-storage-v1-rev20210127-1.32.1.jar" + }, + { + "coord": "com.google.auth:google-auth-library-credentials:1.1.0", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/auth/google-auth-library-credentials/1.1.0/google-auth-library-credentials-1.1.0.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/auth/google-auth-library-credentials/1.1.0/google-auth-library-credentials-1.1.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/auth/google-auth-library-credentials/1.1.0/google-auth-library-credentials-1.1.0.jar", + "https://jcenter.bintray.com/com/google/auth/google-auth-library-credentials/1.1.0/google-auth-library-credentials-1.1.0.jar", + "https://maven.google.com/com/google/auth/google-auth-library-credentials/1.1.0/google-auth-library-credentials-1.1.0.jar" + ], + "sha256": "6e92f773bf4431d6ab51b5419e116b0dd31a901677e4ff96dcce48f4f2b76778", + "url": "https://repo1.maven.org/maven2/com/google/auth/google-auth-library-credentials/1.1.0/google-auth-library-credentials-1.1.0.jar" + }, + { + "coord": "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/auth/google-auth-library-credentials/1.1.0/google-auth-library-credentials-1.1.0-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/auth/google-auth-library-credentials/1.1.0/google-auth-library-credentials-1.1.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/auth/google-auth-library-credentials/1.1.0/google-auth-library-credentials-1.1.0-sources.jar", + "https://jcenter.bintray.com/com/google/auth/google-auth-library-credentials/1.1.0/google-auth-library-credentials-1.1.0-sources.jar", + "https://maven.google.com/com/google/auth/google-auth-library-credentials/1.1.0/google-auth-library-credentials-1.1.0-sources.jar" + ], + "sha256": "5249b6a00c89131ccfa36961983f95517abcd5c515236f24f5f311c064f4e9b7", + "url": "https://repo1.maven.org/maven2/com/google/auth/google-auth-library-credentials/1.1.0/google-auth-library-credentials-1.1.0-sources.jar" + }, + { + "coord": "com.google.auth:google-auth-library-oauth2-http:1.1.0", + "dependencies": [ + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.http-client:google-http-client-gson:1.40.0", + "com.google.http-client:google-http-client:1.40.0" + ], + "directDependencies": [ + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.http-client:google-http-client-gson:1.40.0", + "com.google.http-client:google-http-client:1.40.0" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/auth/google-auth-library-oauth2-http/1.1.0/google-auth-library-oauth2-http-1.1.0.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/auth/google-auth-library-oauth2-http/1.1.0/google-auth-library-oauth2-http-1.1.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/auth/google-auth-library-oauth2-http/1.1.0/google-auth-library-oauth2-http-1.1.0.jar", + "https://jcenter.bintray.com/com/google/auth/google-auth-library-oauth2-http/1.1.0/google-auth-library-oauth2-http-1.1.0.jar", + "https://maven.google.com/com/google/auth/google-auth-library-oauth2-http/1.1.0/google-auth-library-oauth2-http-1.1.0.jar" + ], + "sha256": "c23ebfe3ee67534143f9e2509526d4eefbb806d4ce5180754a6c721fa941c186", + "url": "https://repo1.maven.org/maven2/com/google/auth/google-auth-library-oauth2-http/1.1.0/google-auth-library-oauth2-http-1.1.0.jar" + }, + { + "coord": "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "dependencies": [ + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2" + ], + "directDependencies": [ + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/auth/google-auth-library-oauth2-http/1.1.0/google-auth-library-oauth2-http-1.1.0-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/auth/google-auth-library-oauth2-http/1.1.0/google-auth-library-oauth2-http-1.1.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/auth/google-auth-library-oauth2-http/1.1.0/google-auth-library-oauth2-http-1.1.0-sources.jar", + "https://jcenter.bintray.com/com/google/auth/google-auth-library-oauth2-http/1.1.0/google-auth-library-oauth2-http-1.1.0-sources.jar", + "https://maven.google.com/com/google/auth/google-auth-library-oauth2-http/1.1.0/google-auth-library-oauth2-http-1.1.0-sources.jar" + ], + "sha256": "c46591055036cd15e5ead6608a11a6ba57a1def939f1c30e01c73a6cef00258d", + "url": "https://repo1.maven.org/maven2/com/google/auth/google-auth-library-oauth2-http/1.1.0/google-auth-library-oauth2-http-1.1.0-sources.jar" + }, + { + "coord": "com.google.auto.value:auto-value-annotations:1.8.2", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/auto/value/auto-value-annotations/1.8.2/auto-value-annotations-1.8.2.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/auto/value/auto-value-annotations/1.8.2/auto-value-annotations-1.8.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/auto/value/auto-value-annotations/1.8.2/auto-value-annotations-1.8.2.jar", + "https://jcenter.bintray.com/com/google/auto/value/auto-value-annotations/1.8.2/auto-value-annotations-1.8.2.jar", + "https://maven.google.com/com/google/auto/value/auto-value-annotations/1.8.2/auto-value-annotations-1.8.2.jar" + ], + "sha256": "392a848fc611fff5fd317c2453a9a1bf7775e4f4de9c5af0d688bff57f691f6e", + "url": "https://repo1.maven.org/maven2/com/google/auto/value/auto-value-annotations/1.8.2/auto-value-annotations-1.8.2.jar" + }, + { + "coord": "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/apis/google-api-services-storage/v1-rev20200927-1.30.10/google-api-services-storage-v1-rev20200927-1.30.10.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/auto/value/auto-value-annotations/1.8.2/auto-value-annotations-1.8.2-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/apis/google-api-services-storage/v1-rev20200927-1.30.10/google-api-services-storage-v1-rev20200927-1.30.10.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/apis/google-api-services-storage/v1-rev20200927-1.30.10/google-api-services-storage-v1-rev20200927-1.30.10.jar", - "https://jcenter.bintray.com/com/google/apis/google-api-services-storage/v1-rev20200927-1.30.10/google-api-services-storage-v1-rev20200927-1.30.10.jar", - "https://maven.google.com/com/google/apis/google-api-services-storage/v1-rev20200927-1.30.10/google-api-services-storage-v1-rev20200927-1.30.10.jar" + "https://repo1.maven.org/maven2/com/google/auto/value/auto-value-annotations/1.8.2/auto-value-annotations-1.8.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/auto/value/auto-value-annotations/1.8.2/auto-value-annotations-1.8.2-sources.jar", + "https://jcenter.bintray.com/com/google/auto/value/auto-value-annotations/1.8.2/auto-value-annotations-1.8.2-sources.jar", + "https://maven.google.com/com/google/auto/value/auto-value-annotations/1.8.2/auto-value-annotations-1.8.2-sources.jar" ], - "sha256": "52d26a9d105f8d8a0850807285f307a76cea8f3e0cdb2be4d3b15b1adfa77351", - "url": "https://repo1.maven.org/maven2/com/google/apis/google-api-services-storage/v1-rev20200927-1.30.10/google-api-services-storage-v1-rev20200927-1.30.10.jar" + "sha256": "2c55e2e20d635a978411f0b9d0c990a85b57c0076429182ee5f4ac43880b7d98", + "url": "https://repo1.maven.org/maven2/com/google/auto/value/auto-value-annotations/1.8.2/auto-value-annotations-1.8.2-sources.jar" }, { - "coord": "com.google.auth:google-auth-library-credentials:0.22.0", + "coord": "com.google.auto.value:auto-value:1.8.1", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/auth/google-auth-library-credentials/0.22.0/google-auth-library-credentials-0.22.0.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/auto/value/auto-value/1.8.1/auto-value-1.8.1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/auth/google-auth-library-credentials/0.22.0/google-auth-library-credentials-0.22.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/auth/google-auth-library-credentials/0.22.0/google-auth-library-credentials-0.22.0.jar", - "https://jcenter.bintray.com/com/google/auth/google-auth-library-credentials/0.22.0/google-auth-library-credentials-0.22.0.jar", - "https://maven.google.com/com/google/auth/google-auth-library-credentials/0.22.0/google-auth-library-credentials-0.22.0.jar" + "https://repo1.maven.org/maven2/com/google/auto/value/auto-value/1.8.1/auto-value-1.8.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/auto/value/auto-value/1.8.1/auto-value-1.8.1.jar", + "https://jcenter.bintray.com/com/google/auto/value/auto-value/1.8.1/auto-value-1.8.1.jar", + "https://maven.google.com/com/google/auto/value/auto-value/1.8.1/auto-value-1.8.1.jar" ], - "sha256": "42c76031276de5b520909e9faf88c5b3c9a722d69ee9cfdafedb1c52c355dfc5", - "url": "https://repo1.maven.org/maven2/com/google/auth/google-auth-library-credentials/0.22.0/google-auth-library-credentials-0.22.0.jar" + "sha256": "1cf97f6ec527f31fa2fde0ac97174116f11bbe65e11f118c1ed50fa74634f115", + "url": "https://repo1.maven.org/maven2/com/google/auto/value/auto-value/1.8.1/auto-value-1.8.1.jar" }, { - "coord": "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", + "coord": "com.google.auto.value:auto-value:jar:sources:1.8.1", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/auth/google-auth-library-credentials/0.22.0/google-auth-library-credentials-0.22.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/auto/value/auto-value/1.8.1/auto-value-1.8.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/auth/google-auth-library-credentials/0.22.0/google-auth-library-credentials-0.22.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/auth/google-auth-library-credentials/0.22.0/google-auth-library-credentials-0.22.0-sources.jar", - "https://jcenter.bintray.com/com/google/auth/google-auth-library-credentials/0.22.0/google-auth-library-credentials-0.22.0-sources.jar", - "https://maven.google.com/com/google/auth/google-auth-library-credentials/0.22.0/google-auth-library-credentials-0.22.0-sources.jar" + "https://repo1.maven.org/maven2/com/google/auto/value/auto-value/1.8.1/auto-value-1.8.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/auto/value/auto-value/1.8.1/auto-value-1.8.1-sources.jar", + "https://jcenter.bintray.com/com/google/auto/value/auto-value/1.8.1/auto-value-1.8.1-sources.jar", + "https://maven.google.com/com/google/auto/value/auto-value/1.8.1/auto-value-1.8.1-sources.jar" ], - "sha256": "ed812f3910c89f1bd20c7a2dc654039c0155c1b35928fced9ccef4f560fbf298", - "url": "https://repo1.maven.org/maven2/com/google/auth/google-auth-library-credentials/0.22.0/google-auth-library-credentials-0.22.0-sources.jar" + "sha256": "2613f221eb252811ecb6497c62d3fd68f4f275c8452968c50b51c11f471633a5", + "url": "https://repo1.maven.org/maven2/com/google/auto/value/auto-value/1.8.1/auto-value-1.8.1-sources.jar" }, { - "coord": "com.google.auth:google-auth-library-oauth2-http:0.22.0", + "coord": "com.google.cloud:google-cloud-core-grpc:2.1.6", "dependencies": [ + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", "commons-logging:commons-logging:1.2", - "io.opencensus:opencensus-contrib-http-util:0.24.0", "com.google.code.findbugs:jsr305:3.0.2", - "commons-codec:commons-codec:1.11", - "io.opencensus:opencensus-api:0.24.0", - "org.apache.httpcomponents:httpcore:4.4.13", - "com.google.auto.value:auto-value-annotations:1.7.4", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", + "org.apache.httpcomponents:httpcore:4.4.14", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.cloud:google-cloud-core:2.1.6", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.http-client:google-http-client-gson:1.40.0", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "commons-codec:commons-codec:1.15", + "com.google.api:gax:2.5.0", + "javax.annotation:javax.annotation-api:1.3.2", + "org.threeten:threetenbp:1.5.1", "org.apache.httpcomponents:httpclient:4.5.13", - "com.google.http-client:google-http-client:1.37.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "io.opencensus:opencensus-contrib-http-util:0.28.0", + "com.google.code.gson:gson:2.8.8", + "io.opencensus:opencensus-api:0.28.0", + "com.google.http-client:google-http-client:1.40.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0", + "com.google.api.grpc:proto-google-iam-v1:1.1.2" ], "directDependencies": [ - "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auto.value:auto-value-annotations:1.7.4", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", - "com.google.http-client:google-http-client:1.37.0" + "com.google.api:api-common:2.0.2", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.cloud:google-cloud-core:2.1.6", + "com.google.api:gax:2.5.0", + "com.google.http-client:google-http-client:1.40.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/auth/google-auth-library-oauth2-http/0.22.0/google-auth-library-oauth2-http-0.22.0.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-core-grpc/2.1.6/google-cloud-core-grpc-2.1.6.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/auth/google-auth-library-oauth2-http/0.22.0/google-auth-library-oauth2-http-0.22.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/auth/google-auth-library-oauth2-http/0.22.0/google-auth-library-oauth2-http-0.22.0.jar", - "https://jcenter.bintray.com/com/google/auth/google-auth-library-oauth2-http/0.22.0/google-auth-library-oauth2-http-0.22.0.jar", - "https://maven.google.com/com/google/auth/google-auth-library-oauth2-http/0.22.0/google-auth-library-oauth2-http-0.22.0.jar" + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core-grpc/2.1.6/google-cloud-core-grpc-2.1.6.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-core-grpc/2.1.6/google-cloud-core-grpc-2.1.6.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-core-grpc/2.1.6/google-cloud-core-grpc-2.1.6.jar", + "https://maven.google.com/com/google/cloud/google-cloud-core-grpc/2.1.6/google-cloud-core-grpc-2.1.6.jar" ], - "sha256": "1722d895c42dc42ea1d1f392ddbec1fbb28f7a979022c3a6c29acc39cc777ad1", - "url": "https://repo1.maven.org/maven2/com/google/auth/google-auth-library-oauth2-http/0.22.0/google-auth-library-oauth2-http-0.22.0.jar" + "sha256": "6088d847eb1a7ac8b50f127f26561cb7a70149ee36af2aa0be9403e4f0922d11", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core-grpc/2.1.6/google-cloud-core-grpc-2.1.6.jar" }, { - "coord": "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", + "coord": "com.google.cloud:google-cloud-core-grpc:jar:sources:2.1.6", "dependencies": [ + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", + "com.google.code.gson:gson:jar:sources:2.8.8", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "commons-codec:commons-codec:jar:sources:1.15", "commons-logging:commons-logging:jar:sources:1.2", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "commons-codec:commons-codec:jar:sources:1.11", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4" + "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.1.2", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.api:gax:jar:sources:2.5.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.api:api-common:jar:sources:2.0.2", + "org.threeten:threetenbp:jar:sources:1.5.1", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.cloud:google-cloud-core:jar:sources:2.1.6", + "org.apache.httpcomponents:httpcore:jar:sources:4.4.14", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4" + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.api:gax:jar:sources:2.5.0", + "com.google.api:api-common:jar:sources:2.0.2", + "com.google.cloud:google-cloud-core:jar:sources:2.1.6" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/auth/google-auth-library-oauth2-http/0.22.0/google-auth-library-oauth2-http-0.22.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-core-grpc/2.1.6/google-cloud-core-grpc-2.1.6-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/auth/google-auth-library-oauth2-http/0.22.0/google-auth-library-oauth2-http-0.22.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/auth/google-auth-library-oauth2-http/0.22.0/google-auth-library-oauth2-http-0.22.0-sources.jar", - "https://jcenter.bintray.com/com/google/auth/google-auth-library-oauth2-http/0.22.0/google-auth-library-oauth2-http-0.22.0-sources.jar", - "https://maven.google.com/com/google/auth/google-auth-library-oauth2-http/0.22.0/google-auth-library-oauth2-http-0.22.0-sources.jar" + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core-grpc/2.1.6/google-cloud-core-grpc-2.1.6-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-core-grpc/2.1.6/google-cloud-core-grpc-2.1.6-sources.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-core-grpc/2.1.6/google-cloud-core-grpc-2.1.6-sources.jar", + "https://maven.google.com/com/google/cloud/google-cloud-core-grpc/2.1.6/google-cloud-core-grpc-2.1.6-sources.jar" ], - "sha256": "aa843bae54377e8f7fb51f639e32acf10172229c23f97780f55a20e542802535", - "url": "https://repo1.maven.org/maven2/com/google/auth/google-auth-library-oauth2-http/0.22.0/google-auth-library-oauth2-http-0.22.0-sources.jar" + "sha256": "bf447fe76776be28559d7d31db21c5b705191b9fc4b774c76c1047bfe286c4d6", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core-grpc/2.1.6/google-cloud-core-grpc-2.1.6-sources.jar" }, { - "coord": "com.google.auto.value:auto-value-annotations:1.7.4", + "coord": "com.google.cloud:google-cloud-core-http:1.95.4", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", + "io.opencensus:opencensus-api", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "com.google.cloud:google-cloud-core", "io.grpc:grpc-okhttp", + "com.google.code.findbugs:jsr305", "io.grpc:grpc-protobuf-lite", + "com.google.api:gax", + "com.google.auth:google-auth-library-oauth2-http", + "com.google.http-client:google-http-client-appengine", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", + "com.google.api-client:google-api-client", "io.grpc:grpc-stub", + "com.google.api:api-common", + "com.google.api:gax-httpjson", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.auth:google-auth-library-credentials", + "com.google.api:gax-grpc", + "io.opencensus:opencensus-contrib-http-util", + "com.google.http-client:google-http-client" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/auto/value/auto-value-annotations/1.7.4/auto-value-annotations-1.7.4.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-core-http/1.95.4/google-cloud-core-http-1.95.4.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/auto/value/auto-value-annotations/1.7.4/auto-value-annotations-1.7.4.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/auto/value/auto-value-annotations/1.7.4/auto-value-annotations-1.7.4.jar", - "https://jcenter.bintray.com/com/google/auto/value/auto-value-annotations/1.7.4/auto-value-annotations-1.7.4.jar", - "https://maven.google.com/com/google/auto/value/auto-value-annotations/1.7.4/auto-value-annotations-1.7.4.jar" + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core-http/1.95.4/google-cloud-core-http-1.95.4.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-core-http/1.95.4/google-cloud-core-http-1.95.4.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-core-http/1.95.4/google-cloud-core-http-1.95.4.jar", + "https://maven.google.com/com/google/cloud/google-cloud-core-http/1.95.4/google-cloud-core-http-1.95.4.jar" ], - "sha256": "fedd59b0b4986c342f6ab2d182f2a4ee9fceb2c7e2d5bdc4dc764c92394a23d3", - "url": "https://repo1.maven.org/maven2/com/google/auto/value/auto-value-annotations/1.7.4/auto-value-annotations-1.7.4.jar" + "sha256": "9cfa5ff2791adb654f00b68a8d08513f4adfc242fb46ceb503dd5bcc341f9ca7", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core-http/1.95.4/google-cloud-core-http-1.95.4.jar" }, { - "coord": "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "coord": "com.google.cloud:google-cloud-core-http:jar:sources:1.95.4", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", + "io.opencensus:opencensus-api", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "com.google.cloud:google-cloud-core", "io.grpc:grpc-okhttp", + "com.google.code.findbugs:jsr305", "io.grpc:grpc-protobuf-lite", + "com.google.api:gax", + "com.google.auth:google-auth-library-oauth2-http", + "com.google.http-client:google-http-client-appengine", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", + "com.google.api-client:google-api-client", "io.grpc:grpc-stub", + "com.google.api:api-common", + "com.google.api:gax-httpjson", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.auth:google-auth-library-credentials", + "com.google.api:gax-grpc", + "io.opencensus:opencensus-contrib-http-util", + "com.google.http-client:google-http-client" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/auto/value/auto-value-annotations/1.7.4/auto-value-annotations-1.7.4-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-core-http/1.95.4/google-cloud-core-http-1.95.4-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/auto/value/auto-value-annotations/1.7.4/auto-value-annotations-1.7.4-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/auto/value/auto-value-annotations/1.7.4/auto-value-annotations-1.7.4-sources.jar", - "https://jcenter.bintray.com/com/google/auto/value/auto-value-annotations/1.7.4/auto-value-annotations-1.7.4-sources.jar", - "https://maven.google.com/com/google/auto/value/auto-value-annotations/1.7.4/auto-value-annotations-1.7.4-sources.jar" + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core-http/1.95.4/google-cloud-core-http-1.95.4-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-core-http/1.95.4/google-cloud-core-http-1.95.4-sources.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-core-http/1.95.4/google-cloud-core-http-1.95.4-sources.jar", + "https://maven.google.com/com/google/cloud/google-cloud-core-http/1.95.4/google-cloud-core-http-1.95.4-sources.jar" ], - "sha256": "41dbbed2cc92e72fe61e622f715735b05db45dcc49a1276619781b6e19e7a91f", - "url": "https://repo1.maven.org/maven2/com/google/auto/value/auto-value-annotations/1.7.4/auto-value-annotations-1.7.4-sources.jar" + "sha256": "d84c2ad2d6ea041a297a859342cef5c485c1ab24edec91fa40b40d559384507b", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core-http/1.95.4/google-cloud-core-http-1.95.4-sources.jar" }, { - "coord": "com.google.cloud:google-cloud-core-grpc:1.93.10", + "coord": "com.google.cloud:google-cloud-core:2.1.6", "dependencies": [ - "com.google.api:gax-grpc:1.60.0", - "org.conscrypt:conscrypt-openjdk-uber:2.5.1", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", "commons-logging:commons-logging:1.2", - "io.opencensus:opencensus-contrib-http-util:0.24.0", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "com.google.api:api-common:1.10.1", - "com.google.cloud:google-cloud-core:1.93.10", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "commons-codec:commons-codec:1.11", - "io.opencensus:opencensus-api:0.24.0", - "com.google.code.gson:gson:2.8.6", - "org.threeten:threetenbp:1.5.0", - "io.grpc:grpc-alts:1.33.1", - "org.apache.httpcomponents:httpcore:4.4.13", - "com.google.auto.value:auto-value-annotations:1.7.4", - "com.google.api:gax:1.60.0", - "com.google.protobuf:protobuf-java-util:3.13.0", - "com.google.api.grpc:proto-google-iam-v1:1.0.2", - "io.grpc:grpc-netty-shaded:1.33.1", - "org.apache.commons:commons-lang3:3.5", + "org.apache.httpcomponents:httpcore:4.4.14", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.http-client:google-http-client-gson:1.40.0", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "commons-codec:commons-codec:1.15", + "com.google.api:gax:2.5.0", "javax.annotation:javax.annotation-api:1.3.2", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", - "com.google.protobuf:protobuf-java:3.13.0", + "org.threeten:threetenbp:1.5.1", "org.apache.httpcomponents:httpclient:4.5.13", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.http-client:google-http-client:1.37.0", - "io.grpc:grpc-grpclb:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "io.opencensus:opencensus-contrib-http-util:0.28.0", + "com.google.code.gson:gson:2.8.8", + "io.opencensus:opencensus-api:0.28.0", + "com.google.http-client:google-http-client:1.40.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0", + "com.google.api.grpc:proto-google-iam-v1:1.1.2" ], "directDependencies": [ - "com.google.api:gax-grpc:1.60.0", - "com.google.api:api-common:1.10.1", - "com.google.cloud:google-cloud-core:1.93.10", - "com.google.api:gax:1.60.0", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client:1.37.0" - ], - "exclusions": [ + "com.google.api:api-common:2.0.2", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.http-client:google-http-client-gson:1.40.0", + "com.google.protobuf:protobuf-java:3.17.3", + "com.google.api:gax:2.5.0", + "org.threeten:threetenbp:1.5.1", + "com.google.http-client:google-http-client:1.40.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0", + "com.google.api.grpc:proto-google-iam-v1:1.1.2" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-core-grpc/1.93.10/google-cloud-core-grpc-1.93.10.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-core/2.1.6/google-cloud-core-2.1.6.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core-grpc/1.93.10/google-cloud-core-grpc-1.93.10.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-core-grpc/1.93.10/google-cloud-core-grpc-1.93.10.jar", - "https://jcenter.bintray.com/com/google/cloud/google-cloud-core-grpc/1.93.10/google-cloud-core-grpc-1.93.10.jar", - "https://maven.google.com/com/google/cloud/google-cloud-core-grpc/1.93.10/google-cloud-core-grpc-1.93.10.jar" + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core/2.1.6/google-cloud-core-2.1.6.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-core/2.1.6/google-cloud-core-2.1.6.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-core/2.1.6/google-cloud-core-2.1.6.jar", + "https://maven.google.com/com/google/cloud/google-cloud-core/2.1.6/google-cloud-core-2.1.6.jar" ], - "sha256": "44ce5dca307449f66a479b9c8b26f7f936665912bd963531269e324c60f1939d", - "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core-grpc/1.93.10/google-cloud-core-grpc-1.93.10.jar" + "sha256": "f9a2c1f1896a8cdd7f865ae94a8ee6a0b416a229ac87164d202a8af6d3f684fa", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core/2.1.6/google-cloud-core-2.1.6.jar" }, { - "coord": "com.google.cloud:google-cloud-core-grpc:jar:sources:1.93.10", + "coord": "com.google.cloud:google-cloud-core:jar:sources:2.1.6", "dependencies": [ + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.cloud:google-cloud-core:jar:sources:1.93.10", - "com.google.api:gax:jar:sources:1.60.0", + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", + "com.google.code.gson:gson:jar:sources:2.8.8", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-grpclb:jar:sources:1.33.1", + "commons-codec:commons-codec:jar:sources:1.15", "commons-logging:commons-logging:jar:sources:1.2", - "com.google.api:api-common:jar:sources:1.10.1", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "com.google.code.gson:gson:jar:sources:2.8.6", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "org.apache.commons:commons-lang3:jar:sources:3.5", - "io.grpc:grpc-netty-shaded:jar:sources:1.33.1", - "io.grpc:grpc-alts:jar:sources:1.33.1", - "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.0.2", - "com.google.api:gax-grpc:jar:sources:1.60.0", - "commons-codec:commons-codec:jar:sources:1.11", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", - "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "org.threeten:threetenbp:jar:sources:1.5.0" + "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.1.2", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.api:gax:jar:sources:2.5.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.api:api-common:jar:sources:2.0.2", + "org.threeten:threetenbp:jar:sources:1.5.1", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "org.apache.httpcomponents:httpcore:jar:sources:4.4.14", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "com.google.cloud:google-cloud-core:jar:sources:1.93.10", - "com.google.api:gax:jar:sources:1.60.0", - "com.google.api:api-common:jar:sources:1.10.1", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.api:gax-grpc:jar:sources:1.60.0", - "com.google.http-client:google-http-client:jar:sources:1.37.0" - ], - "exclusions": [ + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.1.2", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.api:gax:jar:sources:2.5.0", + "com.google.api:api-common:jar:sources:2.0.2", + "org.threeten:threetenbp:jar:sources:1.5.1", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-core-grpc/1.93.10/google-cloud-core-grpc-1.93.10-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-core/2.1.6/google-cloud-core-2.1.6-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core-grpc/1.93.10/google-cloud-core-grpc-1.93.10-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-core-grpc/1.93.10/google-cloud-core-grpc-1.93.10-sources.jar", - "https://jcenter.bintray.com/com/google/cloud/google-cloud-core-grpc/1.93.10/google-cloud-core-grpc-1.93.10-sources.jar", - "https://maven.google.com/com/google/cloud/google-cloud-core-grpc/1.93.10/google-cloud-core-grpc-1.93.10-sources.jar" + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core/2.1.6/google-cloud-core-2.1.6-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-core/2.1.6/google-cloud-core-2.1.6-sources.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-core/2.1.6/google-cloud-core-2.1.6-sources.jar", + "https://maven.google.com/com/google/cloud/google-cloud-core/2.1.6/google-cloud-core-2.1.6-sources.jar" ], - "sha256": "62334fd40d8b13dab44ebacf1e5a7d90649e63445ea9c9e8cc19348fa261958e", - "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core-grpc/1.93.10/google-cloud-core-grpc-1.93.10-sources.jar" + "sha256": "17ee67f5277887cfc031abc56942d900057cb2461e6f01740b1aaaa7bf26a71c", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core/2.1.6/google-cloud-core-2.1.6-sources.jar" }, { - "coord": "com.google.cloud:google-cloud-core-http:1.93.9", - "dependencies": [], - "directDependencies": [], + "coord": "com.google.cloud:google-cloud-firestore:2.6.2", + "dependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "org.conscrypt:conscrypt-openjdk-uber:2.5.1", + "io.opencensus:opencensus-contrib-grpc-util:0.28.0", + "com.google.api:api-common:2.0.2", + "com.google.j2objc:j2objc-annotations:1.3", + "commons-logging:commons-logging:1.2", + "com.google.cloud:proto-google-cloud-firestore-bundle-v1:2.6.2", + "com.google.code.findbugs:jsr305:3.0.2", + "org.apache.httpcomponents:httpcore:4.4.14", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.cloud:google-cloud-core-grpc:2.1.6", + "com.google.android:annotations:4.1.1.4", + "io.perfmark:perfmark-api:0.23.0", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.cloud:google-cloud-core:2.1.6", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.http-client:google-http-client-gson:1.40.0", + "com.google.api.grpc:proto-google-cloud-firestore-v1:2.6.2", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "commons-codec:commons-codec:1.15", + "com.google.api:gax:2.5.0", + "javax.annotation:javax.annotation-api:1.3.2", + "com.google.guava:failureaccess:1.0.1", + "org.threeten:threetenbp:1.5.1", + "org.apache.httpcomponents:httpclient:4.5.13", + "io.opencensus:opencensus-contrib-http-util:0.28.0", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", + "com.google.code.gson:gson:2.8.8", + "io.opencensus:opencensus-api:0.28.0", + "com.google.http-client:google-http-client:1.40.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0", + "com.google.api.grpc:proto-google-iam-v1:1.1.2", + "org.checkerframework:checker-compat-qual:2.5.5" + ], + "directDependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "org.conscrypt:conscrypt-openjdk-uber:2.5.1", + "io.opencensus:opencensus-contrib-grpc-util:0.28.0", + "com.google.api:api-common:2.0.2", + "com.google.j2objc:j2objc-annotations:1.3", + "commons-logging:commons-logging:1.2", + "com.google.cloud:proto-google-cloud-firestore-bundle-v1:2.6.2", + "com.google.code.findbugs:jsr305:3.0.2", + "org.apache.httpcomponents:httpcore:4.4.14", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.cloud:google-cloud-core-grpc:2.1.6", + "com.google.android:annotations:4.1.1.4", + "io.perfmark:perfmark-api:0.23.0", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.cloud:google-cloud-core:2.1.6", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.http-client:google-http-client-gson:1.40.0", + "com.google.api.grpc:proto-google-cloud-firestore-v1:2.6.2", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "commons-codec:commons-codec:1.15", + "com.google.api:gax:2.5.0", + "javax.annotation:javax.annotation-api:1.3.2", + "com.google.guava:failureaccess:1.0.1", + "org.threeten:threetenbp:1.5.1", + "org.apache.httpcomponents:httpclient:4.5.13", + "io.opencensus:opencensus-contrib-http-util:0.28.0", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", + "com.google.code.gson:gson:2.8.8", + "io.opencensus:opencensus-api:0.28.0", + "com.google.http-client:google-http-client:1.40.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0", + "com.google.api.grpc:proto-google-iam-v1:1.1.2", + "org.checkerframework:checker-compat-qual:2.5.5" + ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", - "io.opencensus:opencensus-api", "io.grpc:grpc-context", "io.grpc:grpc-services", - "com.google.cloud:google-cloud-core", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", - "com.google.code.findbugs:jsr305", "io.grpc:grpc-protobuf-lite", - "com.google.api:gax", - "com.google.auth:google-auth-library-oauth2-http", - "com.google.http-client:google-http-client-appengine", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", - "com.google.api-client:google-api-client", "io.grpc:grpc-stub", - "com.google.api:api-common", - "com.google.api:gax-httpjson", "com.google.common.html.types:types", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", - "com.google.auth:google-auth-library-credentials", - "io.opencensus:opencensus-contrib-http-util", - "com.google.http-client:google-http-client" + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-core-http/1.93.9/google-cloud-core-http-1.93.9.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-firestore/2.6.2/google-cloud-firestore-2.6.2.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core-http/1.93.9/google-cloud-core-http-1.93.9.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-core-http/1.93.9/google-cloud-core-http-1.93.9.jar", - "https://jcenter.bintray.com/com/google/cloud/google-cloud-core-http/1.93.9/google-cloud-core-http-1.93.9.jar", - "https://maven.google.com/com/google/cloud/google-cloud-core-http/1.93.9/google-cloud-core-http-1.93.9.jar" + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-firestore/2.6.2/google-cloud-firestore-2.6.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-firestore/2.6.2/google-cloud-firestore-2.6.2.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-firestore/2.6.2/google-cloud-firestore-2.6.2.jar", + "https://maven.google.com/com/google/cloud/google-cloud-firestore/2.6.2/google-cloud-firestore-2.6.2.jar" ], - "sha256": "4285f00574b5108761afbc572ce5f7241397d5445344363a5718bf740adf5090", - "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core-http/1.93.9/google-cloud-core-http-1.93.9.jar" + "sha256": "5bd86758b19ba92350a6e2cadc267deb1fbc1dcae3050a889684d99fc3a12a2e", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-firestore/2.6.2/google-cloud-firestore-2.6.2.jar" }, { - "coord": "com.google.cloud:google-cloud-core-http:jar:sources:1.93.9", - "dependencies": [], - "directDependencies": [], + "coord": "com.google.cloud:google-cloud-firestore:jar:sources:2.6.2", + "dependencies": [ + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", + "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", + "com.google.code.gson:gson:jar:sources:2.8.8", + "com.google.android:annotations:jar:sources:4.1.1.4", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "commons-codec:commons-codec:jar:sources:1.15", + "commons-logging:commons-logging:jar:sources:1.2", + "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.1.2", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "com.google.api.grpc:proto-google-cloud-firestore-v1:jar:sources:2.6.2", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.api:gax:jar:sources:2.5.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.cloud:google-cloud-core-grpc:jar:sources:2.1.6", + "com.google.api:api-common:jar:sources:2.0.2", + "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", + "org.threeten:threetenbp:jar:sources:1.5.1", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.cloud:google-cloud-core:jar:sources:2.1.6", + "com.google.cloud:proto-google-cloud-firestore-bundle-v1:jar:sources:2.6.2", + "org.apache.httpcomponents:httpcore:jar:sources:4.4.14", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.perfmark:perfmark-api:jar:sources:0.23.0", + "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2", + "io.opencensus:opencensus-contrib-grpc-util:jar:sources:0.28.0" + ], + "directDependencies": [ + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", + "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", + "com.google.code.gson:gson:jar:sources:2.8.8", + "com.google.android:annotations:jar:sources:4.1.1.4", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "commons-codec:commons-codec:jar:sources:1.15", + "commons-logging:commons-logging:jar:sources:1.2", + "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.1.2", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "com.google.api.grpc:proto-google-cloud-firestore-v1:jar:sources:2.6.2", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.api:gax:jar:sources:2.5.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.cloud:google-cloud-core-grpc:jar:sources:2.1.6", + "com.google.api:api-common:jar:sources:2.0.2", + "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", + "org.threeten:threetenbp:jar:sources:1.5.1", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.cloud:google-cloud-core:jar:sources:2.1.6", + "com.google.cloud:proto-google-cloud-firestore-bundle-v1:jar:sources:2.6.2", + "org.apache.httpcomponents:httpcore:jar:sources:4.4.14", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.perfmark:perfmark-api:jar:sources:0.23.0", + "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2", + "io.opencensus:opencensus-contrib-grpc-util:jar:sources:0.28.0" + ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", - "io.opencensus:opencensus-api", "io.grpc:grpc-context", "io.grpc:grpc-services", - "com.google.cloud:google-cloud-core", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", - "com.google.code.findbugs:jsr305", "io.grpc:grpc-protobuf-lite", - "com.google.api:gax", - "com.google.auth:google-auth-library-oauth2-http", - "com.google.http-client:google-http-client-appengine", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", - "com.google.api-client:google-api-client", "io.grpc:grpc-stub", - "com.google.api:api-common", - "com.google.api:gax-httpjson", "com.google.common.html.types:types", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", - "com.google.auth:google-auth-library-credentials", - "io.opencensus:opencensus-contrib-http-util", - "com.google.http-client:google-http-client" + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-core-http/1.93.9/google-cloud-core-http-1.93.9-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-firestore/2.6.2/google-cloud-firestore-2.6.2-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core-http/1.93.9/google-cloud-core-http-1.93.9-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-core-http/1.93.9/google-cloud-core-http-1.93.9-sources.jar", - "https://jcenter.bintray.com/com/google/cloud/google-cloud-core-http/1.93.9/google-cloud-core-http-1.93.9-sources.jar", - "https://maven.google.com/com/google/cloud/google-cloud-core-http/1.93.9/google-cloud-core-http-1.93.9-sources.jar" + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-firestore/2.6.2/google-cloud-firestore-2.6.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-firestore/2.6.2/google-cloud-firestore-2.6.2-sources.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-firestore/2.6.2/google-cloud-firestore-2.6.2-sources.jar", + "https://maven.google.com/com/google/cloud/google-cloud-firestore/2.6.2/google-cloud-firestore-2.6.2-sources.jar" ], - "sha256": "621323b40227782fe54abf0461e78c5b96bff24fb97f6c9dfcbb1d7495043a47", - "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core-http/1.93.9/google-cloud-core-http-1.93.9-sources.jar" + "sha256": "c91d6eb8825f3ea2d61023078cbbab1c48a16a85879595a9288697d03d384377", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-firestore/2.6.2/google-cloud-firestore-2.6.2-sources.jar" }, { - "coord": "com.google.cloud:google-cloud-core:1.93.10", + "coord": "com.google.cloud:google-cloud-monitoring:2.3.4", "dependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "org.conscrypt:conscrypt-openjdk-uber:2.5.1", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", "commons-logging:commons-logging:1.2", - "io.opencensus:opencensus-contrib-http-util:0.24.0", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "com.google.api:api-common:1.10.1", - "commons-codec:commons-codec:1.11", - "io.opencensus:opencensus-api:0.24.0", - "com.google.code.gson:gson:2.8.6", - "org.threeten:threetenbp:1.5.0", - "org.apache.httpcomponents:httpcore:4.4.13", - "com.google.auto.value:auto-value-annotations:1.7.4", - "com.google.api:gax:1.60.0", - "com.google.protobuf:protobuf-java-util:3.13.0", - "com.google.api.grpc:proto-google-iam-v1:1.0.2", + "org.apache.httpcomponents:httpcore:4.4.14", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.api.grpc:proto-google-cloud-monitoring-v3:2.3.4", + "com.google.android:annotations:4.1.1.4", + "io.perfmark:perfmark-api:0.23.0", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.http-client:google-http-client-gson:1.40.0", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "commons-codec:commons-codec:1.15", + "com.google.api:gax:2.5.0", "javax.annotation:javax.annotation-api:1.3.2", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", - "com.google.protobuf:protobuf-java:3.13.0", + "com.google.guava:failureaccess:1.0.1", + "org.threeten:threetenbp:1.5.1", "org.apache.httpcomponents:httpclient:4.5.13", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.http-client:google-http-client:1.37.0", - "com.google.errorprone:error_prone_annotations:2.4.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "io.opencensus:opencensus-contrib-http-util:0.28.0", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", + "com.google.code.gson:gson:2.8.8", + "io.opencensus:opencensus-api:0.28.0", + "com.google.http-client:google-http-client:1.40.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0", + "org.checkerframework:checker-compat-qual:2.5.5" ], "directDependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "org.conscrypt:conscrypt-openjdk-uber:2.5.1", + "com.google.api:api-common:2.0.2", + "com.google.j2objc:j2objc-annotations:1.3", + "commons-logging:commons-logging:1.2", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "com.google.api:api-common:1.10.1", - "org.threeten:threetenbp:1.5.0", - "com.google.auto.value:auto-value-annotations:1.7.4", - "com.google.api:gax:1.60.0", - "com.google.protobuf:protobuf-java-util:3.13.0", - "com.google.api.grpc:proto-google-iam-v1:1.0.2", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", - "com.google.protobuf:protobuf-java:3.13.0", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.http-client:google-http-client:1.37.0" + "org.apache.httpcomponents:httpcore:4.4.14", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.api.grpc:proto-google-cloud-monitoring-v3:2.3.4", + "com.google.android:annotations:4.1.1.4", + "io.perfmark:perfmark-api:0.23.0", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.http-client:google-http-client-gson:1.40.0", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "commons-codec:commons-codec:1.15", + "com.google.api:gax:2.5.0", + "javax.annotation:javax.annotation-api:1.3.2", + "com.google.guava:failureaccess:1.0.1", + "org.threeten:threetenbp:1.5.1", + "org.apache.httpcomponents:httpclient:4.5.13", + "io.opencensus:opencensus-contrib-http-util:0.28.0", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", + "com.google.code.gson:gson:2.8.8", + "io.opencensus:opencensus-api:0.28.0", + "com.google.http-client:google-http-client:1.40.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0", + "org.checkerframework:checker-compat-qual:2.5.5" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-core/1.93.10/google-cloud-core-1.93.10.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-monitoring/2.3.4/google-cloud-monitoring-2.3.4.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core/1.93.10/google-cloud-core-1.93.10.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-core/1.93.10/google-cloud-core-1.93.10.jar", - "https://jcenter.bintray.com/com/google/cloud/google-cloud-core/1.93.10/google-cloud-core-1.93.10.jar", - "https://maven.google.com/com/google/cloud/google-cloud-core/1.93.10/google-cloud-core-1.93.10.jar" + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-monitoring/2.3.4/google-cloud-monitoring-2.3.4.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-monitoring/2.3.4/google-cloud-monitoring-2.3.4.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-monitoring/2.3.4/google-cloud-monitoring-2.3.4.jar", + "https://maven.google.com/com/google/cloud/google-cloud-monitoring/2.3.4/google-cloud-monitoring-2.3.4.jar" ], - "sha256": "832d74eca66f4601e162a8460d6f59f50d1d23f93c18b02654423b6b0d67c6ea", - "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core/1.93.10/google-cloud-core-1.93.10.jar" + "sha256": "b4a0a04404a705573e63763415f4cd6769a668c1105306189de123c798518b14", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-monitoring/2.3.4/google-cloud-monitoring-2.3.4.jar" }, { - "coord": "com.google.cloud:google-cloud-core:jar:sources:1.93.10", + "coord": "com.google.cloud:google-cloud-monitoring:jar:sources:2.3.4", "dependencies": [ + "com.google.api.grpc:proto-google-cloud-monitoring-v3:jar:sources:2.3.4", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.api:gax:jar:sources:1.60.0", + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", + "com.google.code.gson:gson:jar:sources:2.8.8", + "com.google.android:annotations:jar:sources:4.1.1.4", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "commons-codec:commons-codec:jar:sources:1.15", "commons-logging:commons-logging:jar:sources:1.2", - "com.google.api:api-common:jar:sources:1.10.1", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "com.google.code.gson:gson:jar:sources:2.8.6", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.0.2", - "commons-codec:commons-codec:jar:sources:1.11", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "org.threeten:threetenbp:jar:sources:1.5.0" + "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.api:gax:jar:sources:2.5.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.api:api-common:jar:sources:2.0.2", + "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", + "org.threeten:threetenbp:jar:sources:1.5.1", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "org.apache.httpcomponents:httpcore:jar:sources:4.4.14", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.perfmark:perfmark-api:jar:sources:0.23.0", + "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ + "com.google.api.grpc:proto-google-cloud-monitoring-v3:jar:sources:2.3.4", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", + "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.api:gax:jar:sources:1.60.0", - "com.google.api:api-common:jar:sources:1.10.1", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.0.2", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", - "org.threeten:threetenbp:jar:sources:1.5.0" + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", + "com.google.code.gson:gson:jar:sources:2.8.8", + "com.google.android:annotations:jar:sources:4.1.1.4", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "commons-codec:commons-codec:jar:sources:1.15", + "commons-logging:commons-logging:jar:sources:1.2", + "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.api:gax:jar:sources:2.5.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.api:api-common:jar:sources:2.0.2", + "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", + "org.threeten:threetenbp:jar:sources:1.5.1", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "org.apache.httpcomponents:httpcore:jar:sources:4.4.14", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.perfmark:perfmark-api:jar:sources:0.23.0", + "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-core/1.93.10/google-cloud-core-1.93.10-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-monitoring/2.3.4/google-cloud-monitoring-2.3.4-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core/1.93.10/google-cloud-core-1.93.10-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-core/1.93.10/google-cloud-core-1.93.10-sources.jar", - "https://jcenter.bintray.com/com/google/cloud/google-cloud-core/1.93.10/google-cloud-core-1.93.10-sources.jar", - "https://maven.google.com/com/google/cloud/google-cloud-core/1.93.10/google-cloud-core-1.93.10-sources.jar" + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-monitoring/2.3.4/google-cloud-monitoring-2.3.4-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-monitoring/2.3.4/google-cloud-monitoring-2.3.4-sources.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-monitoring/2.3.4/google-cloud-monitoring-2.3.4-sources.jar", + "https://maven.google.com/com/google/cloud/google-cloud-monitoring/2.3.4/google-cloud-monitoring-2.3.4-sources.jar" ], - "sha256": "2d4ca668f2a0fc68f58c34d8f21c6d3450fb1dad0cd79d03f5f55152948d72fd", - "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-core/1.93.10/google-cloud-core-1.93.10-sources.jar" + "sha256": "7fcdc6131058d0d1753f7b14fb2c9b2c7d69ffc1d54f27f6671d0525bc27f422", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-monitoring/2.3.4/google-cloud-monitoring-2.3.4-sources.jar" }, { - "coord": "com.google.cloud:google-cloud-firestore:2.1.0", + "coord": "com.google.cloud:google-cloud-pubsub:1.113.5", "dependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.api:gax-grpc:1.60.0", "org.conscrypt:conscrypt-openjdk-uber:2.5.1", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", "commons-logging:commons-logging:1.2", - "io.opencensus:opencensus-contrib-http-util:0.24.0", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "com.google.api:api-common:1.10.1", - "com.google.cloud:google-cloud-core-grpc:1.93.10", + "org.apache.httpcomponents:httpcore:4.4.14", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.auto.value:auto-value-annotations:1.8.2", "com.google.android:annotations:4.1.1.4", - "com.google.cloud:google-cloud-core:1.93.10", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", "io.perfmark:perfmark-api:0.23.0", - "commons-codec:commons-codec:1.11", - "io.opencensus:opencensus-api:0.24.0", - "com.google.code.gson:gson:2.8.6", - "org.threeten:threetenbp:1.5.0", - "io.grpc:grpc-alts:1.33.1", - "org.apache.httpcomponents:httpcore:4.4.13", - "io.opencensus:opencensus-contrib-grpc-util:0.24.0", - "com.google.auto.value:auto-value-annotations:1.7.4", - "com.google.api:gax:1.60.0", - "com.google.protobuf:protobuf-java-util:3.13.0", - "com.google.api.grpc:proto-google-iam-v1:1.0.2", - "io.grpc:grpc-netty-shaded:1.33.1", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.http-client:google-http-client-gson:1.40.0", + "com.google.api.grpc:proto-google-cloud-pubsub-v1:1.95.5", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "commons-codec:commons-codec:1.15", + "com.google.api:gax:2.5.0", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", - "com.google.protobuf:protobuf-java:3.13.0", + "org.threeten:threetenbp:1.5.1", "org.apache.httpcomponents:httpclient:4.5.13", - "com.google.api.grpc:proto-google-cloud-firestore-v1:2.1.0", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.http-client:google-http-client:1.37.0", - "io.grpc:grpc-grpclb:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3", + "io.opencensus:opencensus-contrib-http-util:0.28.0", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", + "com.google.code.gson:gson:2.8.8", + "io.opencensus:opencensus-api:0.28.0", + "com.google.http-client:google-http-client:1.40.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0", + "com.google.api.grpc:proto-google-iam-v1:1.1.2", "org.checkerframework:checker-compat-qual:2.5.5" ], "directDependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.api:gax-grpc:1.60.0", "org.conscrypt:conscrypt-openjdk-uber:2.5.1", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", "commons-logging:commons-logging:1.2", - "io.opencensus:opencensus-contrib-http-util:0.24.0", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "com.google.api:api-common:1.10.1", - "com.google.cloud:google-cloud-core-grpc:1.93.10", + "org.apache.httpcomponents:httpcore:4.4.14", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.auto.value:auto-value-annotations:1.8.2", "com.google.android:annotations:4.1.1.4", - "com.google.cloud:google-cloud-core:1.93.10", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", "io.perfmark:perfmark-api:0.23.0", - "commons-codec:commons-codec:1.11", - "io.opencensus:opencensus-api:0.24.0", - "com.google.code.gson:gson:2.8.6", - "org.threeten:threetenbp:1.5.0", - "io.grpc:grpc-alts:1.33.1", - "org.apache.httpcomponents:httpcore:4.4.13", - "io.opencensus:opencensus-contrib-grpc-util:0.24.0", - "com.google.auto.value:auto-value-annotations:1.7.4", - "com.google.api:gax:1.60.0", - "com.google.protobuf:protobuf-java-util:3.13.0", - "com.google.api.grpc:proto-google-iam-v1:1.0.2", - "io.grpc:grpc-netty-shaded:1.33.1", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.http-client:google-http-client-gson:1.40.0", + "com.google.api.grpc:proto-google-cloud-pubsub-v1:1.95.5", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "commons-codec:commons-codec:1.15", + "com.google.api:gax:2.5.0", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", - "com.google.protobuf:protobuf-java:3.13.0", + "org.threeten:threetenbp:1.5.1", "org.apache.httpcomponents:httpclient:4.5.13", - "com.google.api.grpc:proto-google-cloud-firestore-v1:2.1.0", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.http-client:google-http-client:1.37.0", - "io.grpc:grpc-grpclb:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3", + "io.opencensus:opencensus-contrib-http-util:0.28.0", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", + "com.google.code.gson:gson:2.8.8", + "io.opencensus:opencensus-api:0.28.0", + "com.google.http-client:google-http-client:1.40.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0", + "com.google.api.grpc:proto-google-iam-v1:1.1.2", "org.checkerframework:checker-compat-qual:2.5.5" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-firestore/2.1.0/google-cloud-firestore-2.1.0.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-pubsub/1.113.5/google-cloud-pubsub-1.113.5.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-firestore/2.1.0/google-cloud-firestore-2.1.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-firestore/2.1.0/google-cloud-firestore-2.1.0.jar", - "https://jcenter.bintray.com/com/google/cloud/google-cloud-firestore/2.1.0/google-cloud-firestore-2.1.0.jar", - "https://maven.google.com/com/google/cloud/google-cloud-firestore/2.1.0/google-cloud-firestore-2.1.0.jar" + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-pubsub/1.113.5/google-cloud-pubsub-1.113.5.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-pubsub/1.113.5/google-cloud-pubsub-1.113.5.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-pubsub/1.113.5/google-cloud-pubsub-1.113.5.jar", + "https://maven.google.com/com/google/cloud/google-cloud-pubsub/1.113.5/google-cloud-pubsub-1.113.5.jar" ], - "sha256": "e89a30fa19be4ff6a83909e90446b6867561f674d93526cafff545dccd61d833", - "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-firestore/2.1.0/google-cloud-firestore-2.1.0.jar" + "sha256": "e7537c81b4d45893f4e30bdbed3d5cc92cb821d375d201843de5a63b99ce0df2", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-pubsub/1.113.5/google-cloud-pubsub-1.113.5.jar" }, { - "coord": "com.google.cloud:google-cloud-firestore:jar:sources:2.1.0", + "coord": "com.google.cloud:google-cloud-pubsub:jar:sources:1.113.5", "dependencies": [ + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", + "com.google.code.gson:gson:jar:sources:2.8.8", "com.google.android:annotations:jar:sources:4.1.1.4", - "com.google.cloud:google-cloud-core:jar:sources:1.93.10", - "com.google.api:gax:jar:sources:1.60.0", + "com.google.api.grpc:proto-google-cloud-pubsub-v1:jar:sources:1.95.5", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-grpclb:jar:sources:1.33.1", + "commons-codec:commons-codec:jar:sources:1.15", "commons-logging:commons-logging:jar:sources:1.2", - "com.google.api:api-common:jar:sources:1.10.1", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "com.google.api.grpc:proto-google-cloud-firestore-v1:jar:sources:2.1.0", - "com.google.code.gson:gson:jar:sources:2.8.6", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "io.grpc:grpc-netty-shaded:jar:sources:1.33.1", - "io.grpc:grpc-alts:jar:sources:1.33.1", - "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.0.2", - "com.google.api:gax-grpc:jar:sources:1.60.0", + "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.1.2", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.api:gax:jar:sources:2.5.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.api:api-common:jar:sources:2.0.2", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "commons-codec:commons-codec:jar:sources:1.11", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", + "org.threeten:threetenbp:jar:sources:1.5.1", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "org.apache.httpcomponents:httpcore:jar:sources:4.4.14", "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", "io.perfmark:perfmark-api:jar:sources:0.23.0", - "com.google.cloud:google-cloud-core-grpc:jar:sources:1.93.10", "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1", - "io.opencensus:opencensus-contrib-grpc-util:jar:sources:0.24.0", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "org.threeten:threetenbp:jar:sources:1.5.0" + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", + "com.google.code.gson:gson:jar:sources:2.8.8", "com.google.android:annotations:jar:sources:4.1.1.4", - "com.google.cloud:google-cloud-core:jar:sources:1.93.10", - "com.google.api:gax:jar:sources:1.60.0", + "com.google.api.grpc:proto-google-cloud-pubsub-v1:jar:sources:1.95.5", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-grpclb:jar:sources:1.33.1", + "commons-codec:commons-codec:jar:sources:1.15", "commons-logging:commons-logging:jar:sources:1.2", - "com.google.api:api-common:jar:sources:1.10.1", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "com.google.api.grpc:proto-google-cloud-firestore-v1:jar:sources:2.1.0", - "com.google.code.gson:gson:jar:sources:2.8.6", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "io.grpc:grpc-netty-shaded:jar:sources:1.33.1", - "io.grpc:grpc-alts:jar:sources:1.33.1", - "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.0.2", - "com.google.api:gax-grpc:jar:sources:1.60.0", + "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.1.2", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.api:gax:jar:sources:2.5.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.api:api-common:jar:sources:2.0.2", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "commons-codec:commons-codec:jar:sources:1.11", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", + "org.threeten:threetenbp:jar:sources:1.5.1", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "org.apache.httpcomponents:httpcore:jar:sources:4.4.14", "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", "io.perfmark:perfmark-api:jar:sources:0.23.0", - "com.google.cloud:google-cloud-core-grpc:jar:sources:1.93.10", "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1", - "io.opencensus:opencensus-contrib-grpc-util:jar:sources:0.24.0", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "org.threeten:threetenbp:jar:sources:1.5.0" + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-firestore/2.1.0/google-cloud-firestore-2.1.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-pubsub/1.113.5/google-cloud-pubsub-1.113.5-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-firestore/2.1.0/google-cloud-firestore-2.1.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-firestore/2.1.0/google-cloud-firestore-2.1.0-sources.jar", - "https://jcenter.bintray.com/com/google/cloud/google-cloud-firestore/2.1.0/google-cloud-firestore-2.1.0-sources.jar", - "https://maven.google.com/com/google/cloud/google-cloud-firestore/2.1.0/google-cloud-firestore-2.1.0-sources.jar" + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-pubsub/1.113.5/google-cloud-pubsub-1.113.5-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-pubsub/1.113.5/google-cloud-pubsub-1.113.5-sources.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-pubsub/1.113.5/google-cloud-pubsub-1.113.5-sources.jar", + "https://maven.google.com/com/google/cloud/google-cloud-pubsub/1.113.5/google-cloud-pubsub-1.113.5-sources.jar" ], - "sha256": "add7148fcb6c9477efcb42615587d425847d9ab0f69ddf12202fe8c774fdedf1", - "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-firestore/2.1.0/google-cloud-firestore-2.1.0-sources.jar" + "sha256": "ee90739e5752248940876b15d153c1d386665a49b8a2cdd18dae82c0c1f52de4", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-pubsub/1.113.5/google-cloud-pubsub-1.113.5-sources.jar" }, { - "coord": "com.google.cloud:google-cloud-monitoring:2.0.7", + "coord": "com.google.cloud:google-cloud-spanner:6.13.0", "dependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.api:gax-grpc:1.60.0", + "com.google.api.grpc:grpc-google-cloud-spanner-v1:6.13.0", + "com.google.api.grpc:grpc-google-cloud-spanner-admin-instance-v1:6.13.0", "org.conscrypt:conscrypt-openjdk-uber:2.5.1", + "io.opencensus:opencensus-contrib-grpc-util:0.28.0", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", "commons-logging:commons-logging:1.2", - "io.opencensus:opencensus-contrib-http-util:0.24.0", + "com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1:6.13.0", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "com.google.api:api-common:1.10.1", + "org.apache.httpcomponents:httpcore:4.4.14", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.cloud:google-cloud-core-grpc:2.1.6", "com.google.android:annotations:4.1.1.4", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", "io.perfmark:perfmark-api:0.23.0", - "commons-codec:commons-codec:1.11", - "io.opencensus:opencensus-api:0.24.0", - "com.google.api.grpc:proto-google-cloud-monitoring-v3:2.0.7", - "com.google.code.gson:gson:2.8.6", - "org.threeten:threetenbp:1.5.0", - "io.grpc:grpc-alts:1.33.1", - "org.apache.httpcomponents:httpcore:4.4.13", - "com.google.auto.value:auto-value-annotations:1.7.4", - "com.google.api:gax:1.60.0", - "com.google.protobuf:protobuf-java-util:3.13.0", - "io.grpc:grpc-netty-shaded:1.33.1", - "org.apache.commons:commons-lang3:3.5", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.cloud:google-cloud-core:2.1.6", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.cloud:grpc-gcp:1.1.0", + "org.checkerframework:checker-qual:3.13.0", + "com.google.http-client:google-http-client-gson:1.40.0", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.api.grpc:proto-google-cloud-spanner-admin-database-v1:6.13.0", + "com.google.protobuf:protobuf-java:3.17.3", + "commons-codec:commons-codec:1.15", + "com.google.api:gax:2.5.0", + "com.google.api.grpc:grpc-google-cloud-spanner-admin-database-v1:6.13.0", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", - "com.google.protobuf:protobuf-java:3.13.0", + "org.threeten:threetenbp:1.5.1", "org.apache.httpcomponents:httpclient:4.5.13", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.http-client:google-http-client:1.37.0", - "io.grpc:grpc-grpclb:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3", + "io.opencensus:opencensus-contrib-http-util:0.28.0", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", + "com.google.code.gson:gson:2.8.8", + "io.opencensus:opencensus-api:0.28.0", + "com.google.http-client:google-http-client:1.40.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0", + "com.google.api.grpc:proto-google-cloud-spanner-v1:6.13.0", + "com.google.api.grpc:proto-google-iam-v1:1.1.2", "org.checkerframework:checker-compat-qual:2.5.5" ], "directDependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.api:gax-grpc:1.60.0", + "com.google.api.grpc:grpc-google-cloud-spanner-v1:6.13.0", + "com.google.api.grpc:grpc-google-cloud-spanner-admin-instance-v1:6.13.0", "org.conscrypt:conscrypt-openjdk-uber:2.5.1", + "io.opencensus:opencensus-contrib-grpc-util:0.28.0", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", "commons-logging:commons-logging:1.2", - "io.opencensus:opencensus-contrib-http-util:0.24.0", + "com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1:6.13.0", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "com.google.api:api-common:1.10.1", + "org.apache.httpcomponents:httpcore:4.4.14", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.cloud:google-cloud-core-grpc:2.1.6", "com.google.android:annotations:4.1.1.4", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", "io.perfmark:perfmark-api:0.23.0", - "commons-codec:commons-codec:1.11", - "io.opencensus:opencensus-api:0.24.0", - "com.google.api.grpc:proto-google-cloud-monitoring-v3:2.0.7", - "com.google.code.gson:gson:2.8.6", - "org.threeten:threetenbp:1.5.0", - "io.grpc:grpc-alts:1.33.1", - "org.apache.httpcomponents:httpcore:4.4.13", - "com.google.auto.value:auto-value-annotations:1.7.4", - "com.google.api:gax:1.60.0", - "com.google.protobuf:protobuf-java-util:3.13.0", - "io.grpc:grpc-netty-shaded:1.33.1", - "org.apache.commons:commons-lang3:3.5", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.cloud:google-cloud-core:2.1.6", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.cloud:grpc-gcp:1.1.0", + "org.checkerframework:checker-qual:3.13.0", + "com.google.http-client:google-http-client-gson:1.40.0", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.api.grpc:proto-google-cloud-spanner-admin-database-v1:6.13.0", + "com.google.protobuf:protobuf-java:3.17.3", + "commons-codec:commons-codec:1.15", + "com.google.api:gax:2.5.0", + "com.google.api.grpc:grpc-google-cloud-spanner-admin-database-v1:6.13.0", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", - "com.google.protobuf:protobuf-java:3.13.0", + "org.threeten:threetenbp:1.5.1", "org.apache.httpcomponents:httpclient:4.5.13", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.http-client:google-http-client:1.37.0", - "io.grpc:grpc-grpclb:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3", + "io.opencensus:opencensus-contrib-http-util:0.28.0", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", + "com.google.code.gson:gson:2.8.8", + "io.opencensus:opencensus-api:0.28.0", + "com.google.http-client:google-http-client:1.40.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0", + "com.google.api.grpc:proto-google-cloud-spanner-v1:6.13.0", + "com.google.api.grpc:proto-google-iam-v1:1.1.2", "org.checkerframework:checker-compat-qual:2.5.5" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-monitoring/2.0.7/google-cloud-monitoring-2.0.7.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-spanner/6.13.0/google-cloud-spanner-6.13.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-monitoring/2.0.7/google-cloud-monitoring-2.0.7.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-monitoring/2.0.7/google-cloud-monitoring-2.0.7.jar", - "https://jcenter.bintray.com/com/google/cloud/google-cloud-monitoring/2.0.7/google-cloud-monitoring-2.0.7.jar", - "https://maven.google.com/com/google/cloud/google-cloud-monitoring/2.0.7/google-cloud-monitoring-2.0.7.jar" + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-spanner/6.13.0/google-cloud-spanner-6.13.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-spanner/6.13.0/google-cloud-spanner-6.13.0.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-spanner/6.13.0/google-cloud-spanner-6.13.0.jar", + "https://maven.google.com/com/google/cloud/google-cloud-spanner/6.13.0/google-cloud-spanner-6.13.0.jar" ], - "sha256": "f378a5187214250bc3b561ac827b3d054cb5bc5392321814b6fe6b3e16bdae44", - "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-monitoring/2.0.7/google-cloud-monitoring-2.0.7.jar" + "sha256": "d2f863d36dbf49f074ae985010c2f9fd71a16611d7fa6f7cf396fd6da17b004a", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-spanner/6.13.0/google-cloud-spanner-6.13.0.jar" }, { - "coord": "com.google.cloud:google-cloud-monitoring:jar:sources:2.0.7", + "coord": "com.google.cloud:google-cloud-spanner:jar:sources:6.13.0", "dependencies": [ + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", + "com.google.code.gson:gson:jar:sources:2.8.8", + "com.google.api.grpc:grpc-google-cloud-spanner-v1:jar:sources:6.13.0", "com.google.android:annotations:jar:sources:4.1.1.4", - "com.google.api:gax:jar:sources:1.60.0", + "com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1:jar:sources:6.13.0", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-grpclb:jar:sources:1.33.1", + "com.google.api.grpc:proto-google-cloud-spanner-admin-database-v1:jar:sources:6.13.0", + "commons-codec:commons-codec:jar:sources:1.15", + "com.google.cloud:grpc-gcp:jar:sources:1.1.0", "commons-logging:commons-logging:jar:sources:1.2", - "com.google.api.grpc:proto-google-cloud-monitoring-v3:jar:sources:2.0.7", - "com.google.api:api-common:jar:sources:1.10.1", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "com.google.code.gson:gson:jar:sources:2.8.6", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "org.apache.commons:commons-lang3:jar:sources:3.5", - "io.grpc:grpc-netty-shaded:jar:sources:1.33.1", - "io.grpc:grpc-alts:jar:sources:1.33.1", - "com.google.api:gax-grpc:jar:sources:1.60.0", + "com.google.api.grpc:grpc-google-cloud-spanner-admin-instance-v1:jar:sources:6.13.0", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "com.google.api.grpc:proto-google-cloud-spanner-v1:jar:sources:6.13.0", + "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.1.2", + "com.google.api.grpc:grpc-google-cloud-spanner-admin-database-v1:jar:sources:6.13.0", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.api:gax:jar:sources:2.5.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.cloud:google-cloud-core-grpc:jar:sources:2.1.6", + "com.google.api:api-common:jar:sources:2.0.2", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "commons-codec:commons-codec:jar:sources:1.11", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", + "org.threeten:threetenbp:jar:sources:1.5.1", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.cloud:google-cloud-core:jar:sources:2.1.6", + "org.apache.httpcomponents:httpcore:jar:sources:4.4.14", "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", "io.perfmark:perfmark-api:jar:sources:0.23.0", "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "org.threeten:threetenbp:jar:sources:1.5.0" + "io.opencensus:opencensus-contrib-grpc-util:jar:sources:0.28.0" ], "directDependencies": [ + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", + "com.google.code.gson:gson:jar:sources:2.8.8", + "com.google.api.grpc:grpc-google-cloud-spanner-v1:jar:sources:6.13.0", "com.google.android:annotations:jar:sources:4.1.1.4", - "com.google.api:gax:jar:sources:1.60.0", + "com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1:jar:sources:6.13.0", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-grpclb:jar:sources:1.33.1", + "com.google.api.grpc:proto-google-cloud-spanner-admin-database-v1:jar:sources:6.13.0", + "commons-codec:commons-codec:jar:sources:1.15", + "com.google.cloud:grpc-gcp:jar:sources:1.1.0", "commons-logging:commons-logging:jar:sources:1.2", - "com.google.api.grpc:proto-google-cloud-monitoring-v3:jar:sources:2.0.7", - "com.google.api:api-common:jar:sources:1.10.1", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "com.google.code.gson:gson:jar:sources:2.8.6", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "org.apache.commons:commons-lang3:jar:sources:3.5", - "io.grpc:grpc-netty-shaded:jar:sources:1.33.1", - "io.grpc:grpc-alts:jar:sources:1.33.1", - "com.google.api:gax-grpc:jar:sources:1.60.0", + "com.google.api.grpc:grpc-google-cloud-spanner-admin-instance-v1:jar:sources:6.13.0", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "com.google.api.grpc:proto-google-cloud-spanner-v1:jar:sources:6.13.0", + "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.1.2", + "com.google.api.grpc:grpc-google-cloud-spanner-admin-database-v1:jar:sources:6.13.0", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.api:gax:jar:sources:2.5.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.cloud:google-cloud-core-grpc:jar:sources:2.1.6", + "com.google.api:api-common:jar:sources:2.0.2", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "commons-codec:commons-codec:jar:sources:1.11", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", + "org.threeten:threetenbp:jar:sources:1.5.1", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.cloud:google-cloud-core:jar:sources:2.1.6", + "org.apache.httpcomponents:httpcore:jar:sources:4.4.14", "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", "io.perfmark:perfmark-api:jar:sources:0.23.0", "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "org.threeten:threetenbp:jar:sources:1.5.0" + "io.opencensus:opencensus-contrib-grpc-util:jar:sources:0.28.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-monitoring/2.0.7/google-cloud-monitoring-2.0.7-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-spanner/6.13.0/google-cloud-spanner-6.13.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-monitoring/2.0.7/google-cloud-monitoring-2.0.7-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-monitoring/2.0.7/google-cloud-monitoring-2.0.7-sources.jar", - "https://jcenter.bintray.com/com/google/cloud/google-cloud-monitoring/2.0.7/google-cloud-monitoring-2.0.7-sources.jar", - "https://maven.google.com/com/google/cloud/google-cloud-monitoring/2.0.7/google-cloud-monitoring-2.0.7-sources.jar" + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-spanner/6.13.0/google-cloud-spanner-6.13.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-spanner/6.13.0/google-cloud-spanner-6.13.0-sources.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-spanner/6.13.0/google-cloud-spanner-6.13.0-sources.jar", + "https://maven.google.com/com/google/cloud/google-cloud-spanner/6.13.0/google-cloud-spanner-6.13.0-sources.jar" ], - "sha256": "e92275d6b25df5c2e460dc491715c8cc420f18a8865f0051d652144eea936f03", - "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-monitoring/2.0.7/google-cloud-monitoring-2.0.7-sources.jar" + "sha256": "be1f72fe3e4f6cf45f6e6fc392b5403044f212a4deffd658977aadeb15c73081", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-spanner/6.13.0/google-cloud-spanner-6.13.0-sources.jar" }, { - "coord": "com.google.cloud:google-cloud-pubsub:1.108.7", + "coord": "com.google.cloud:google-cloud-storage:1.118.0", "dependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.api:gax-grpc:1.60.0", - "org.conscrypt:conscrypt-openjdk-uber:2.5.1", + "com.fasterxml.jackson.core:jackson-core:2.12.3", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", - "commons-logging:commons-logging:1.2", - "io.opencensus:opencensus-contrib-http-util:0.24.0", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "com.google.api:api-common:1.10.1", - "com.google.android:annotations:4.1.1.4", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "io.perfmark:perfmark-api:0.23.0", - "commons-codec:commons-codec:1.11", - "io.opencensus:opencensus-api:0.24.0", - "com.google.code.gson:gson:2.8.6", - "org.threeten:threetenbp:1.5.0", - "io.grpc:grpc-alts:1.33.1", - "org.apache.httpcomponents:httpcore:4.4.13", - "com.google.api.grpc:proto-google-cloud-pubsub-v1:1.90.7", - "com.google.auto.value:auto-value-annotations:1.7.4", - "com.google.api:gax:1.60.0", - "com.google.protobuf:protobuf-java-util:3.13.0", - "com.google.api.grpc:proto-google-iam-v1:1.0.2", - "io.grpc:grpc-netty-shaded:1.33.1", - "org.apache.commons:commons-lang3:3.5", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.cloud:google-cloud-core:2.1.6", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.http-client:google-http-client-jackson2:1.39.2", + "com.google.oauth-client:google-oauth-client:1.31.5", + "com.google.http-client:google-http-client-apache-v2:1.39.2", + "com.google.http-client:google-http-client-gson:1.40.0", + "com.google.apis:google-api-services-storage:v1-rev20210127-1.32.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.http-client:google-http-client-appengine:1.39.2", + "com.google.cloud:google-cloud-core-http:1.95.4", + "com.google.protobuf:protobuf-java:3.17.3", + "com.google.api-client:google-api-client:1.32.1", + "com.google.api:gax-httpjson:0.83.0", + "com.google.api:gax:2.5.0", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", - "com.google.protobuf:protobuf-java:3.13.0", - "org.apache.httpcomponents:httpclient:4.5.13", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.http-client:google-http-client:1.37.0", - "io.grpc:grpc-grpclb:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3", + "org.threeten:threetenbp:1.5.1", + "io.opencensus:opencensus-contrib-http-util:0.28.0", + "com.google.code.gson:gson:2.8.8", + "io.opencensus:opencensus-api:0.28.0", + "com.google.http-client:google-http-client:1.40.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0", + "com.google.api.grpc:proto-google-iam-v1:1.1.2", "org.checkerframework:checker-compat-qual:2.5.5" ], "directDependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.api:gax-grpc:1.60.0", - "org.conscrypt:conscrypt-openjdk-uber:2.5.1", + "com.fasterxml.jackson.core:jackson-core:2.12.3", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", - "commons-logging:commons-logging:1.2", - "io.opencensus:opencensus-contrib-http-util:0.24.0", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "com.google.api:api-common:1.10.1", - "com.google.android:annotations:4.1.1.4", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "io.perfmark:perfmark-api:0.23.0", - "commons-codec:commons-codec:1.11", - "io.opencensus:opencensus-api:0.24.0", - "com.google.code.gson:gson:2.8.6", - "org.threeten:threetenbp:1.5.0", - "io.grpc:grpc-alts:1.33.1", - "org.apache.httpcomponents:httpcore:4.4.13", - "com.google.api.grpc:proto-google-cloud-pubsub-v1:1.90.7", - "com.google.auto.value:auto-value-annotations:1.7.4", - "com.google.api:gax:1.60.0", - "com.google.protobuf:protobuf-java-util:3.13.0", - "com.google.api.grpc:proto-google-iam-v1:1.0.2", - "io.grpc:grpc-netty-shaded:1.33.1", - "org.apache.commons:commons-lang3:3.5", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.cloud:google-cloud-core:2.1.6", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.http-client:google-http-client-jackson2:1.39.2", + "com.google.oauth-client:google-oauth-client:1.31.5", + "com.google.http-client:google-http-client-apache-v2:1.39.2", + "com.google.http-client:google-http-client-gson:1.40.0", + "com.google.apis:google-api-services-storage:v1-rev20210127-1.32.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.http-client:google-http-client-appengine:1.39.2", + "com.google.cloud:google-cloud-core-http:1.95.4", + "com.google.protobuf:protobuf-java:3.17.3", + "com.google.api-client:google-api-client:1.32.1", + "com.google.api:gax-httpjson:0.83.0", + "com.google.api:gax:2.5.0", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", - "com.google.protobuf:protobuf-java:3.13.0", - "org.apache.httpcomponents:httpclient:4.5.13", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.http-client:google-http-client:1.37.0", - "io.grpc:grpc-grpclb:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3", + "org.threeten:threetenbp:1.5.1", + "io.opencensus:opencensus-contrib-http-util:0.28.0", + "com.google.code.gson:gson:2.8.8", + "io.opencensus:opencensus-api:0.28.0", + "com.google.http-client:google-http-client:1.40.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0", + "com.google.api.grpc:proto-google-iam-v1:1.1.2", "org.checkerframework:checker-compat-qual:2.5.5" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-pubsub/1.108.7/google-cloud-pubsub-1.108.7.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-storage/1.118.0/google-cloud-storage-1.118.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-pubsub/1.108.7/google-cloud-pubsub-1.108.7.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-pubsub/1.108.7/google-cloud-pubsub-1.108.7.jar", - "https://jcenter.bintray.com/com/google/cloud/google-cloud-pubsub/1.108.7/google-cloud-pubsub-1.108.7.jar", - "https://maven.google.com/com/google/cloud/google-cloud-pubsub/1.108.7/google-cloud-pubsub-1.108.7.jar" + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-storage/1.118.0/google-cloud-storage-1.118.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-storage/1.118.0/google-cloud-storage-1.118.0.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-storage/1.118.0/google-cloud-storage-1.118.0.jar", + "https://maven.google.com/com/google/cloud/google-cloud-storage/1.118.0/google-cloud-storage-1.118.0.jar" ], - "sha256": "8f04784313e82768eb8d0f24ddff8c677dcf66956c3c8a3b003794210a91715d", - "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-pubsub/1.108.7/google-cloud-pubsub-1.108.7.jar" + "sha256": "45068e5d6e5e8b0f658364567317bf79378c3f4b61d63d3fea3f00d6fb54492a", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-storage/1.118.0/google-cloud-storage-1.118.0.jar" }, { - "coord": "com.google.cloud:google-cloud-pubsub:jar:sources:1.108.7", + "coord": "com.google.cloud:google-cloud-storage:jar:sources:1.118.0", "dependencies": [ - "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.android:annotations:jar:sources:4.1.1.4", - "com.google.api:gax:jar:sources:1.60.0", + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", + "com.google.code.gson:gson:jar:sources:2.8.8", + "com.google.http-client:google-http-client-apache-v2:jar:sources:1.39.2", + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-grpclb:jar:sources:1.33.1", - "commons-logging:commons-logging:jar:sources:1.2", - "com.google.api:api-common:jar:sources:1.10.1", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "com.google.code.gson:gson:jar:sources:2.8.6", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "org.apache.commons:commons-lang3:jar:sources:3.5", - "io.grpc:grpc-netty-shaded:jar:sources:1.33.1", - "io.grpc:grpc-alts:jar:sources:1.33.1", - "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.0.2", - "com.google.api:gax-grpc:jar:sources:1.60.0", + "com.google.http-client:google-http-client-jackson2:jar:sources:1.39.2", + "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.1.2", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.api:gax-httpjson:jar:sources:0.83.0", + "com.google.api:gax:jar:sources:2.5.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.http-client:google-http-client-appengine:jar:sources:1.39.2", + "com.google.api:api-common:jar:sources:2.0.2", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "commons-codec:commons-codec:jar:sources:1.11", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", + "org.threeten:threetenbp:jar:sources:1.5.1", + "com.google.oauth-client:google-oauth-client:jar:sources:1.31.5", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.api.grpc:proto-google-cloud-pubsub-v1:jar:sources:1.90.7", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.cloud:google-cloud-core:jar:sources:2.1.6", "com.google.guava:failureaccess:jar:sources:1.0.1", - "io.perfmark:perfmark-api:jar:sources:0.23.0", - "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "org.threeten:threetenbp:jar:sources:1.5.0" + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "com.google.api-client:google-api-client:jar:sources:1.32.1", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "com.google.cloud:google-cloud-core-http:jar:sources:1.95.4", + "com.google.apis:google-api-services-storage:jar:sources:v1-rev20210127-1.32.1", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.android:annotations:jar:sources:4.1.1.4", - "com.google.api:gax:jar:sources:1.60.0", + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", + "com.google.code.gson:gson:jar:sources:2.8.8", + "com.google.http-client:google-http-client-apache-v2:jar:sources:1.39.2", + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-grpclb:jar:sources:1.33.1", - "commons-logging:commons-logging:jar:sources:1.2", - "com.google.api:api-common:jar:sources:1.10.1", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "com.google.code.gson:gson:jar:sources:2.8.6", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "org.apache.commons:commons-lang3:jar:sources:3.5", - "io.grpc:grpc-netty-shaded:jar:sources:1.33.1", - "io.grpc:grpc-alts:jar:sources:1.33.1", - "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.0.2", - "com.google.api:gax-grpc:jar:sources:1.60.0", + "com.google.http-client:google-http-client-jackson2:jar:sources:1.39.2", + "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.1.2", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.api:gax-httpjson:jar:sources:0.83.0", + "com.google.api:gax:jar:sources:2.5.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.http-client:google-http-client-appengine:jar:sources:1.39.2", + "com.google.api:api-common:jar:sources:2.0.2", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "commons-codec:commons-codec:jar:sources:1.11", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", + "org.threeten:threetenbp:jar:sources:1.5.1", + "com.google.oauth-client:google-oauth-client:jar:sources:1.31.5", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.api.grpc:proto-google-cloud-pubsub-v1:jar:sources:1.90.7", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.cloud:google-cloud-core:jar:sources:2.1.6", "com.google.guava:failureaccess:jar:sources:1.0.1", - "io.perfmark:perfmark-api:jar:sources:0.23.0", - "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "org.threeten:threetenbp:jar:sources:1.5.0" + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "com.google.api-client:google-api-client:jar:sources:1.32.1", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "com.google.cloud:google-cloud-core-http:jar:sources:1.95.4", + "com.google.apis:google-api-services-storage:jar:sources:v1-rev20210127-1.32.1", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-pubsub/1.108.7/google-cloud-pubsub-1.108.7-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-storage/1.118.0/google-cloud-storage-1.118.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-pubsub/1.108.7/google-cloud-pubsub-1.108.7-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-pubsub/1.108.7/google-cloud-pubsub-1.108.7-sources.jar", - "https://jcenter.bintray.com/com/google/cloud/google-cloud-pubsub/1.108.7/google-cloud-pubsub-1.108.7-sources.jar", - "https://maven.google.com/com/google/cloud/google-cloud-pubsub/1.108.7/google-cloud-pubsub-1.108.7-sources.jar" + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-storage/1.118.0/google-cloud-storage-1.118.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-storage/1.118.0/google-cloud-storage-1.118.0-sources.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-storage/1.118.0/google-cloud-storage-1.118.0-sources.jar", + "https://maven.google.com/com/google/cloud/google-cloud-storage/1.118.0/google-cloud-storage-1.118.0-sources.jar" ], - "sha256": "cd2a11132c915f8cac059afd35e03223ab55082b6390a4ab71b484682a88b7c3", - "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-pubsub/1.108.7/google-cloud-pubsub-1.108.7-sources.jar" + "sha256": "5b8d19e7b7b19118be2e2a1d488e316c8bd5435baba90ec8d3745b1bf089da84", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-storage/1.118.0/google-cloud-storage-1.118.0-sources.jar" }, { - "coord": "com.google.cloud:google-cloud-storage:1.113.2", + "coord": "com.google.cloud:google-cloud-tasks:1.33.2", "dependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "org.conscrypt:conscrypt-openjdk-uber:2.5.1", + "com.google.api:api-common:2.0.2", + "com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.89.2", "com.google.j2objc:j2objc-annotations:1.3", - "io.opencensus:opencensus-contrib-http-util:0.24.0", + "commons-logging:commons-logging:1.2", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "com.google.api:api-common:1.10.1", - "com.google.cloud:google-cloud-core:1.93.10", - "io.opencensus:opencensus-api:0.24.0", - "com.google.code.gson:gson:2.8.6", - "org.threeten:threetenbp:1.5.0", - "com.google.api-client:google-api-client:1.30.11", - "com.google.oauth-client:google-oauth-client:1.31.1", - "com.google.auto.value:auto-value-annotations:1.7.4", - "com.google.api:gax:1.60.0", - "com.google.protobuf:protobuf-java-util:3.13.0", - "com.google.api.grpc:proto-google-iam-v1:1.0.2", - "com.google.cloud:google-cloud-core-http:1.93.9", - "com.google.http-client:google-http-client-appengine:1.37.0", + "org.apache.httpcomponents:httpcore:4.4.14", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.android:annotations:4.1.1.4", + "io.perfmark:perfmark-api:0.23.0", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.http-client:google-http-client-gson:1.40.0", + "com.google.api.grpc:proto-google-cloud-tasks-v2:1.33.2", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.89.2", + "commons-codec:commons-codec:1.15", + "com.google.api:gax:2.5.0", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", - "com.google.protobuf:protobuf-java:3.13.0", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.apis:google-api-services-storage:v1-rev20200927-1.30.10", - "com.google.http-client:google-http-client:1.37.0", - "com.google.errorprone:error_prone_annotations:2.4.0", - "com.google.api:gax-httpjson:0.77.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3", + "org.threeten:threetenbp:1.5.1", + "org.apache.httpcomponents:httpclient:4.5.13", + "io.opencensus:opencensus-contrib-http-util:0.28.0", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", + "com.google.code.gson:gson:2.8.8", + "io.opencensus:opencensus-api:0.28.0", + "com.google.http-client:google-http-client:1.40.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0", + "com.google.api.grpc:proto-google-iam-v1:1.1.2", "org.checkerframework:checker-compat-qual:2.5.5" ], "directDependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "org.conscrypt:conscrypt-openjdk-uber:2.5.1", + "com.google.api:api-common:2.0.2", + "com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.89.2", "com.google.j2objc:j2objc-annotations:1.3", - "io.opencensus:opencensus-contrib-http-util:0.24.0", + "commons-logging:commons-logging:1.2", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "com.google.api:api-common:1.10.1", - "com.google.cloud:google-cloud-core:1.93.10", - "io.opencensus:opencensus-api:0.24.0", - "com.google.code.gson:gson:2.8.6", - "org.threeten:threetenbp:1.5.0", - "com.google.api-client:google-api-client:1.30.11", - "com.google.oauth-client:google-oauth-client:1.31.1", - "com.google.auto.value:auto-value-annotations:1.7.4", - "com.google.api:gax:1.60.0", - "com.google.protobuf:protobuf-java-util:3.13.0", - "com.google.api.grpc:proto-google-iam-v1:1.0.2", - "com.google.cloud:google-cloud-core-http:1.93.9", - "com.google.http-client:google-http-client-appengine:1.37.0", + "org.apache.httpcomponents:httpcore:4.4.14", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.android:annotations:4.1.1.4", + "io.perfmark:perfmark-api:0.23.0", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.http-client:google-http-client-gson:1.40.0", + "com.google.api.grpc:proto-google-cloud-tasks-v2:1.33.2", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.89.2", + "commons-codec:commons-codec:1.15", + "com.google.api:gax:2.5.0", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", - "com.google.protobuf:protobuf-java:3.13.0", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.apis:google-api-services-storage:v1-rev20200927-1.30.10", - "com.google.http-client:google-http-client:1.37.0", - "com.google.errorprone:error_prone_annotations:2.4.0", - "com.google.api:gax-httpjson:0.77.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3", + "org.threeten:threetenbp:1.5.1", + "org.apache.httpcomponents:httpclient:4.5.13", + "io.opencensus:opencensus-contrib-http-util:0.28.0", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", + "com.google.code.gson:gson:2.8.8", + "io.opencensus:opencensus-api:0.28.0", + "com.google.http-client:google-http-client:1.40.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0", + "com.google.api.grpc:proto-google-iam-v1:1.1.2", "org.checkerframework:checker-compat-qual:2.5.5" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-storage/1.113.2/google-cloud-storage-1.113.2.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-tasks/1.33.2/google-cloud-tasks-1.33.2.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-storage/1.113.2/google-cloud-storage-1.113.2.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-storage/1.113.2/google-cloud-storage-1.113.2.jar", - "https://jcenter.bintray.com/com/google/cloud/google-cloud-storage/1.113.2/google-cloud-storage-1.113.2.jar", - "https://maven.google.com/com/google/cloud/google-cloud-storage/1.113.2/google-cloud-storage-1.113.2.jar" + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-tasks/1.33.2/google-cloud-tasks-1.33.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-tasks/1.33.2/google-cloud-tasks-1.33.2.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-tasks/1.33.2/google-cloud-tasks-1.33.2.jar", + "https://maven.google.com/com/google/cloud/google-cloud-tasks/1.33.2/google-cloud-tasks-1.33.2.jar" ], - "sha256": "401a121da65984214c83b32d4621d4f2e2855033a91663a6a859840e851580cf", - "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-storage/1.113.2/google-cloud-storage-1.113.2.jar" + "sha256": "e45c425bc105297b76d21b2045d86b5c5b5fba8afe8bbb49e04b8e125137c351", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-tasks/1.33.2/google-cloud-tasks-1.33.2.jar" }, { - "coord": "com.google.cloud:google-cloud-storage:jar:sources:1.113.2", + "coord": "com.google.cloud:google-cloud-tasks:jar:sources:1.33.2", "dependencies": [ + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", + "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.cloud:google-cloud-core:jar:sources:1.93.10", - "com.google.api:gax:jar:sources:1.60.0", + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", + "com.google.code.gson:gson:jar:sources:2.8.8", + "com.google.android:annotations:jar:sources:4.1.1.4", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "com.google.http-client:google-http-client-appengine:jar:sources:1.37.0", - "com.google.api:gax-httpjson:jar:sources:0.77.0", - "com.google.api:api-common:jar:sources:1.10.1", - "com.google.cloud:google-cloud-core-http:jar:sources:1.93.9", - "com.google.api-client:google-api-client:jar:sources:1.30.11", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "com.google.code.gson:gson:jar:sources:2.8.6", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.0.2", - "com.google.oauth-client:google-oauth-client:jar:sources:1.31.1", + "com.google.api.grpc:proto-google-cloud-tasks-v2:jar:sources:1.33.2", + "commons-codec:commons-codec:jar:sources:1.15", + "commons-logging:commons-logging:jar:sources:1.2", + "com.google.api.grpc:proto-google-cloud-tasks-v2beta2:jar:sources:0.89.2", + "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.1.2", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.api:gax:jar:sources:2.5.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.api.grpc:proto-google-cloud-tasks-v2beta3:jar:sources:0.89.2", + "com.google.api:api-common:jar:sources:2.0.2", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", + "org.threeten:threetenbp:jar:sources:1.5.1", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "org.apache.httpcomponents:httpcore:jar:sources:4.4.14", "com.google.guava:failureaccess:jar:sources:1.0.1", - "com.google.apis:google-api-services-storage:jar:sources:v1-rev20200927-1.30.10", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "org.threeten:threetenbp:jar:sources:1.5.0" + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.perfmark:perfmark-api:jar:sources:0.23.0", + "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", + "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.cloud:google-cloud-core:jar:sources:1.93.10", - "com.google.api:gax:jar:sources:1.60.0", + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", + "com.google.code.gson:gson:jar:sources:2.8.8", + "com.google.android:annotations:jar:sources:4.1.1.4", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "com.google.http-client:google-http-client-appengine:jar:sources:1.37.0", - "com.google.api:gax-httpjson:jar:sources:0.77.0", - "com.google.api:api-common:jar:sources:1.10.1", - "com.google.cloud:google-cloud-core-http:jar:sources:1.93.9", - "com.google.api-client:google-api-client:jar:sources:1.30.11", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "com.google.code.gson:gson:jar:sources:2.8.6", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.0.2", - "com.google.oauth-client:google-oauth-client:jar:sources:1.31.1", + "com.google.api.grpc:proto-google-cloud-tasks-v2:jar:sources:1.33.2", + "commons-codec:commons-codec:jar:sources:1.15", + "commons-logging:commons-logging:jar:sources:1.2", + "com.google.api.grpc:proto-google-cloud-tasks-v2beta2:jar:sources:0.89.2", + "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.1.2", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "com.google.api:gax:jar:sources:2.5.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.api.grpc:proto-google-cloud-tasks-v2beta3:jar:sources:0.89.2", + "com.google.api:api-common:jar:sources:2.0.2", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", + "org.threeten:threetenbp:jar:sources:1.5.1", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "org.apache.httpcomponents:httpcore:jar:sources:4.4.14", "com.google.guava:failureaccess:jar:sources:1.0.1", - "com.google.apis:google-api-services-storage:jar:sources:v1-rev20200927-1.30.10", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "org.threeten:threetenbp:jar:sources:1.5.0" + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.perfmark:perfmark-api:jar:sources:0.23.0", + "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-tasks/1.33.2/google-cloud-tasks-1.33.2-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-tasks/1.33.2/google-cloud-tasks-1.33.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-tasks/1.33.2/google-cloud-tasks-1.33.2-sources.jar", + "https://jcenter.bintray.com/com/google/cloud/google-cloud-tasks/1.33.2/google-cloud-tasks-1.33.2-sources.jar", + "https://maven.google.com/com/google/cloud/google-cloud-tasks/1.33.2/google-cloud-tasks-1.33.2-sources.jar" + ], + "sha256": "f3ec15aca974144219bf2e6f64a4c34c2bc6111a8fa48d5448032d4016a01dc4", + "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-tasks/1.33.2/google-cloud-tasks-1.33.2-sources.jar" + }, + { + "coord": "com.google.cloud:grpc-gcp:1.1.0", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "io.opencensus:opencensus-api", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "com.google.protobuf:protobuf-java-util", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/grpc-gcp/1.1.0/grpc-gcp-1.1.0.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/cloud/grpc-gcp/1.1.0/grpc-gcp-1.1.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/grpc-gcp/1.1.0/grpc-gcp-1.1.0.jar", + "https://jcenter.bintray.com/com/google/cloud/grpc-gcp/1.1.0/grpc-gcp-1.1.0.jar", + "https://maven.google.com/com/google/cloud/grpc-gcp/1.1.0/grpc-gcp-1.1.0.jar" ], + "sha256": "ccaabef8102f2e8d8292cf218cd9bb88d012b0e58578532ac1ce7985c7356046", + "url": "https://repo1.maven.org/maven2/com/google/cloud/grpc-gcp/1.1.0/grpc-gcp-1.1.0.jar" + }, + { + "coord": "com.google.cloud:grpc-gcp:jar:sources:1.1.0", + "dependencies": [], + "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", + "io.opencensus:opencensus-api", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", + "com.google.protobuf:protobuf-java-util", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-storage/1.113.2/google-cloud-storage-1.113.2-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/grpc-gcp/1.1.0/grpc-gcp-1.1.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-storage/1.113.2/google-cloud-storage-1.113.2-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-storage/1.113.2/google-cloud-storage-1.113.2-sources.jar", - "https://jcenter.bintray.com/com/google/cloud/google-cloud-storage/1.113.2/google-cloud-storage-1.113.2-sources.jar", - "https://maven.google.com/com/google/cloud/google-cloud-storage/1.113.2/google-cloud-storage-1.113.2-sources.jar" + "https://repo1.maven.org/maven2/com/google/cloud/grpc-gcp/1.1.0/grpc-gcp-1.1.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/grpc-gcp/1.1.0/grpc-gcp-1.1.0-sources.jar", + "https://jcenter.bintray.com/com/google/cloud/grpc-gcp/1.1.0/grpc-gcp-1.1.0-sources.jar", + "https://maven.google.com/com/google/cloud/grpc-gcp/1.1.0/grpc-gcp-1.1.0-sources.jar" ], - "sha256": "63540b93dc4d740b98154d20e38271cb64490a0c98e67652acc29437577e1a71", - "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-storage/1.113.2/google-cloud-storage-1.113.2-sources.jar" + "sha256": "ea90e63dcc46bb52b29d6009ed58d3d6744a1ff2580fbb5020fe1fa8c5b3558b", + "url": "https://repo1.maven.org/maven2/com/google/cloud/grpc-gcp/1.1.0/grpc-gcp-1.1.0-sources.jar" }, { - "coord": "com.google.cloud:google-cloud-tasks:1.30.7", + "coord": "com.google.cloud:proto-google-cloud-firestore-bundle-v1:2.6.2", "dependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.api:gax-grpc:1.60.0", - "org.conscrypt:conscrypt-openjdk-uber:2.5.1", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", - "commons-logging:commons-logging:1.2", - "io.opencensus:opencensus-contrib-http-util:0.24.0", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "com.google.api:api-common:1.10.1", - "com.google.android:annotations:4.1.1.4", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "io.perfmark:perfmark-api:0.23.0", - "com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.86.7", - "commons-codec:commons-codec:1.11", - "io.opencensus:opencensus-api:0.24.0", - "com.google.code.gson:gson:2.8.6", - "org.threeten:threetenbp:1.5.0", - "io.grpc:grpc-alts:1.33.1", - "org.apache.httpcomponents:httpcore:4.4.13", - "com.google.auto.value:auto-value-annotations:1.7.4", - "com.google.api:gax:1.60.0", - "com.google.protobuf:protobuf-java-util:3.13.0", - "com.google.api.grpc:proto-google-iam-v1:1.0.2", - "io.grpc:grpc-netty-shaded:1.33.1", - "org.apache.commons:commons-lang3:3.5", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.errorprone:error_prone_annotations:2.9.0", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.api.grpc:proto-google-cloud-tasks-v2:1.30.7", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", - "com.google.protobuf:protobuf-java:3.13.0", - "org.apache.httpcomponents:httpclient:4.5.13", - "com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.86.7", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.http-client:google-http-client:1.37.0", - "io.grpc:grpc-grpclb:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3", "org.checkerframework:checker-compat-qual:2.5.5" ], "directDependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.api:gax-grpc:1.60.0", - "org.conscrypt:conscrypt-openjdk-uber:2.5.1", + "com.google.api:api-common:2.0.2", "com.google.j2objc:j2objc-annotations:1.3", - "commons-logging:commons-logging:1.2", - "io.opencensus:opencensus-contrib-http-util:0.24.0", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "com.google.api:api-common:1.10.1", - "com.google.android:annotations:4.1.1.4", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "io.perfmark:perfmark-api:0.23.0", - "com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.86.7", - "commons-codec:commons-codec:1.11", - "io.opencensus:opencensus-api:0.24.0", - "com.google.code.gson:gson:2.8.6", - "org.threeten:threetenbp:1.5.0", - "io.grpc:grpc-alts:1.33.1", - "org.apache.httpcomponents:httpcore:4.4.13", - "com.google.auto.value:auto-value-annotations:1.7.4", - "com.google.api:gax:1.60.0", - "com.google.protobuf:protobuf-java-util:3.13.0", - "com.google.api.grpc:proto-google-iam-v1:1.0.2", - "io.grpc:grpc-netty-shaded:1.33.1", - "org.apache.commons:commons-lang3:3.5", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "com.google.errorprone:error_prone_annotations:2.9.0", "javax.annotation:javax.annotation-api:1.3.2", "com.google.guava:failureaccess:1.0.1", - "com.google.api.grpc:proto-google-cloud-tasks-v2:1.30.7", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", - "com.google.protobuf:protobuf-java:3.13.0", - "org.apache.httpcomponents:httpclient:4.5.13", - "com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.86.7", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.http-client:google-http-client:1.37.0", - "io.grpc:grpc-grpclb:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3", "org.checkerframework:checker-compat-qual:2.5.5" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", + "com.google.api.grpc:proto-google-cloud-firestore-v1", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-tasks/1.30.7/google-cloud-tasks-1.30.7.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/proto-google-cloud-firestore-bundle-v1/2.6.2/proto-google-cloud-firestore-bundle-v1-2.6.2.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-tasks/1.30.7/google-cloud-tasks-1.30.7.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-tasks/1.30.7/google-cloud-tasks-1.30.7.jar", - "https://jcenter.bintray.com/com/google/cloud/google-cloud-tasks/1.30.7/google-cloud-tasks-1.30.7.jar", - "https://maven.google.com/com/google/cloud/google-cloud-tasks/1.30.7/google-cloud-tasks-1.30.7.jar" + "https://repo1.maven.org/maven2/com/google/cloud/proto-google-cloud-firestore-bundle-v1/2.6.2/proto-google-cloud-firestore-bundle-v1-2.6.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/proto-google-cloud-firestore-bundle-v1/2.6.2/proto-google-cloud-firestore-bundle-v1-2.6.2.jar", + "https://jcenter.bintray.com/com/google/cloud/proto-google-cloud-firestore-bundle-v1/2.6.2/proto-google-cloud-firestore-bundle-v1-2.6.2.jar", + "https://maven.google.com/com/google/cloud/proto-google-cloud-firestore-bundle-v1/2.6.2/proto-google-cloud-firestore-bundle-v1-2.6.2.jar" ], - "sha256": "1f70f727043d787391eb750647566025da513a573dca74bbae73f4120204c652", - "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-tasks/1.30.7/google-cloud-tasks-1.30.7.jar" + "sha256": "bd5a1639662c3c4caf36378477fa4a51fe8d4be819458a8cde42d2abc1bce6c1", + "url": "https://repo1.maven.org/maven2/com/google/cloud/proto-google-cloud-firestore-bundle-v1/2.6.2/proto-google-cloud-firestore-bundle-v1-2.6.2.jar" }, { - "coord": "com.google.cloud:google-cloud-tasks:jar:sources:1.30.7", + "coord": "com.google.cloud:proto-google-cloud-firestore-bundle-v1:jar:sources:2.6.2", "dependencies": [ - "com.google.api.grpc:proto-google-cloud-tasks-v2beta3:jar:sources:0.86.7", - "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.android:annotations:jar:sources:4.1.1.4", - "com.google.api:gax:jar:sources:1.60.0", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-grpclb:jar:sources:1.33.1", - "commons-logging:commons-logging:jar:sources:1.2", - "com.google.api:api-common:jar:sources:1.10.1", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "com.google.code.gson:gson:jar:sources:2.8.6", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "org.apache.commons:commons-lang3:jar:sources:3.5", - "io.grpc:grpc-netty-shaded:jar:sources:1.33.1", - "io.grpc:grpc-alts:jar:sources:1.33.1", - "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.0.2", - "com.google.api:gax-grpc:jar:sources:1.60.0", + "com.google.api:api-common:jar:sources:2.0.2", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "commons-codec:commons-codec:jar:sources:1.11", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", "com.google.guava:failureaccess:jar:sources:1.0.1", - "com.google.api.grpc:proto-google-cloud-tasks-v2beta2:jar:sources:0.86.7", - "io.perfmark:perfmark-api:jar:sources:0.23.0", - "com.google.api.grpc:proto-google-cloud-tasks-v2:jar:sources:1.30.7", - "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "org.threeten:threetenbp:jar:sources:1.5.0" + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "com.google.api.grpc:proto-google-cloud-tasks-v2beta3:jar:sources:0.86.7", - "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.android:annotations:jar:sources:4.1.1.4", - "com.google.api:gax:jar:sources:1.60.0", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-grpclb:jar:sources:1.33.1", - "commons-logging:commons-logging:jar:sources:1.2", - "com.google.api:api-common:jar:sources:1.10.1", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "com.google.code.gson:gson:jar:sources:2.8.6", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "org.apache.commons:commons-lang3:jar:sources:3.5", - "io.grpc:grpc-netty-shaded:jar:sources:1.33.1", - "io.grpc:grpc-alts:jar:sources:1.33.1", - "com.google.api.grpc:proto-google-iam-v1:jar:sources:1.0.2", - "com.google.api:gax-grpc:jar:sources:1.60.0", + "com.google.api:api-common:jar:sources:2.0.2", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "commons-codec:commons-codec:jar:sources:1.11", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", "com.google.guava:failureaccess:jar:sources:1.0.1", - "com.google.api.grpc:proto-google-cloud-tasks-v2beta2:jar:sources:0.86.7", - "io.perfmark:perfmark-api:jar:sources:0.23.0", - "com.google.api.grpc:proto-google-cloud-tasks-v2:jar:sources:1.30.7", - "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "org.threeten:threetenbp:jar:sources:1.5.0" + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", + "com.google.protobuf:protobuf-java", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", + "com.google.api.grpc:proto-google-cloud-firestore-v1", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/google-cloud-tasks/1.30.7/google-cloud-tasks-1.30.7-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/cloud/proto-google-cloud-firestore-bundle-v1/2.6.2/proto-google-cloud-firestore-bundle-v1-2.6.2-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-tasks/1.30.7/google-cloud-tasks-1.30.7-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/google-cloud-tasks/1.30.7/google-cloud-tasks-1.30.7-sources.jar", - "https://jcenter.bintray.com/com/google/cloud/google-cloud-tasks/1.30.7/google-cloud-tasks-1.30.7-sources.jar", - "https://maven.google.com/com/google/cloud/google-cloud-tasks/1.30.7/google-cloud-tasks-1.30.7-sources.jar" + "https://repo1.maven.org/maven2/com/google/cloud/proto-google-cloud-firestore-bundle-v1/2.6.2/proto-google-cloud-firestore-bundle-v1-2.6.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/cloud/proto-google-cloud-firestore-bundle-v1/2.6.2/proto-google-cloud-firestore-bundle-v1-2.6.2-sources.jar", + "https://jcenter.bintray.com/com/google/cloud/proto-google-cloud-firestore-bundle-v1/2.6.2/proto-google-cloud-firestore-bundle-v1-2.6.2-sources.jar", + "https://maven.google.com/com/google/cloud/proto-google-cloud-firestore-bundle-v1/2.6.2/proto-google-cloud-firestore-bundle-v1-2.6.2-sources.jar" ], - "sha256": "8eb45f859101ff7463cce15198ec3a023a125b9d2e31b3a46a78abdc293ff4d1", - "url": "https://repo1.maven.org/maven2/com/google/cloud/google-cloud-tasks/1.30.7/google-cloud-tasks-1.30.7-sources.jar" + "sha256": "dc49cd082d834f122b239ae7f6ffcb3d85f106e5ad8fe12a5893304ea9b8af36", + "url": "https://repo1.maven.org/maven2/com/google/cloud/proto-google-cloud-firestore-bundle-v1/2.6.2/proto-google-cloud-firestore-bundle-v1-2.6.2-sources.jar" }, { "coord": "com.google.code.findbugs:jsr305:3.0.2", @@ -3609,76 +4901,92 @@ "url": "https://repo1.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2-sources.jar" }, { - "coord": "com.google.code.gson:gson:2.8.6", + "coord": "com.google.code.gson:gson:2.8.8", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/code/gson/gson/2.8.8/gson-2.8.8.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar", - "https://jcenter.bintray.com/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar", - "https://maven.google.com/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar" + "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.8/gson-2.8.8.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/code/gson/gson/2.8.8/gson-2.8.8.jar", + "https://jcenter.bintray.com/com/google/code/gson/gson/2.8.8/gson-2.8.8.jar", + "https://maven.google.com/com/google/code/gson/gson/2.8.8/gson-2.8.8.jar" ], - "sha256": "c8fb4839054d280b3033f800d1f5a97de2f028eb8ba2eb458ad287e536f3f25f", - "url": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar" + "sha256": "c6f3152b0c39c1b7ebe267e99604a750e9d5585b2f3f2e19d6fbd7ec9080d0fb", + "url": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.8/gson-2.8.8.jar" }, { - "coord": "com.google.code.gson:gson:jar:sources:2.8.6", + "coord": "com.google.code.gson:gson:jar:sources:2.8.8", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/code/gson/gson/2.8.8/gson-2.8.8-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/code/gson/gson/2.8.6/gson-2.8.6-sources.jar", - "https://jcenter.bintray.com/com/google/code/gson/gson/2.8.6/gson-2.8.6-sources.jar", - "https://maven.google.com/com/google/code/gson/gson/2.8.6/gson-2.8.6-sources.jar" + "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.8/gson-2.8.8-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/code/gson/gson/2.8.8/gson-2.8.8-sources.jar", + "https://jcenter.bintray.com/com/google/code/gson/gson/2.8.8/gson-2.8.8-sources.jar", + "https://maven.google.com/com/google/code/gson/gson/2.8.8/gson-2.8.8-sources.jar" ], - "sha256": "da4d787939dc8de214724a20d88614b70ef8c3a4931d9c694300b5d9098ed9bc", - "url": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6-sources.jar" + "sha256": "af40caf8da8cc5ce88cc6adfe642784012364d93ddbf93c279495913da0536f6", + "url": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.8/gson-2.8.8-sources.jar" }, { - "coord": "com.google.errorprone:error_prone_annotations:2.4.0", + "coord": "com.google.errorprone:error_prone_annotations:2.9.0", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.4.0/error_prone_annotations-2.4.0.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.9.0/error_prone_annotations-2.9.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.4.0/error_prone_annotations-2.4.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/errorprone/error_prone_annotations/2.4.0/error_prone_annotations-2.4.0.jar", - "https://jcenter.bintray.com/com/google/errorprone/error_prone_annotations/2.4.0/error_prone_annotations-2.4.0.jar", - "https://maven.google.com/com/google/errorprone/error_prone_annotations/2.4.0/error_prone_annotations-2.4.0.jar" + "https://repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.9.0/error_prone_annotations-2.9.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/errorprone/error_prone_annotations/2.9.0/error_prone_annotations-2.9.0.jar", + "https://jcenter.bintray.com/com/google/errorprone/error_prone_annotations/2.9.0/error_prone_annotations-2.9.0.jar", + "https://maven.google.com/com/google/errorprone/error_prone_annotations/2.9.0/error_prone_annotations-2.9.0.jar" ], - "sha256": "5f2a0648230a662e8be049df308d583d7369f13af683e44ddf5829b6d741a228", - "url": "https://repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.4.0/error_prone_annotations-2.4.0.jar" + "sha256": "f947bdc33ae27a6b4aa44799e6c21e1944797bd0010ba43eb82d11446e163694", + "url": "https://repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.9.0/error_prone_annotations-2.9.0.jar" }, { - "coord": "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", + "coord": "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", "com.google.template:soy", - "com.google.common.html.types:types" + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.4.0/error_prone_annotations-2.4.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.9.0/error_prone_annotations-2.9.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.4.0/error_prone_annotations-2.4.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/errorprone/error_prone_annotations/2.4.0/error_prone_annotations-2.4.0-sources.jar", - "https://jcenter.bintray.com/com/google/errorprone/error_prone_annotations/2.4.0/error_prone_annotations-2.4.0-sources.jar", - "https://maven.google.com/com/google/errorprone/error_prone_annotations/2.4.0/error_prone_annotations-2.4.0-sources.jar" + "https://repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.9.0/error_prone_annotations-2.9.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/errorprone/error_prone_annotations/2.9.0/error_prone_annotations-2.9.0-sources.jar", + "https://jcenter.bintray.com/com/google/errorprone/error_prone_annotations/2.9.0/error_prone_annotations-2.9.0-sources.jar", + "https://maven.google.com/com/google/errorprone/error_prone_annotations/2.9.0/error_prone_annotations-2.9.0-sources.jar" ], - "sha256": "d74a623bd5c8861621c267187c5a4a4f891b4989de91cda6577e243ee2716da5", - "url": "https://repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.4.0/error_prone_annotations-2.4.0-sources.jar" + "sha256": "0e41e099f0d4c9be030c34a6991821e67a57e2846dba908f4e9bc9ec60732837", + "url": "https://repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.9.0/error_prone_annotations-2.9.0-sources.jar" }, { "coord": "com.google.guava:failureaccess:1.0.1", @@ -3717,323 +5025,491 @@ "url": "https://repo1.maven.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1-sources.jar" }, { - "coord": "com.google.guava:guava:30.0-android", + "coord": "com.google.guava:guava:30.1.1-android", "dependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", + "com.google.errorprone:error_prone_annotations:2.9.0", "com.google.guava:failureaccess:1.0.1", - "com.google.errorprone:error_prone_annotations:2.4.0", "org.checkerframework:checker-compat-qual:2.5.5" ], "directDependencies": [ "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", + "com.google.errorprone:error_prone_annotations:2.9.0", "com.google.guava:failureaccess:1.0.1", - "com.google.errorprone:error_prone_annotations:2.4.0", "org.checkerframework:checker-compat-qual:2.5.5" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/guava/guava/30.0-android/guava-30.0-android.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/guava/guava/30.1.1-android/guava-30.1.1-android.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/guava/guava/30.0-android/guava-30.0-android.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/guava/guava/30.0-android/guava-30.0-android.jar", - "https://jcenter.bintray.com/com/google/guava/guava/30.0-android/guava-30.0-android.jar", - "https://maven.google.com/com/google/guava/guava/30.0-android/guava-30.0-android.jar" + "https://repo1.maven.org/maven2/com/google/guava/guava/30.1.1-android/guava-30.1.1-android.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/guava/guava/30.1.1-android/guava-30.1.1-android.jar", + "https://jcenter.bintray.com/com/google/guava/guava/30.1.1-android/guava-30.1.1-android.jar", + "https://maven.google.com/com/google/guava/guava/30.1.1-android/guava-30.1.1-android.jar" ], - "sha256": "3345c82c2cc70a0053e8db9031edc6d71625ef0dea6a2c8f5ebd6cb76d2bf843", - "url": "https://repo1.maven.org/maven2/com/google/guava/guava/30.0-android/guava-30.0-android.jar" + "sha256": "355f79352f8c252f2bdaa06c687c4836a38016caccfc4c28d16ae77ecfdffa2f", + "url": "https://repo1.maven.org/maven2/com/google/guava/guava/30.1.1-android/guava-30.1.1-android.jar" }, { - "coord": "com.google.guava:guava:jar:sources:30.0-android", + "coord": "com.google.guava:guava:jar:sources:30.1.1-android", "dependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.guava:failureaccess:jar:sources:1.0.1" + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0" ], "directDependencies": [ "com.google.code.findbugs:jsr305:jar:sources:3.0.2", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.guava:failureaccess:jar:sources:1.0.1" + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/guava/guava/30.0-android/guava-30.0-android-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/guava/guava/30.1.1-android/guava-30.1.1-android-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/guava/guava/30.0-android/guava-30.0-android-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/guava/guava/30.0-android/guava-30.0-android-sources.jar", - "https://jcenter.bintray.com/com/google/guava/guava/30.0-android/guava-30.0-android-sources.jar", - "https://maven.google.com/com/google/guava/guava/30.0-android/guava-30.0-android-sources.jar" + "https://repo1.maven.org/maven2/com/google/guava/guava/30.1.1-android/guava-30.1.1-android-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/guava/guava/30.1.1-android/guava-30.1.1-android-sources.jar", + "https://jcenter.bintray.com/com/google/guava/guava/30.1.1-android/guava-30.1.1-android-sources.jar", + "https://maven.google.com/com/google/guava/guava/30.1.1-android/guava-30.1.1-android-sources.jar" ], - "sha256": "86939cb2313fcdc0380f348cd05532aaa21808e4437ccb8f561d42cfbe26b6e9", - "url": "https://repo1.maven.org/maven2/com/google/guava/guava/30.0-android/guava-30.0-android-sources.jar" + "sha256": "dac52416371d86baea1ffe6ef7b54b2730b52fcea19091ae32942cb5cd9f868c", + "url": "https://repo1.maven.org/maven2/com/google/guava/guava/30.1.1-android/guava-30.1.1-android-sources.jar" }, { "coord": "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", "dependencies": [], "directDependencies": [], "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar", + "https://jcenter.bintray.com/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar", + "https://maven.google.com/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" + ], + "sha256": "b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99", + "url": "https://repo1.maven.org/maven2/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" + }, + { + "coord": "com.google.http-client:google-http-client-apache-v2:1.39.2", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "org.apache.httpcomponents:httpcore", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.http-client:google-http-client", + "org.apache.httpcomponents:httpclient" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/http-client/google-http-client-apache-v2/1.39.2/google-http-client-apache-v2-1.39.2.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar", - "https://jcenter.bintray.com/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar", - "https://maven.google.com/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" + "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-apache-v2/1.39.2/google-http-client-apache-v2-1.39.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/http-client/google-http-client-apache-v2/1.39.2/google-http-client-apache-v2-1.39.2.jar", + "https://jcenter.bintray.com/com/google/http-client/google-http-client-apache-v2/1.39.2/google-http-client-apache-v2-1.39.2.jar", + "https://maven.google.com/com/google/http-client/google-http-client-apache-v2/1.39.2/google-http-client-apache-v2-1.39.2.jar" ], - "sha256": "b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99", - "url": "https://repo1.maven.org/maven2/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" + "sha256": "52d39209f65abf6f577e1cab466adb99f12d1cd2ba4e98d86e501f75d7f850cf", + "url": "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-apache-v2/1.39.2/google-http-client-apache-v2-1.39.2.jar" + }, + { + "coord": "com.google.http-client:google-http-client-apache-v2:jar:sources:1.39.2", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "org.apache.httpcomponents:httpcore", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.http-client:google-http-client", + "org.apache.httpcomponents:httpclient" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/http-client/google-http-client-apache-v2/1.39.2/google-http-client-apache-v2-1.39.2-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-apache-v2/1.39.2/google-http-client-apache-v2-1.39.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/http-client/google-http-client-apache-v2/1.39.2/google-http-client-apache-v2-1.39.2-sources.jar", + "https://jcenter.bintray.com/com/google/http-client/google-http-client-apache-v2/1.39.2/google-http-client-apache-v2-1.39.2-sources.jar", + "https://maven.google.com/com/google/http-client/google-http-client-apache-v2/1.39.2/google-http-client-apache-v2-1.39.2-sources.jar" + ], + "sha256": "b90287869755fe7e3e095fbe0d7354feebfa70501e7e122283b11be4b8fe82f6", + "url": "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-apache-v2/1.39.2/google-http-client-apache-v2-1.39.2-sources.jar" }, { - "coord": "com.google.http-client:google-http-client-appengine:1.37.0", + "coord": "com.google.http-client:google-http-client-appengine:1.39.2", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "com.google.appengine:appengine-api-1.0-sdk", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", + "com.google.api:gax-grpc", "com.google.http-client:google-http-client" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/http-client/google-http-client-appengine/1.37.0/google-http-client-appengine-1.37.0.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/http-client/google-http-client-appengine/1.39.2/google-http-client-appengine-1.39.2.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-appengine/1.37.0/google-http-client-appengine-1.37.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/http-client/google-http-client-appengine/1.37.0/google-http-client-appengine-1.37.0.jar", - "https://jcenter.bintray.com/com/google/http-client/google-http-client-appengine/1.37.0/google-http-client-appengine-1.37.0.jar", - "https://maven.google.com/com/google/http-client/google-http-client-appengine/1.37.0/google-http-client-appengine-1.37.0.jar" + "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-appengine/1.39.2/google-http-client-appengine-1.39.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/http-client/google-http-client-appengine/1.39.2/google-http-client-appengine-1.39.2.jar", + "https://jcenter.bintray.com/com/google/http-client/google-http-client-appengine/1.39.2/google-http-client-appengine-1.39.2.jar", + "https://maven.google.com/com/google/http-client/google-http-client-appengine/1.39.2/google-http-client-appengine-1.39.2.jar" ], - "sha256": "bba2c30a01b253224a677ed834af618a739d1931764f643a92d7e8e48e4d68fd", - "url": "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-appengine/1.37.0/google-http-client-appengine-1.37.0.jar" + "sha256": "f6320dc0db035aff6eab2e28748560da718ab465fbc3481b653ea3f27aa5cac9", + "url": "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-appengine/1.39.2/google-http-client-appengine-1.39.2.jar" }, { - "coord": "com.google.http-client:google-http-client-appengine:jar:sources:1.37.0", + "coord": "com.google.http-client:google-http-client-appengine:jar:sources:1.39.2", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "com.google.appengine:appengine-api-1.0-sdk", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", + "com.google.api:gax-grpc", "com.google.http-client:google-http-client" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/http-client/google-http-client-appengine/1.37.0/google-http-client-appengine-1.37.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/http-client/google-http-client-appengine/1.39.2/google-http-client-appengine-1.39.2-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-appengine/1.37.0/google-http-client-appengine-1.37.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/http-client/google-http-client-appengine/1.37.0/google-http-client-appengine-1.37.0-sources.jar", - "https://jcenter.bintray.com/com/google/http-client/google-http-client-appengine/1.37.0/google-http-client-appengine-1.37.0-sources.jar", - "https://maven.google.com/com/google/http-client/google-http-client-appengine/1.37.0/google-http-client-appengine-1.37.0-sources.jar" + "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-appengine/1.39.2/google-http-client-appengine-1.39.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/http-client/google-http-client-appengine/1.39.2/google-http-client-appengine-1.39.2-sources.jar", + "https://jcenter.bintray.com/com/google/http-client/google-http-client-appengine/1.39.2/google-http-client-appengine-1.39.2-sources.jar", + "https://maven.google.com/com/google/http-client/google-http-client-appengine/1.39.2/google-http-client-appengine-1.39.2-sources.jar" ], - "sha256": "e46e62a803f25cbd89e031afba01726f70fdc0d887e87edf925601c0fed1c785", - "url": "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-appengine/1.37.0/google-http-client-appengine-1.37.0-sources.jar" + "sha256": "19886f930139b518ad1eac4353734c20e7f9b2f9c955d9299f5d22fa0f530693", + "url": "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-appengine/1.39.2/google-http-client-appengine-1.39.2-sources.jar" }, { - "coord": "com.google.http-client:google-http-client-jackson2:1.37.0", + "coord": "com.google.http-client:google-http-client-gson:1.40.0", "dependencies": [ "com.google.j2objc:j2objc-annotations:1.3", "commons-logging:commons-logging:1.2", - "io.opencensus:opencensus-contrib-http-util:0.24.0", "com.google.code.findbugs:jsr305:3.0.2", - "commons-codec:commons-codec:1.11", - "io.opencensus:opencensus-api:0.24.0", - "org.apache.httpcomponents:httpcore:4.4.13", + "org.apache.httpcomponents:httpcore:4.4.14", + "commons-codec:commons-codec:1.15", "org.apache.httpcomponents:httpclient:4.5.13", - "com.google.http-client:google-http-client:1.37.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "io.opencensus:opencensus-contrib-http-util:0.28.0", + "com.google.code.gson:gson:2.8.8", + "io.opencensus:opencensus-api:0.28.0", + "com.google.http-client:google-http-client:1.40.0" + ], + "directDependencies": [ + "com.google.code.gson:gson:2.8.8", + "com.google.http-client:google-http-client:1.40.0" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/http-client/google-http-client-gson/1.40.0/google-http-client-gson-1.40.0.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-gson/1.40.0/google-http-client-gson-1.40.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/http-client/google-http-client-gson/1.40.0/google-http-client-gson-1.40.0.jar", + "https://jcenter.bintray.com/com/google/http-client/google-http-client-gson/1.40.0/google-http-client-gson-1.40.0.jar", + "https://maven.google.com/com/google/http-client/google-http-client-gson/1.40.0/google-http-client-gson-1.40.0.jar" + ], + "sha256": "6c5c98d468e16edc728e47c3d3486c69c407daeec247bad9ebfeeedae44aa4a9", + "url": "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-gson/1.40.0/google-http-client-gson-1.40.0.jar" + }, + { + "coord": "com.google.http-client:google-http-client-gson:jar:sources:1.40.0", + "dependencies": [ + "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", + "com.google.code.gson:gson:jar:sources:2.8.8", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "commons-codec:commons-codec:jar:sources:1.15", + "commons-logging:commons-logging:jar:sources:1.2", + "com.google.http-client:google-http-client:jar:sources:1.40.0", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "org.apache.httpcomponents:httpcore:jar:sources:4.4.14" ], "directDependencies": [ - "com.fasterxml.jackson.core:jackson-core:2.11.3", - "com.google.http-client:google-http-client:1.37.0" + "com.google.code.gson:gson:jar:sources:2.8.8", + "com.google.http-client:google-http-client:jar:sources:1.40.0" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], + "file": "v1/https/repo1.maven.org/maven2/com/google/http-client/google-http-client-gson/1.40.0/google-http-client-gson-1.40.0-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-gson/1.40.0/google-http-client-gson-1.40.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/http-client/google-http-client-gson/1.40.0/google-http-client-gson-1.40.0-sources.jar", + "https://jcenter.bintray.com/com/google/http-client/google-http-client-gson/1.40.0/google-http-client-gson-1.40.0-sources.jar", + "https://maven.google.com/com/google/http-client/google-http-client-gson/1.40.0/google-http-client-gson-1.40.0-sources.jar" + ], + "sha256": "d015af3addf03417dbf167b9140c2fc71823a69a1e189c3a95c8e871def20aa1", + "url": "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-gson/1.40.0/google-http-client-gson-1.40.0-sources.jar" + }, + { + "coord": "com.google.http-client:google-http-client-jackson2:1.39.2", + "dependencies": [], + "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "com.fasterxml.jackson.core:jackson-core", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.http-client:google-http-client" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/http-client/google-http-client-jackson2/1.37.0/google-http-client-jackson2-1.37.0.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/http-client/google-http-client-jackson2/1.39.2/google-http-client-jackson2-1.39.2.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-jackson2/1.37.0/google-http-client-jackson2-1.37.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/http-client/google-http-client-jackson2/1.37.0/google-http-client-jackson2-1.37.0.jar", - "https://jcenter.bintray.com/com/google/http-client/google-http-client-jackson2/1.37.0/google-http-client-jackson2-1.37.0.jar", - "https://maven.google.com/com/google/http-client/google-http-client-jackson2/1.37.0/google-http-client-jackson2-1.37.0.jar" + "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-jackson2/1.39.2/google-http-client-jackson2-1.39.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/http-client/google-http-client-jackson2/1.39.2/google-http-client-jackson2-1.39.2.jar", + "https://jcenter.bintray.com/com/google/http-client/google-http-client-jackson2/1.39.2/google-http-client-jackson2-1.39.2.jar", + "https://maven.google.com/com/google/http-client/google-http-client-jackson2/1.39.2/google-http-client-jackson2-1.39.2.jar" ], - "sha256": "f779817a1e972443952a0e996fcbee4e83fa863437d6dce3b9af612ab0e3ed5c", - "url": "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-jackson2/1.37.0/google-http-client-jackson2-1.37.0.jar" + "sha256": "74a493a7ba25e1dfb3d3a4ddf713306d5a3786db00a3489d59061dc8cff321b1", + "url": "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-jackson2/1.39.2/google-http-client-jackson2-1.39.2.jar" }, { - "coord": "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", + "coord": "com.google.http-client:google-http-client-jackson2:jar:sources:1.39.2", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "com.fasterxml.jackson.core:jackson-core", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", + "com.google.api:gax-grpc", "com.google.http-client:google-http-client" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/http-client/google-http-client-jackson2/1.37.0/google-http-client-jackson2-1.37.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/http-client/google-http-client-jackson2/1.39.2/google-http-client-jackson2-1.39.2-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-jackson2/1.37.0/google-http-client-jackson2-1.37.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/http-client/google-http-client-jackson2/1.37.0/google-http-client-jackson2-1.37.0-sources.jar", - "https://jcenter.bintray.com/com/google/http-client/google-http-client-jackson2/1.37.0/google-http-client-jackson2-1.37.0-sources.jar", - "https://maven.google.com/com/google/http-client/google-http-client-jackson2/1.37.0/google-http-client-jackson2-1.37.0-sources.jar" + "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-jackson2/1.39.2/google-http-client-jackson2-1.39.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/http-client/google-http-client-jackson2/1.39.2/google-http-client-jackson2-1.39.2-sources.jar", + "https://jcenter.bintray.com/com/google/http-client/google-http-client-jackson2/1.39.2/google-http-client-jackson2-1.39.2-sources.jar", + "https://maven.google.com/com/google/http-client/google-http-client-jackson2/1.39.2/google-http-client-jackson2-1.39.2-sources.jar" ], - "sha256": "88849cc8e0b6f5e9cd0692908360b9f9405c74fe5379f896db72c6691b248bdc", - "url": "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-jackson2/1.37.0/google-http-client-jackson2-1.37.0-sources.jar" + "sha256": "e740303182032f07916b59e6697c0ba033d9c8f4ac2b171c943465637d53e092", + "url": "https://repo1.maven.org/maven2/com/google/http-client/google-http-client-jackson2/1.39.2/google-http-client-jackson2-1.39.2-sources.jar" }, { - "coord": "com.google.http-client:google-http-client:1.37.0", + "coord": "com.google.http-client:google-http-client:1.40.0", "dependencies": [ "com.google.j2objc:j2objc-annotations:1.3", "commons-logging:commons-logging:1.2", - "io.opencensus:opencensus-contrib-http-util:0.24.0", "com.google.code.findbugs:jsr305:3.0.2", - "commons-codec:commons-codec:1.11", - "io.opencensus:opencensus-api:0.24.0", - "org.apache.httpcomponents:httpcore:4.4.13", - "org.apache.httpcomponents:httpclient:4.5.13" + "org.apache.httpcomponents:httpcore:4.4.14", + "commons-codec:commons-codec:1.15", + "org.apache.httpcomponents:httpclient:4.5.13", + "io.opencensus:opencensus-contrib-http-util:0.28.0", + "io.opencensus:opencensus-api:0.28.0" ], "directDependencies": [ "com.google.j2objc:j2objc-annotations:1.3", - "io.opencensus:opencensus-contrib-http-util:0.24.0", "com.google.code.findbugs:jsr305:3.0.2", - "io.opencensus:opencensus-api:0.24.0", - "org.apache.httpcomponents:httpcore:4.4.13", - "org.apache.httpcomponents:httpclient:4.5.13" + "org.apache.httpcomponents:httpcore:4.4.14", + "org.apache.httpcomponents:httpclient:4.5.13", + "io.opencensus:opencensus-contrib-http-util:0.28.0", + "io.opencensus:opencensus-api:0.28.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/http-client/google-http-client/1.37.0/google-http-client-1.37.0.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/http-client/google-http-client/1.40.0/google-http-client-1.40.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/http-client/google-http-client/1.37.0/google-http-client-1.37.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/http-client/google-http-client/1.37.0/google-http-client-1.37.0.jar", - "https://jcenter.bintray.com/com/google/http-client/google-http-client/1.37.0/google-http-client-1.37.0.jar", - "https://maven.google.com/com/google/http-client/google-http-client/1.37.0/google-http-client-1.37.0.jar" + "https://repo1.maven.org/maven2/com/google/http-client/google-http-client/1.40.0/google-http-client-1.40.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/http-client/google-http-client/1.40.0/google-http-client-1.40.0.jar", + "https://jcenter.bintray.com/com/google/http-client/google-http-client/1.40.0/google-http-client-1.40.0.jar", + "https://maven.google.com/com/google/http-client/google-http-client/1.40.0/google-http-client-1.40.0.jar" ], - "sha256": "a5498ef562f6e8c3143673f9ae7976692a882a58baa1e6e74d4d32bbbb114331", - "url": "https://repo1.maven.org/maven2/com/google/http-client/google-http-client/1.37.0/google-http-client-1.37.0.jar" + "sha256": "b8cf1d7fe650a7c1a6072fe882b2c2063eab7f6d3b7475dd39122d27f9d73136", + "url": "https://repo1.maven.org/maven2/com/google/http-client/google-http-client/1.40.0/google-http-client-1.40.0.jar" }, { - "coord": "com.google.http-client:google-http-client:jar:sources:1.37.0", + "coord": "com.google.http-client:google-http-client:jar:sources:1.40.0", "dependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "commons-codec:commons-codec:jar:sources:1.15", "commons-logging:commons-logging:jar:sources:1.2", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "commons-codec:commons-codec:jar:sources:1.11", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13" + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "org.apache.httpcomponents:httpcore:jar:sources:4.4.14", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-context:jar:sources:1.38.1" ], "directDependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13" + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "org.apache.httpcomponents:httpcore:jar:sources:4.4.14" ], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/http-client/google-http-client/1.37.0/google-http-client-1.37.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/http-client/google-http-client/1.40.0/google-http-client-1.40.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/http-client/google-http-client/1.37.0/google-http-client-1.37.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/http-client/google-http-client/1.37.0/google-http-client-1.37.0-sources.jar", - "https://jcenter.bintray.com/com/google/http-client/google-http-client/1.37.0/google-http-client-1.37.0-sources.jar", - "https://maven.google.com/com/google/http-client/google-http-client/1.37.0/google-http-client-1.37.0-sources.jar" + "https://repo1.maven.org/maven2/com/google/http-client/google-http-client/1.40.0/google-http-client-1.40.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/http-client/google-http-client/1.40.0/google-http-client-1.40.0-sources.jar", + "https://jcenter.bintray.com/com/google/http-client/google-http-client/1.40.0/google-http-client-1.40.0-sources.jar", + "https://maven.google.com/com/google/http-client/google-http-client/1.40.0/google-http-client-1.40.0-sources.jar" ], - "sha256": "53ee36ed92a531bec14fef8d4ecc9d8b5eacb29b500ed8c3fd0504417cd2a34e", - "url": "https://repo1.maven.org/maven2/com/google/http-client/google-http-client/1.37.0/google-http-client-1.37.0-sources.jar" + "sha256": "70e1e456040265b739d9e1f1943b3fef60a202126e25e2301785c84b5e4d981b", + "url": "https://repo1.maven.org/maven2/com/google/http-client/google-http-client/1.40.0/google-http-client-1.40.0-sources.jar" }, { "coord": "com.google.j2objc:j2objc-annotations:1.3", @@ -4072,193 +5548,522 @@ "url": "https://repo1.maven.org/maven2/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3-sources.jar" }, { - "coord": "com.google.oauth-client:google-oauth-client:1.31.1", + "coord": "com.google.oauth-client:google-oauth-client:1.31.5", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.http-client:google-http-client" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/oauth-client/google-oauth-client/1.31.5/google-oauth-client-1.31.5.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/oauth-client/google-oauth-client/1.31.5/google-oauth-client-1.31.5.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/oauth-client/google-oauth-client/1.31.5/google-oauth-client-1.31.5.jar", + "https://jcenter.bintray.com/com/google/oauth-client/google-oauth-client/1.31.5/google-oauth-client-1.31.5.jar", + "https://maven.google.com/com/google/oauth-client/google-oauth-client/1.31.5/google-oauth-client-1.31.5.jar" + ], + "sha256": "f827130fb11f8d4be9cd4a3a34167fe1b83071b6096d26a51a9f79494393ea8d", + "url": "https://repo1.maven.org/maven2/com/google/oauth-client/google-oauth-client/1.31.5/google-oauth-client-1.31.5.jar" + }, + { + "coord": "com.google.oauth-client:google-oauth-client:jar:sources:1.31.5", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.guava:guava", + "com.google.template:soy", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc", + "com.google.http-client:google-http-client" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/oauth-client/google-oauth-client/1.31.5/google-oauth-client-1.31.5-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/oauth-client/google-oauth-client/1.31.5/google-oauth-client-1.31.5-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/oauth-client/google-oauth-client/1.31.5/google-oauth-client-1.31.5-sources.jar", + "https://jcenter.bintray.com/com/google/oauth-client/google-oauth-client/1.31.5/google-oauth-client-1.31.5-sources.jar", + "https://maven.google.com/com/google/oauth-client/google-oauth-client/1.31.5/google-oauth-client-1.31.5-sources.jar" + ], + "sha256": "40bd20602b9089e24436fca18828306145f40bcdf2417a7b6aac4289e137314e", + "url": "https://repo1.maven.org/maven2/com/google/oauth-client/google-oauth-client/1.31.5/google-oauth-client-1.31.5-sources.jar" + }, + { + "coord": "com.google.protobuf:protobuf-java-util:3.17.3", + "dependencies": [ + "com.google.code.gson:gson:2.8.8", + "com.google.protobuf:protobuf-java:3.17.3" + ], + "directDependencies": [ + "com.google.code.gson:gson:2.8.8", + "com.google.protobuf:protobuf-java:3.17.3" + ], + "exclusions": [ + "com.google.guava:guava", + "com.google.errorprone:error_prone_annotations", + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3.jar", + "https://jcenter.bintray.com/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3.jar", + "https://maven.google.com/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3.jar" + ], + "sha256": "bf320ed076000e1d8c7cbf7601b056acaecab80f75b9a659b9f6398d0d7e3f79", + "url": "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3.jar" + }, + { + "coord": "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "dependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.code.gson:gson:jar:sources:2.8.8", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0" + ], + "directDependencies": [ + "com.google.code.gson:gson:jar:sources:2.8.8", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "com.google.guava:guava:jar:sources:30.1.1-android", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.template:soy", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3-sources.jar", + "https://jcenter.bintray.com/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3-sources.jar", + "https://maven.google.com/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3-sources.jar" + ], + "sha256": "4046612802edfa6f9e201b2a53d10439a4ebbab5324ae415874e041cd1d70bbf", + "url": "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.17.3/protobuf-java-util-3.17.3-sources.jar" + }, + { + "coord": "com.google.protobuf:protobuf-java:3.17.3", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", - "com.google.code.findbugs:jsr305", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", - "com.google.http-client:google-http-client" + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/oauth-client/google-oauth-client/1.31.1/google-oauth-client-1.31.1.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.17.3/protobuf-java-3.17.3.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/oauth-client/google-oauth-client/1.31.1/google-oauth-client-1.31.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/oauth-client/google-oauth-client/1.31.1/google-oauth-client-1.31.1.jar", - "https://jcenter.bintray.com/com/google/oauth-client/google-oauth-client/1.31.1/google-oauth-client-1.31.1.jar", - "https://maven.google.com/com/google/oauth-client/google-oauth-client/1.31.1/google-oauth-client-1.31.1.jar" + "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.17.3/protobuf-java-3.17.3.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/protobuf/protobuf-java/3.17.3/protobuf-java-3.17.3.jar", + "https://jcenter.bintray.com/com/google/protobuf/protobuf-java/3.17.3/protobuf-java-3.17.3.jar", + "https://maven.google.com/com/google/protobuf/protobuf-java/3.17.3/protobuf-java-3.17.3.jar" ], - "sha256": "4ed4e2948251dbda66ce251bd7f3b32cd8570055e5cdb165a3c7aea8f43da0ff", - "url": "https://repo1.maven.org/maven2/com/google/oauth-client/google-oauth-client/1.31.1/google-oauth-client-1.31.1.jar" + "sha256": "4ac549b192694141958049f060a1c826a33342f619e108ced8c17d9877f5e3ed", + "url": "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.17.3/protobuf-java-3.17.3.jar" }, { - "coord": "com.google.oauth-client:google-oauth-client:jar:sources:1.31.1", + "coord": "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", - "com.google.code.findbugs:jsr305", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", + "io.grpc:grpc-alts", "io.grpc:grpc-core", - "com.google.http-client:google-http-client" + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/oauth-client/google-oauth-client/1.31.1/google-oauth-client-1.31.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.17.3/protobuf-java-3.17.3-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/oauth-client/google-oauth-client/1.31.1/google-oauth-client-1.31.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/oauth-client/google-oauth-client/1.31.1/google-oauth-client-1.31.1-sources.jar", - "https://jcenter.bintray.com/com/google/oauth-client/google-oauth-client/1.31.1/google-oauth-client-1.31.1-sources.jar", - "https://maven.google.com/com/google/oauth-client/google-oauth-client/1.31.1/google-oauth-client-1.31.1-sources.jar" + "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.17.3/protobuf-java-3.17.3-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/protobuf/protobuf-java/3.17.3/protobuf-java-3.17.3-sources.jar", + "https://jcenter.bintray.com/com/google/protobuf/protobuf-java/3.17.3/protobuf-java-3.17.3-sources.jar", + "https://maven.google.com/com/google/protobuf/protobuf-java/3.17.3/protobuf-java-3.17.3-sources.jar" ], - "sha256": "2cfa242f572b814077919b9b80087996dfa141c65877950073daeae6eef53e20", - "url": "https://repo1.maven.org/maven2/com/google/oauth-client/google-oauth-client/1.31.1/google-oauth-client-1.31.1-sources.jar" + "sha256": "204bad0a36827296ed0b6fdbdf1a17028f3e3d289dd20042980331b8f0ef646e", + "url": "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.17.3/protobuf-java-3.17.3-sources.jar" }, { - "coord": "com.google.protobuf:protobuf-java-util:3.13.0", + "coord": "com.google.truth.extensions:truth-java8-extension:1.1.3", "dependencies": [ - "com.google.code.gson:gson:2.8.6", - "com.google.protobuf:protobuf-java:3.13.0" + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", + "com.google.truth:truth:1.1.3", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.guava:guava:30.1.1-android", + "org.hamcrest:hamcrest-core:1.3", + "org.checkerframework:checker-qual:3.13.0", + "org.ow2.asm:asm:9.2", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.guava:failureaccess:1.0.1", + "junit:junit:4.13.2", + "org.checkerframework:checker-compat-qual:2.5.5" ], "directDependencies": [ - "com.google.code.gson:gson:2.8.6", - "com.google.protobuf:protobuf-java:3.13.0" + "com.google.truth:truth:1.1.3", + "org.checkerframework:checker-qual:3.13.0" ], "exclusions": [ - "com.google.guava:guava", - "com.google.errorprone:error_prone_annotations", "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.13.0/protobuf-java-util-3.13.0.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/truth/extensions/truth-java8-extension/1.1.3/truth-java8-extension-1.1.3.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.13.0/protobuf-java-util-3.13.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/protobuf/protobuf-java-util/3.13.0/protobuf-java-util-3.13.0.jar", - "https://jcenter.bintray.com/com/google/protobuf/protobuf-java-util/3.13.0/protobuf-java-util-3.13.0.jar", - "https://maven.google.com/com/google/protobuf/protobuf-java-util/3.13.0/protobuf-java-util-3.13.0.jar" + "https://repo1.maven.org/maven2/com/google/truth/extensions/truth-java8-extension/1.1.3/truth-java8-extension-1.1.3.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/truth/extensions/truth-java8-extension/1.1.3/truth-java8-extension-1.1.3.jar", + "https://jcenter.bintray.com/com/google/truth/extensions/truth-java8-extension/1.1.3/truth-java8-extension-1.1.3.jar", + "https://maven.google.com/com/google/truth/extensions/truth-java8-extension/1.1.3/truth-java8-extension-1.1.3.jar" ], - "sha256": "d9de66b8c9445905dfa7064f6d5213d47ce88a20d34e21d83c4a94a229e14e62", - "url": "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.13.0/protobuf-java-util-3.13.0.jar" + "sha256": "2bbd32dd2fa9470d17f1bbda4f52b33b60bce4574052c1d46610a0aa371fc446", + "url": "https://repo1.maven.org/maven2/com/google/truth/extensions/truth-java8-extension/1.1.3/truth-java8-extension-1.1.3.jar" }, { - "coord": "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", + "coord": "com.google.truth.extensions:truth-java8-extension:jar:sources:1.1.3", "dependencies": [ + "org.ow2.asm:asm:jar:sources:9.2", + "com.google.guava:guava:jar:sources:30.1.1-android", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "com.google.code.gson:gson:jar:sources:2.8.6", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "junit:junit:jar:sources:4.13.2", + "org.hamcrest:hamcrest-core:jar:sources:1.3", + "com.google.truth:truth:jar:sources:1.1.3", "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.guava:guava:jar:sources:30.0-android", - "com.google.guava:failureaccess:jar:sources:1.0.1" + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0" ], "directDependencies": [ - "com.google.code.gson:gson:jar:sources:2.8.6", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.guava:guava:jar:sources:30.0-android", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0" + "com.google.truth:truth:jar:sources:1.1.3", + "org.checkerframework:checker-qual:jar:sources:3.13.0" ], "exclusions": [ "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.13.0/protobuf-java-util-3.13.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/truth/extensions/truth-java8-extension/1.1.3/truth-java8-extension-1.1.3-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.13.0/protobuf-java-util-3.13.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/protobuf/protobuf-java-util/3.13.0/protobuf-java-util-3.13.0-sources.jar", - "https://jcenter.bintray.com/com/google/protobuf/protobuf-java-util/3.13.0/protobuf-java-util-3.13.0-sources.jar", - "https://maven.google.com/com/google/protobuf/protobuf-java-util/3.13.0/protobuf-java-util-3.13.0-sources.jar" + "https://repo1.maven.org/maven2/com/google/truth/extensions/truth-java8-extension/1.1.3/truth-java8-extension-1.1.3-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/truth/extensions/truth-java8-extension/1.1.3/truth-java8-extension-1.1.3-sources.jar", + "https://jcenter.bintray.com/com/google/truth/extensions/truth-java8-extension/1.1.3/truth-java8-extension-1.1.3-sources.jar", + "https://maven.google.com/com/google/truth/extensions/truth-java8-extension/1.1.3/truth-java8-extension-1.1.3-sources.jar" ], - "sha256": "6ebe9b3fb6ab4f5aebf7bf72fc0b7fb967b76f6d94bdc347b7fcdf32ef25dd97", - "url": "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.13.0/protobuf-java-util-3.13.0-sources.jar" + "sha256": "1eeb6d2962aa1f9baa30cb90c3671a81b60bb0a7705ad4d873f62c344b935a1a", + "url": "https://repo1.maven.org/maven2/com/google/truth/extensions/truth-java8-extension/1.1.3/truth-java8-extension-1.1.3-sources.jar" }, { - "coord": "com.google.protobuf:protobuf-java:3.13.0", - "dependencies": [], - "directDependencies": [], + "coord": "com.google.truth.extensions:truth-liteproto-extension:1.1.3", + "dependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", + "com.google.truth:truth:1.1.3", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.guava:guava:30.1.1-android", + "org.hamcrest:hamcrest-core:1.3", + "org.checkerframework:checker-qual:3.13.0", + "org.ow2.asm:asm:9.2", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.guava:failureaccess:1.0.1", + "junit:junit:4.13.2", + "org.checkerframework:checker-compat-qual:2.5.5" + ], + "directDependencies": [ + "com.google.truth:truth:1.1.3", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.guava:guava:30.1.1-android", + "org.checkerframework:checker-qual:3.13.0", + "com.google.errorprone:error_prone_annotations:2.9.0" + ], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.13.0/protobuf-java-3.13.0.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/truth/extensions/truth-liteproto-extension/1.1.3/truth-liteproto-extension-1.1.3.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.13.0/protobuf-java-3.13.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/protobuf/protobuf-java/3.13.0/protobuf-java-3.13.0.jar", - "https://jcenter.bintray.com/com/google/protobuf/protobuf-java/3.13.0/protobuf-java-3.13.0.jar", - "https://maven.google.com/com/google/protobuf/protobuf-java/3.13.0/protobuf-java-3.13.0.jar" + "https://repo1.maven.org/maven2/com/google/truth/extensions/truth-liteproto-extension/1.1.3/truth-liteproto-extension-1.1.3.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/truth/extensions/truth-liteproto-extension/1.1.3/truth-liteproto-extension-1.1.3.jar", + "https://jcenter.bintray.com/com/google/truth/extensions/truth-liteproto-extension/1.1.3/truth-liteproto-extension-1.1.3.jar", + "https://maven.google.com/com/google/truth/extensions/truth-liteproto-extension/1.1.3/truth-liteproto-extension-1.1.3.jar" ], - "sha256": "97d5b2758408690c0dc276238707492a0b6a4d71206311b6c442cdc26c5973ff", - "url": "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.13.0/protobuf-java-3.13.0.jar" + "sha256": "71cce6284554e546d1b5ba48e310ee4b4050676f09fb0eced136d779284ff78d", + "url": "https://repo1.maven.org/maven2/com/google/truth/extensions/truth-liteproto-extension/1.1.3/truth-liteproto-extension-1.1.3.jar" }, { - "coord": "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "dependencies": [], - "directDependencies": [], + "coord": "com.google.truth.extensions:truth-liteproto-extension:jar:sources:1.1.3", + "dependencies": [ + "org.ow2.asm:asm:jar:sources:9.2", + "com.google.guava:guava:jar:sources:30.1.1-android", + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "junit:junit:jar:sources:4.13.2", + "org.hamcrest:hamcrest-core:jar:sources:1.3", + "com.google.truth:truth:jar:sources:1.1.3", + "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0" + ], + "directDependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "com.google.truth:truth:jar:sources:1.1.3", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0" + ], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/truth/extensions/truth-liteproto-extension/1.1.3/truth-liteproto-extension-1.1.3-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/truth/extensions/truth-liteproto-extension/1.1.3/truth-liteproto-extension-1.1.3-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/truth/extensions/truth-liteproto-extension/1.1.3/truth-liteproto-extension-1.1.3-sources.jar", + "https://jcenter.bintray.com/com/google/truth/extensions/truth-liteproto-extension/1.1.3/truth-liteproto-extension-1.1.3-sources.jar", + "https://maven.google.com/com/google/truth/extensions/truth-liteproto-extension/1.1.3/truth-liteproto-extension-1.1.3-sources.jar" + ], + "sha256": "fd3c26b2232966a3ff25e5b9c642f9ae9f19c86b29dfeb6e72aeb67e45e36130", + "url": "https://repo1.maven.org/maven2/com/google/truth/extensions/truth-liteproto-extension/1.1.3/truth-liteproto-extension-1.1.3-sources.jar" + }, + { + "coord": "com.google.truth.extensions:truth-proto-extension:1.1.3", + "dependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", + "com.google.truth:truth:1.1.3", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.guava:guava:30.1.1-android", + "org.hamcrest:hamcrest-core:1.3", + "org.checkerframework:checker-qual:3.13.0", + "org.ow2.asm:asm:9.2", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "com.google.truth.extensions:truth-liteproto-extension:1.1.3", + "com.google.guava:failureaccess:1.0.1", + "junit:junit:4.13.2", + "org.checkerframework:checker-compat-qual:2.5.5" + ], + "directDependencies": [ + "com.google.truth:truth:1.1.3", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.guava:guava:30.1.1-android", + "org.checkerframework:checker-qual:3.13.0", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "com.google.truth.extensions:truth-liteproto-extension:1.1.3" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/truth/extensions/truth-proto-extension/1.1.3/truth-proto-extension-1.1.3.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/truth/extensions/truth-proto-extension/1.1.3/truth-proto-extension-1.1.3.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/truth/extensions/truth-proto-extension/1.1.3/truth-proto-extension-1.1.3.jar", + "https://jcenter.bintray.com/com/google/truth/extensions/truth-proto-extension/1.1.3/truth-proto-extension-1.1.3.jar", + "https://maven.google.com/com/google/truth/extensions/truth-proto-extension/1.1.3/truth-proto-extension-1.1.3.jar" + ], + "sha256": "821993e4794e7034ae4a7b68105ef83f1913f0de6112f2fe4b5a7130f6a2bf49", + "url": "https://repo1.maven.org/maven2/com/google/truth/extensions/truth-proto-extension/1.1.3/truth-proto-extension-1.1.3.jar" + }, + { + "coord": "com.google.truth.extensions:truth-proto-extension:jar:sources:1.1.3", + "dependencies": [ + "org.ow2.asm:asm:jar:sources:9.2", + "com.google.guava:guava:jar:sources:30.1.1-android", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "com.google.truth.extensions:truth-liteproto-extension:jar:sources:1.1.3", + "junit:junit:jar:sources:4.13.2", + "org.hamcrest:hamcrest-core:jar:sources:1.3", + "com.google.truth:truth:jar:sources:1.1.3", + "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0" + ], + "directDependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "com.google.truth.extensions:truth-liteproto-extension:jar:sources:1.1.3", + "com.google.truth:truth:jar:sources:1.1.3", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/truth/extensions/truth-proto-extension/1.1.3/truth-proto-extension-1.1.3-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/truth/extensions/truth-proto-extension/1.1.3/truth-proto-extension-1.1.3-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/truth/extensions/truth-proto-extension/1.1.3/truth-proto-extension-1.1.3-sources.jar", + "https://jcenter.bintray.com/com/google/truth/extensions/truth-proto-extension/1.1.3/truth-proto-extension-1.1.3-sources.jar", + "https://maven.google.com/com/google/truth/extensions/truth-proto-extension/1.1.3/truth-proto-extension-1.1.3-sources.jar" + ], + "sha256": "ce56b0d96c959a12f426cb7279eacfaee1d654767136f0e1d054b56fcf564849", + "url": "https://repo1.maven.org/maven2/com/google/truth/extensions/truth-proto-extension/1.1.3/truth-proto-extension-1.1.3-sources.jar" + }, + { + "coord": "com.google.truth:truth:1.1.3", + "dependencies": [ + "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.j2objc:j2objc-annotations:1.3", + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.guava:guava:30.1.1-android", + "org.hamcrest:hamcrest-core:1.3", + "org.checkerframework:checker-qual:3.13.0", + "org.ow2.asm:asm:9.2", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.guava:failureaccess:1.0.1", + "junit:junit:4.13.2", + "org.checkerframework:checker-compat-qual:2.5.5" + ], + "directDependencies": [ + "com.google.auto.value:auto-value-annotations:1.8.2", + "com.google.guava:guava:30.1.1-android", + "org.checkerframework:checker-qual:3.13.0", + "org.ow2.asm:asm:9.2", + "com.google.errorprone:error_prone_annotations:2.9.0", + "junit:junit:4.13.2" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/com/google/truth/truth/1.1.3/truth-1.1.3.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/google/truth/truth/1.1.3/truth-1.1.3.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/truth/truth/1.1.3/truth-1.1.3.jar", + "https://jcenter.bintray.com/com/google/truth/truth/1.1.3/truth-1.1.3.jar", + "https://maven.google.com/com/google/truth/truth/1.1.3/truth-1.1.3.jar" + ], + "sha256": "fc0b67782289a2aabfddfdf99eff1dcd5edc890d49143fcd489214b107b8f4f3", + "url": "https://repo1.maven.org/maven2/com/google/truth/truth/1.1.3/truth-1.1.3.jar" + }, + { + "coord": "com.google.truth:truth:jar:sources:1.1.3", + "dependencies": [ + "org.ow2.asm:asm:jar:sources:9.2", + "com.google.guava:guava:jar:sources:30.1.1-android", + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "junit:junit:jar:sources:4.13.2", + "org.hamcrest:hamcrest-core:jar:sources:1.3", + "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", + "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.guava:failureaccess:jar:sources:1.0.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0" + ], + "directDependencies": [ + "org.ow2.asm:asm:jar:sources:9.2", + "com.google.guava:guava:jar:sources:30.1.1-android", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "junit:junit:jar:sources:4.13.2", + "com.google.auto.value:auto-value-annotations:jar:sources:1.8.2", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.13.0/protobuf-java-3.13.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/com/google/truth/truth/1.1.3/truth-1.1.3-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.13.0/protobuf-java-3.13.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/com/google/protobuf/protobuf-java/3.13.0/protobuf-java-3.13.0-sources.jar", - "https://jcenter.bintray.com/com/google/protobuf/protobuf-java/3.13.0/protobuf-java-3.13.0-sources.jar", - "https://maven.google.com/com/google/protobuf/protobuf-java/3.13.0/protobuf-java-3.13.0-sources.jar" + "https://repo1.maven.org/maven2/com/google/truth/truth/1.1.3/truth-1.1.3-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/com/google/truth/truth/1.1.3/truth-1.1.3-sources.jar", + "https://jcenter.bintray.com/com/google/truth/truth/1.1.3/truth-1.1.3-sources.jar", + "https://maven.google.com/com/google/truth/truth/1.1.3/truth-1.1.3-sources.jar" ], - "sha256": "ced0309df90244c977361fa14ebc848ff1fef4910a051b2ee686a18b3840710c", - "url": "https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java/3.13.0/protobuf-java-3.13.0-sources.jar" + "sha256": "6c35e3d7087cd222938b41bbdb54041239b79dda07cf96c4027c118d566df545", + "url": "https://repo1.maven.org/maven2/com/google/truth/truth/1.1.3/truth-1.1.3-sources.jar" }, { "coord": "com.squareup.okhttp3:okhttp:3.11.0", @@ -4377,62 +6182,40 @@ "url": "https://repo1.maven.org/maven2/com/univocity/univocity-parsers/2.8.1/univocity-parsers-2.8.1-sources.jar" }, { - "coord": "commons-codec:commons-codec:1.11", + "coord": "commons-codec:commons-codec:1.15", "dependencies": [], "directDependencies": [], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/commons-codec/commons-codec/1.11/commons-codec-1.11.jar", + "file": "v1/https/repo1.maven.org/maven2/commons-codec/commons-codec/1.15/commons-codec-1.15.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/commons-codec/commons-codec/1.11/commons-codec-1.11.jar", - "https://dl.bintray.com/micronaut/core-releases-local/commons-codec/commons-codec/1.11/commons-codec-1.11.jar", - "https://jcenter.bintray.com/commons-codec/commons-codec/1.11/commons-codec-1.11.jar", - "https://maven.google.com/commons-codec/commons-codec/1.11/commons-codec-1.11.jar" + "https://repo1.maven.org/maven2/commons-codec/commons-codec/1.15/commons-codec-1.15.jar", + "https://dl.bintray.com/micronaut/core-releases-local/commons-codec/commons-codec/1.15/commons-codec-1.15.jar", + "https://jcenter.bintray.com/commons-codec/commons-codec/1.15/commons-codec-1.15.jar", + "https://maven.google.com/commons-codec/commons-codec/1.15/commons-codec-1.15.jar" ], - "sha256": "e599d5318e97aa48f42136a2927e6dfa4e8881dff0e6c8e3109ddbbff51d7b7d", - "url": "https://repo1.maven.org/maven2/commons-codec/commons-codec/1.11/commons-codec-1.11.jar" + "sha256": "b3e9f6d63a790109bf0d056611fbed1cf69055826defeb9894a71369d246ed63", + "url": "https://repo1.maven.org/maven2/commons-codec/commons-codec/1.15/commons-codec-1.15.jar" }, { - "coord": "commons-codec:commons-codec:jar:sources:1.11", + "coord": "commons-codec:commons-codec:jar:sources:1.15", "dependencies": [], "directDependencies": [], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/commons-codec/commons-codec/1.11/commons-codec-1.11-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/commons-codec/commons-codec/1.15/commons-codec-1.15-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/commons-codec/commons-codec/1.11/commons-codec-1.11-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/commons-codec/commons-codec/1.11/commons-codec-1.11-sources.jar", - "https://jcenter.bintray.com/commons-codec/commons-codec/1.11/commons-codec-1.11-sources.jar", - "https://maven.google.com/commons-codec/commons-codec/1.11/commons-codec-1.11-sources.jar" + "https://repo1.maven.org/maven2/commons-codec/commons-codec/1.15/commons-codec-1.15-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/commons-codec/commons-codec/1.15/commons-codec-1.15-sources.jar", + "https://jcenter.bintray.com/commons-codec/commons-codec/1.15/commons-codec-1.15-sources.jar", + "https://maven.google.com/commons-codec/commons-codec/1.15/commons-codec-1.15-sources.jar" ], - "sha256": "901cb5d1f7c2877017c95d3c5efd5a497738d0162ef72cdf58e9cb13f50b2e9c", - "url": "https://repo1.maven.org/maven2/commons-codec/commons-codec/1.11/commons-codec-1.11-sources.jar" + "sha256": "7019940b2298d333edb946e2db3d10f1caacbbd52bb64e85832cfd0017e049cc", + "url": "https://repo1.maven.org/maven2/commons-codec/commons-codec/1.15/commons-codec-1.15-sources.jar" }, { "coord": "commons-io:commons-io:2.6", @@ -4475,23 +6258,8 @@ "dependencies": [], "directDependencies": [], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "logkit:logkit", - "javax.servlet:servlet-api", - "com.google.common.html.types:types", - "avalon-framework:avalon-framework", - "io.grpc:grpc-netty", - "io.grpc:grpc-core", - "log4j:log4j" + "com.google.common.html.types:types" ], "file": "v1/https/repo1.maven.org/maven2/commons-logging/commons-logging/1.2/commons-logging-1.2.jar", "mirror_urls": [ @@ -4508,23 +6276,8 @@ "dependencies": [], "directDependencies": [], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "logkit:logkit", - "javax.servlet:servlet-api", - "com.google.common.html.types:types", - "avalon-framework:avalon-framework", - "io.grpc:grpc-netty", - "io.grpc:grpc-core", - "log4j:log4j" + "com.google.common.html.types:types" ], "file": "v1/https/repo1.maven.org/maven2/commons-logging/commons-logging/1.2/commons-logging-1.2-sources.jar", "mirror_urls": [ @@ -4619,9 +6372,7 @@ { "coord": "io.arrow-kt:arrow-annotations:0.9.0", "dependencies": [ - "org.jetbrains:annotations:13.0", "io.kindedj:kindedj:1.1.0", - "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib:1.4.10" ], "directDependencies": [ @@ -4646,8 +6397,6 @@ "coord": "io.arrow-kt:arrow-annotations:jar:sources:0.9.0", "dependencies": [ "io.kindedj:kindedj:jar:sources:1.1.0", - "org.jetbrains:annotations:jar:sources:13.0", - "org.jetbrains.kotlin:kotlin-stdlib-common:jar:sources:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.4.10" ], "directDependencies": [ @@ -4671,12 +6420,10 @@ { "coord": "io.arrow-kt:arrow-core-data:0.9.0", "dependencies": [ - "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10", - "io.kindedj:kindedj:1.1.0", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10", - "org.jetbrains.kotlin:kotlin-stdlib:1.4.10", + "io.kindedj:kindedj:1.1.0", "io.arrow-kt:arrow-annotations:0.9.0", - "org.jetbrains:annotations:13.0" + "org.jetbrains.kotlin:kotlin-stdlib:1.4.10" ], "directDependencies": [ "io.arrow-kt:arrow-annotations:0.9.0", @@ -4699,12 +6446,10 @@ { "coord": "io.arrow-kt:arrow-core-data:jar:sources:0.9.0", "dependencies": [ - "org.jetbrains:annotations:jar:sources:13.0", - "org.jetbrains.kotlin:kotlin-stdlib-common:jar:sources:1.4.10", - "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.4.10", + "io.kindedj:kindedj:jar:sources:1.1.0", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:jar:sources:1.4.10", - "io.arrow-kt:arrow-annotations:jar:sources:0.9.0", - "io.kindedj:kindedj:jar:sources:1.1.0" + "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.4.10", + "io.arrow-kt:arrow-annotations:jar:sources:0.9.0" ], "directDependencies": [ "io.arrow-kt:arrow-annotations:jar:sources:0.9.0", @@ -4727,14 +6472,12 @@ { "coord": "io.arrow-kt:arrow-core-extensions:0.9.0", "dependencies": [ - "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10", "io.kindedj:kindedj:1.1.0", "io.arrow-kt:arrow-core-data:0.9.0", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib:1.4.10", "io.arrow-kt:arrow-typeclasses:0.9.0", - "io.arrow-kt:arrow-annotations:0.9.0", - "org.jetbrains:annotations:13.0" + "io.arrow-kt:arrow-annotations:0.9.0" ], "directDependencies": [ "io.arrow-kt:arrow-annotations:0.9.0", @@ -4759,8 +6502,6 @@ { "coord": "io.arrow-kt:arrow-core-extensions:jar:sources:0.9.0", "dependencies": [ - "org.jetbrains:annotations:jar:sources:13.0", - "org.jetbrains.kotlin:kotlin-stdlib-common:jar:sources:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:jar:sources:1.4.10", "io.arrow-kt:arrow-typeclasses:jar:sources:0.9.0", @@ -4791,13 +6532,11 @@ { "coord": "io.arrow-kt:arrow-typeclasses:0.9.0", "dependencies": [ - "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10", "io.kindedj:kindedj:1.1.0", "io.arrow-kt:arrow-core-data:0.9.0", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib:1.4.10", - "io.arrow-kt:arrow-annotations:0.9.0", - "org.jetbrains:annotations:13.0" + "io.arrow-kt:arrow-annotations:0.9.0" ], "directDependencies": [ "io.arrow-kt:arrow-annotations:0.9.0", @@ -4821,8 +6560,6 @@ { "coord": "io.arrow-kt:arrow-typeclasses:jar:sources:0.9.0", "dependencies": [ - "org.jetbrains:annotations:jar:sources:13.0", - "org.jetbrains.kotlin:kotlin-stdlib-common:jar:sources:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:jar:sources:1.4.10", "io.arrow-kt:arrow-core-data:jar:sources:0.9.0", @@ -4885,936 +6622,832 @@ "url": "https://repo1.maven.org/maven2/io/github/classgraph/classgraph/4.8.1/classgraph-4.8.1-sources.jar" }, { - "coord": "io.grpc:grpc-alts:1.33.1", + "coord": "io.grpc:grpc-alts:1.38.1", "dependencies": [ "org.conscrypt:conscrypt-openjdk-uber:2.5.1", - "com.google.j2objc:j2objc-annotations:1.3", - "commons-logging:commons-logging:1.2", - "io.opencensus:opencensus-contrib-http-util:0.24.0", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "commons-codec:commons-codec:1.11", - "io.opencensus:opencensus-api:0.24.0", - "com.google.code.gson:gson:2.8.6", - "org.apache.httpcomponents:httpcore:4.4.13", - "com.google.auto.value:auto-value-annotations:1.7.4", - "com.google.protobuf:protobuf-java-util:3.13.0", - "io.grpc:grpc-netty-shaded:1.33.1", - "org.apache.commons:commons-lang3:3.5", - "com.google.auth:google-auth-library-credentials:0.22.0", - "com.google.http-client:google-http-client-jackson2:1.37.0", - "com.google.protobuf:protobuf-java:3.13.0", - "org.apache.httpcomponents:httpclient:4.5.13", - "com.google.http-client:google-http-client:1.37.0", - "io.grpc:grpc-grpclb:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.guava:guava:30.1.1-android", + "io.grpc:grpc-grpclb:1.38.1", + "com.google.auth:google-auth-library-credentials:1.1.0", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "io.grpc:grpc-protobuf-lite:1.38.1", + "io.grpc:grpc-core:1.38.1", + "io.grpc:grpc-protobuf:1.38.1", + "io.grpc:grpc-netty-shaded:1.38.1", + "io.grpc:grpc-context:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "io.grpc:grpc-api:1.38.1", + "io.grpc:grpc-auth:1.38.1", + "io.grpc:grpc-stub:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", + "io.opencensus:opencensus-api:0.28.0", + "com.google.auth:google-auth-library-oauth2-http:1.1.0" ], "directDependencies": [ "org.conscrypt:conscrypt-openjdk-uber:2.5.1", - "com.google.auth:google-auth-library-oauth2-http:0.22.0", - "io.grpc:grpc-netty-shaded:1.33.1", - "org.apache.commons:commons-lang3:3.5", - "com.google.protobuf:protobuf-java:3.13.0", - "io.grpc:grpc-grpclb:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0" + "com.google.guava:guava:30.1.1-android", + "io.grpc:grpc-grpclb:1.38.1", + "io.grpc:grpc-protobuf:1.38.1", + "io.grpc:grpc-netty-shaded:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "io.grpc:grpc-auth:1.38.1", + "io.grpc:grpc-stub:1.38.1", + "com.google.auth:google-auth-library-oauth2-http:1.1.0" ], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-alts/1.33.1/grpc-alts-1.33.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-alts/1.38.1/grpc-alts-1.38.1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-alts/1.33.1/grpc-alts-1.33.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-alts/1.33.1/grpc-alts-1.33.1.jar", - "https://jcenter.bintray.com/io/grpc/grpc-alts/1.33.1/grpc-alts-1.33.1.jar", - "https://maven.google.com/io/grpc/grpc-alts/1.33.1/grpc-alts-1.33.1.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-alts/1.38.1/grpc-alts-1.38.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-alts/1.38.1/grpc-alts-1.38.1.jar", + "https://jcenter.bintray.com/io/grpc/grpc-alts/1.38.1/grpc-alts-1.38.1.jar", + "https://maven.google.com/io/grpc/grpc-alts/1.38.1/grpc-alts-1.38.1.jar" ], - "sha256": "c4495a739ecef31ae94bb32972f0ecf944557f6f53ea5bef204cef729abe1506", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-alts/1.33.1/grpc-alts-1.33.1.jar" + "sha256": "8904440825debe75d0499237b983a0701d2910df7307b56a029e86d28042d739", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-alts/1.38.1/grpc-alts-1.38.1.jar" }, { - "coord": "io.grpc:grpc-alts:jar:sources:1.33.1", + "coord": "io.grpc:grpc-alts:jar:sources:1.38.1", "dependencies": [ - "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", + "com.google.guava:guava:jar:sources:30.1.1-android", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-grpclb:jar:sources:1.33.1", - "commons-logging:commons-logging:jar:sources:1.2", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "io.opencensus:opencensus-api:jar:sources:0.24.0", - "com.google.code.gson:gson:jar:sources:2.8.6", - "com.google.http-client:google-http-client-jackson2:jar:sources:1.37.0", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "org.apache.commons:commons-lang3:jar:sources:3.5", - "io.grpc:grpc-netty-shaded:jar:sources:1.33.1", - "commons-codec:commons-codec:jar:sources:1.11", - "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.http-client:google-http-client:jar:sources:1.37.0", - "com.google.auto.value:auto-value-annotations:jar:sources:1.7.4", + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "io.grpc:grpc-stub:jar:sources:1.38.1", + "io.grpc:grpc-api:jar:sources:1.38.1", + "io.grpc:grpc-auth:jar:sources:1.38.1", + "io.grpc:grpc-core:jar:sources:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "io.grpc:grpc-grpclb:jar:sources:1.38.1", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "io.grpc:grpc-protobuf-lite:jar:sources:1.38.1", + "io.grpc:grpc-netty-shaded:jar:sources:1.38.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-protobuf:jar:sources:1.38.1", "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19" - ], - "directDependencies": [ - "io.grpc:grpc-grpclb:jar:sources:1.33.1", - "com.google.auth:google-auth-library-oauth2-http:jar:sources:0.22.0", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "org.apache.commons:commons-lang3:jar:sources:3.5", - "io.grpc:grpc-netty-shaded:jar:sources:1.33.1", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "io.grpc:grpc-context:jar:sources:1.38.1" + ], + "directDependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", + "io.grpc:grpc-stub:jar:sources:1.38.1", + "io.grpc:grpc-auth:jar:sources:1.38.1", + "com.google.auth:google-auth-library-oauth2-http:jar:sources:1.1.0", + "io.grpc:grpc-grpclb:jar:sources:1.38.1", + "io.grpc:grpc-netty-shaded:jar:sources:1.38.1", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-protobuf:jar:sources:1.38.1", "org.conscrypt:conscrypt-openjdk-uber:jar:sources:2.5.1" ], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-alts/1.33.1/grpc-alts-1.33.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-alts/1.38.1/grpc-alts-1.38.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-alts/1.33.1/grpc-alts-1.33.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-alts/1.33.1/grpc-alts-1.33.1-sources.jar", - "https://jcenter.bintray.com/io/grpc/grpc-alts/1.33.1/grpc-alts-1.33.1-sources.jar", - "https://maven.google.com/io/grpc/grpc-alts/1.33.1/grpc-alts-1.33.1-sources.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-alts/1.38.1/grpc-alts-1.38.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-alts/1.38.1/grpc-alts-1.38.1-sources.jar", + "https://jcenter.bintray.com/io/grpc/grpc-alts/1.38.1/grpc-alts-1.38.1-sources.jar", + "https://maven.google.com/io/grpc/grpc-alts/1.38.1/grpc-alts-1.38.1-sources.jar" ], - "sha256": "e43052b8c213ee8a0e085b1060091e4b24e4f18003aba5529d667b59fe34a9ad", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-alts/1.33.1/grpc-alts-1.33.1-sources.jar" + "sha256": "e2a3b5fab9bb095f88d152b63fe40f406470a8cbdb5791d69b41bfed65f4cd87", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-alts/1.38.1/grpc-alts-1.38.1-sources.jar" }, { - "coord": "io.grpc:grpc-api:1.33.1", + "coord": "io.grpc:grpc-api:1.38.1", "dependencies": [ - "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "com.google.guava:guava:30.0-android", - "com.google.guava:failureaccess:1.0.1", - "io.grpc:grpc-context:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "org.checkerframework:checker-compat-qual:2.5.5" + "com.google.guava:guava:30.1.1-android", + "io.grpc:grpc-context:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "org.codehaus.mojo:animal-sniffer-annotations:1.20" ], "directDependencies": [ "com.google.code.findbugs:jsr305:3.0.2", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "com.google.guava:guava:30.0-android", - "io.grpc:grpc-context:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0" + "com.google.guava:guava:30.1.1-android", + "io.grpc:grpc-context:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "org.codehaus.mojo:animal-sniffer-annotations:1.20" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-api/1.33.1/grpc-api-1.33.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-api/1.38.1/grpc-api-1.38.1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-api/1.33.1/grpc-api-1.33.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-api/1.33.1/grpc-api-1.33.1.jar", - "https://jcenter.bintray.com/io/grpc/grpc-api/1.33.1/grpc-api-1.33.1.jar", - "https://maven.google.com/io/grpc/grpc-api/1.33.1/grpc-api-1.33.1.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-api/1.38.1/grpc-api-1.38.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-api/1.38.1/grpc-api-1.38.1.jar", + "https://jcenter.bintray.com/io/grpc/grpc-api/1.38.1/grpc-api-1.38.1.jar", + "https://maven.google.com/io/grpc/grpc-api/1.38.1/grpc-api-1.38.1.jar" ], - "sha256": "32a9b466ade3c5125fd97f1621a372f78e72f1e82d09618e5ec45074c8b8ecf2", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-api/1.33.1/grpc-api-1.33.1.jar" + "sha256": "f63d5bfb646537644c5ea1129cbdc698dd73864867b11481e140bc0060421b33", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-api/1.38.1/grpc-api-1.38.1.jar" }, { - "coord": "io.grpc:grpc-api:jar:sources:1.33.1", + "coord": "io.grpc:grpc-api:jar:sources:1.38.1", "dependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-context:jar:sources:1.33.1", - "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.guava:guava:jar:sources:30.0-android", - "com.google.guava:failureaccess:jar:sources:1.0.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19" + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-context:jar:sources:1.38.1" ], "directDependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.grpc:grpc-context:jar:sources:1.33.1", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.guava:guava:jar:sources:30.0-android", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19" + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-context:jar:sources:1.38.1" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-api/1.33.1/grpc-api-1.33.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-api/1.38.1/grpc-api-1.38.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-api/1.33.1/grpc-api-1.33.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-api/1.33.1/grpc-api-1.33.1-sources.jar", - "https://jcenter.bintray.com/io/grpc/grpc-api/1.33.1/grpc-api-1.33.1-sources.jar", - "https://maven.google.com/io/grpc/grpc-api/1.33.1/grpc-api-1.33.1-sources.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-api/1.38.1/grpc-api-1.38.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-api/1.38.1/grpc-api-1.38.1-sources.jar", + "https://jcenter.bintray.com/io/grpc/grpc-api/1.38.1/grpc-api-1.38.1-sources.jar", + "https://maven.google.com/io/grpc/grpc-api/1.38.1/grpc-api-1.38.1-sources.jar" ], - "sha256": "fe4d7fbf76812a9abc72fafd3776d6472138d56d6f2e594278a0f6bea48fec1c", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-api/1.33.1/grpc-api-1.33.1-sources.jar" + "sha256": "a7b205664a1358769105d6b21a3740471ed61a702937d7c49f434af981ebac39", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-api/1.38.1/grpc-api-1.38.1-sources.jar" }, { - "coord": "io.grpc:grpc-auth:1.33.1", + "coord": "io.grpc:grpc-auth:1.38.1", "dependencies": [ - "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.j2objc:j2objc-annotations:1.3", - "com.google.code.findbugs:jsr305:3.0.2", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "com.google.guava:guava:30.0-android", - "com.google.guava:failureaccess:1.0.1", - "com.google.auth:google-auth-library-credentials:0.22.0", - "io.grpc:grpc-context:1.33.1", - "io.grpc:grpc-api:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "org.checkerframework:checker-compat-qual:2.5.5" + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.guava:guava:30.1.1-android", + "com.google.auth:google-auth-library-credentials:1.1.0", + "io.grpc:grpc-context:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "io.grpc:grpc-api:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", + "io.opencensus:opencensus-api:0.28.0" ], "directDependencies": [ "com.google.code.findbugs:jsr305:3.0.2", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "com.google.guava:guava:30.0-android", - "com.google.auth:google-auth-library-credentials:0.22.0", - "io.grpc:grpc-api:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0" + "com.google.guava:guava:30.1.1-android", + "com.google.auth:google-auth-library-credentials:1.1.0", + "io.grpc:grpc-context:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "io.grpc:grpc-api:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", + "io.opencensus:opencensus-api:0.28.0" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-auth/1.33.1/grpc-auth-1.33.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-auth/1.38.1/grpc-auth-1.38.1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-auth/1.33.1/grpc-auth-1.33.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-auth/1.33.1/grpc-auth-1.33.1.jar", - "https://jcenter.bintray.com/io/grpc/grpc-auth/1.33.1/grpc-auth-1.33.1.jar", - "https://maven.google.com/io/grpc/grpc-auth/1.33.1/grpc-auth-1.33.1.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-auth/1.38.1/grpc-auth-1.38.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-auth/1.38.1/grpc-auth-1.38.1.jar", + "https://jcenter.bintray.com/io/grpc/grpc-auth/1.38.1/grpc-auth-1.38.1.jar", + "https://maven.google.com/io/grpc/grpc-auth/1.38.1/grpc-auth-1.38.1.jar" ], - "sha256": "857f1aa2aef695206360dc496f5fb5c57a3af00619e83ef0092240fd1561d9f2", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-auth/1.33.1/grpc-auth-1.33.1.jar" + "sha256": "c05c624619dcc993da1f0f7bb386d60f9ad96f40807621fdaa2a315005cd1f68", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-auth/1.38.1/grpc-auth-1.38.1.jar" }, { - "coord": "io.grpc:grpc-auth:jar:sources:1.33.1", + "coord": "io.grpc:grpc-auth:jar:sources:1.38.1", "dependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.grpc:grpc-api:jar:sources:1.33.1", - "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-context:jar:sources:1.33.1", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.guava:guava:jar:sources:30.0-android", - "com.google.guava:failureaccess:jar:sources:1.0.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19" + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "io.grpc:grpc-api:jar:sources:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-context:jar:sources:1.38.1" ], "directDependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.grpc:grpc-api:jar:sources:1.33.1", - "com.google.auth:google-auth-library-credentials:jar:sources:0.22.0", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.guava:guava:jar:sources:30.0-android", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19" + "com.google.auth:google-auth-library-credentials:jar:sources:1.1.0", + "io.grpc:grpc-api:jar:sources:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-context:jar:sources:1.38.1" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-auth/1.33.1/grpc-auth-1.33.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-auth/1.38.1/grpc-auth-1.38.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-auth/1.33.1/grpc-auth-1.33.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-auth/1.33.1/grpc-auth-1.33.1-sources.jar", - "https://jcenter.bintray.com/io/grpc/grpc-auth/1.33.1/grpc-auth-1.33.1-sources.jar", - "https://maven.google.com/io/grpc/grpc-auth/1.33.1/grpc-auth-1.33.1-sources.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-auth/1.38.1/grpc-auth-1.38.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-auth/1.38.1/grpc-auth-1.38.1-sources.jar", + "https://jcenter.bintray.com/io/grpc/grpc-auth/1.38.1/grpc-auth-1.38.1-sources.jar", + "https://maven.google.com/io/grpc/grpc-auth/1.38.1/grpc-auth-1.38.1-sources.jar" ], - "sha256": "343630beaef95749b5db69f09287b3019512bc661efc007dea5b3b7799b66045", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-auth/1.33.1/grpc-auth-1.33.1-sources.jar" + "sha256": "6cdcb6d9eb58d1af4eb7b9d3a66a9951d597c3072b599a05d64b71bcd15a54ef", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-auth/1.38.1/grpc-auth-1.38.1-sources.jar" }, { - "coord": "io.grpc:grpc-context:1.33.1", + "coord": "io.grpc:grpc-context:1.38.1", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-context/1.33.1/grpc-context-1.33.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-context/1.38.1/grpc-context-1.38.1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-context/1.33.1/grpc-context-1.33.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-context/1.33.1/grpc-context-1.33.1.jar", - "https://jcenter.bintray.com/io/grpc/grpc-context/1.33.1/grpc-context-1.33.1.jar", - "https://maven.google.com/io/grpc/grpc-context/1.33.1/grpc-context-1.33.1.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-context/1.38.1/grpc-context-1.38.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-context/1.38.1/grpc-context-1.38.1.jar", + "https://jcenter.bintray.com/io/grpc/grpc-context/1.38.1/grpc-context-1.38.1.jar", + "https://maven.google.com/io/grpc/grpc-context/1.38.1/grpc-context-1.38.1.jar" ], - "sha256": "99b8aea2b614fe0e61c3676e681259dc43c2de7f64620998e1a8435eb2976496", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-context/1.33.1/grpc-context-1.33.1.jar" + "sha256": "f167b6c65908c4d1d952247c5c9a8fa4f496a7f1927a78f71bd7b4ae2b3dd7d3", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-context/1.38.1/grpc-context-1.38.1.jar" }, { - "coord": "io.grpc:grpc-context:jar:sources:1.33.1", + "coord": "io.grpc:grpc-context:jar:sources:1.38.1", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-context/1.33.1/grpc-context-1.33.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-context/1.38.1/grpc-context-1.38.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-context/1.33.1/grpc-context-1.33.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-context/1.33.1/grpc-context-1.33.1-sources.jar", - "https://jcenter.bintray.com/io/grpc/grpc-context/1.33.1/grpc-context-1.33.1-sources.jar", - "https://maven.google.com/io/grpc/grpc-context/1.33.1/grpc-context-1.33.1-sources.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-context/1.38.1/grpc-context-1.38.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-context/1.38.1/grpc-context-1.38.1-sources.jar", + "https://jcenter.bintray.com/io/grpc/grpc-context/1.38.1/grpc-context-1.38.1-sources.jar", + "https://maven.google.com/io/grpc/grpc-context/1.38.1/grpc-context-1.38.1-sources.jar" ], - "sha256": "9b0a72b9a9a9473aba2b540f85e1d3721bc00dfe859841f895ace4f9bcbf6e69", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-context/1.33.1/grpc-context-1.33.1-sources.jar" + "sha256": "d334d2fcf43e91a04c4a1540f36ff7a754f46b3ee251376de2effa92e3c88402", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-context/1.38.1/grpc-context-1.38.1-sources.jar" }, { - "coord": "io.grpc:grpc-core:1.33.1", + "coord": "io.grpc:grpc-core:1.38.1", "dependencies": [ - "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", "com.google.android:annotations:4.1.1.4", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", + "com.google.guava:guava:30.1.1-android", "io.perfmark:perfmark-api:0.23.0", - "com.google.guava:guava:30.0-android", - "com.google.code.gson:gson:2.8.6", - "com.google.guava:failureaccess:1.0.1", - "io.grpc:grpc-context:1.33.1", - "io.grpc:grpc-api:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "org.checkerframework:checker-compat-qual:2.5.5" + "com.google.errorprone:error_prone_annotations:2.9.0", + "io.grpc:grpc-api:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", + "com.google.code.gson:gson:2.8.8" ], "directDependencies": [ "com.google.code.findbugs:jsr305:3.0.2", "com.google.android:annotations:4.1.1.4", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", + "com.google.guava:guava:30.1.1-android", "io.perfmark:perfmark-api:0.23.0", - "com.google.guava:guava:30.0-android", - "com.google.code.gson:gson:2.8.6", - "io.grpc:grpc-api:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0" + "com.google.errorprone:error_prone_annotations:2.9.0", + "io.grpc:grpc-api:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", + "com.google.code.gson:gson:2.8.8" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-core/1.33.1/grpc-core-1.33.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-core/1.38.1/grpc-core-1.38.1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-core/1.33.1/grpc-core-1.33.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-core/1.33.1/grpc-core-1.33.1.jar", - "https://jcenter.bintray.com/io/grpc/grpc-core/1.33.1/grpc-core-1.33.1.jar", - "https://maven.google.com/io/grpc/grpc-core/1.33.1/grpc-core-1.33.1.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-core/1.38.1/grpc-core-1.38.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-core/1.38.1/grpc-core-1.38.1.jar", + "https://jcenter.bintray.com/io/grpc/grpc-core/1.38.1/grpc-core-1.38.1.jar", + "https://maven.google.com/io/grpc/grpc-core/1.38.1/grpc-core-1.38.1.jar" ], - "sha256": "341ddfa2d17e01ce512034a91458b77db379465668834a02b3259744cc74b34f", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-core/1.33.1/grpc-core-1.33.1.jar" + "sha256": "c1a658bf9ada06f5cb3714e6b6a08c832d9bca881b08039cb629c96292482fa9", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-core/1.38.1/grpc-core-1.38.1.jar" }, { - "coord": "io.grpc:grpc-core:jar:sources:1.33.1", + "coord": "io.grpc:grpc-core:jar:sources:1.38.1", "dependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.grpc:grpc-api:jar:sources:1.33.1", + "com.google.code.gson:gson:jar:sources:2.8.8", "com.google.android:annotations:jar:sources:4.1.1.4", - "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-context:jar:sources:1.33.1", - "com.google.code.gson:gson:jar:sources:2.8.6", - "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.guava:guava:jar:sources:30.0-android", - "com.google.guava:failureaccess:jar:sources:1.0.1", - "io.perfmark:perfmark-api:jar:sources:0.23.0", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19" + "io.grpc:grpc-api:jar:sources:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.perfmark:perfmark-api:jar:sources:0.23.0" ], "directDependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.grpc:grpc-api:jar:sources:1.33.1", + "com.google.code.gson:gson:jar:sources:2.8.8", "com.google.android:annotations:jar:sources:4.1.1.4", - "com.google.code.gson:gson:jar:sources:2.8.6", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.guava:guava:jar:sources:30.0-android", - "io.perfmark:perfmark-api:jar:sources:0.23.0", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19" + "io.grpc:grpc-api:jar:sources:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.perfmark:perfmark-api:jar:sources:0.23.0" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-core/1.33.1/grpc-core-1.33.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-core/1.38.1/grpc-core-1.38.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-core/1.33.1/grpc-core-1.33.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-core/1.33.1/grpc-core-1.33.1-sources.jar", - "https://jcenter.bintray.com/io/grpc/grpc-core/1.33.1/grpc-core-1.33.1-sources.jar", - "https://maven.google.com/io/grpc/grpc-core/1.33.1/grpc-core-1.33.1-sources.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-core/1.38.1/grpc-core-1.38.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-core/1.38.1/grpc-core-1.38.1-sources.jar", + "https://jcenter.bintray.com/io/grpc/grpc-core/1.38.1/grpc-core-1.38.1-sources.jar", + "https://maven.google.com/io/grpc/grpc-core/1.38.1/grpc-core-1.38.1-sources.jar" ], - "sha256": "31a9f49d770551ca068816d4b9021352dde6a2cf95bbfef6cb42c3130ceeef8f", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-core/1.33.1/grpc-core-1.33.1-sources.jar" + "sha256": "dec380ab070a115bb85c19a10867e2888ecffc2a8e99bd38c70c42627a2d27e4", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-core/1.38.1/grpc-core-1.38.1-sources.jar" }, { - "coord": "io.grpc:grpc-grpclb:1.33.1", - "dependencies": [], - "directDependencies": [], + "coord": "io.grpc:grpc-grpclb:1.38.1", + "dependencies": [ + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.guava:guava:30.1.1-android", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "io.grpc:grpc-protobuf-lite:1.38.1", + "io.grpc:grpc-core:1.38.1", + "io.grpc:grpc-protobuf:1.38.1", + "io.grpc:grpc-context:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "io.grpc:grpc-api:1.38.1", + "io.grpc:grpc-stub:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20" + ], + "directDependencies": [ + "com.google.code.findbugs:jsr305:3.0.2", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.guava:guava:30.1.1-android", + "io.grpc:grpc-core:1.38.1", + "io.grpc:grpc-protobuf:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "io.grpc:grpc-stub:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20" + ], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "com.google.code.findbugs:jsr305", - "io.grpc:grpc-protobuf-lite", - "org.codehaus.mojo:animal-sniffer-annotations", - "com.google.protobuf:protobuf-java-util", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "com.google.protobuf:protobuf-java", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "com.google.errorprone:error_prone_annotations", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-grpclb/1.33.1/grpc-grpclb-1.33.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-grpclb/1.38.1/grpc-grpclb-1.38.1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-grpclb/1.33.1/grpc-grpclb-1.33.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-grpclb/1.33.1/grpc-grpclb-1.33.1.jar", - "https://jcenter.bintray.com/io/grpc/grpc-grpclb/1.33.1/grpc-grpclb-1.33.1.jar", - "https://maven.google.com/io/grpc/grpc-grpclb/1.33.1/grpc-grpclb-1.33.1.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-grpclb/1.38.1/grpc-grpclb-1.38.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-grpclb/1.38.1/grpc-grpclb-1.38.1.jar", + "https://jcenter.bintray.com/io/grpc/grpc-grpclb/1.38.1/grpc-grpclb-1.38.1.jar", + "https://maven.google.com/io/grpc/grpc-grpclb/1.38.1/grpc-grpclb-1.38.1.jar" ], - "sha256": "68e048a9e45a935d92f15dd3cd97926c307622949c2dccb943387df7ec9e445b", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-grpclb/1.33.1/grpc-grpclb-1.33.1.jar" + "sha256": "d79e7c4d47659990b5c6c19634fb862eb8c11f30d7c1fee797c8db2c182c6de0", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-grpclb/1.38.1/grpc-grpclb-1.38.1.jar" }, { - "coord": "io.grpc:grpc-grpclb:jar:sources:1.33.1", - "dependencies": [], - "directDependencies": [], + "coord": "io.grpc:grpc-grpclb:jar:sources:1.38.1", + "dependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "io.grpc:grpc-stub:jar:sources:1.38.1", + "io.grpc:grpc-api:jar:sources:1.38.1", + "io.grpc:grpc-core:jar:sources:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "io.grpc:grpc-protobuf-lite:jar:sources:1.38.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-protobuf:jar:sources:1.38.1", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "io.grpc:grpc-context:jar:sources:1.38.1" + ], + "directDependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "io.grpc:grpc-stub:jar:sources:1.38.1", + "io.grpc:grpc-core:jar:sources:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-protobuf:jar:sources:1.38.1", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3" + ], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "com.google.code.findbugs:jsr305", - "io.grpc:grpc-protobuf-lite", - "org.codehaus.mojo:animal-sniffer-annotations", - "com.google.protobuf:protobuf-java-util", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "com.google.protobuf:protobuf-java", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "com.google.errorprone:error_prone_annotations", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-grpclb/1.33.1/grpc-grpclb-1.33.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-grpclb/1.38.1/grpc-grpclb-1.38.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-grpclb/1.33.1/grpc-grpclb-1.33.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-grpclb/1.33.1/grpc-grpclb-1.33.1-sources.jar", - "https://jcenter.bintray.com/io/grpc/grpc-grpclb/1.33.1/grpc-grpclb-1.33.1-sources.jar", - "https://maven.google.com/io/grpc/grpc-grpclb/1.33.1/grpc-grpclb-1.33.1-sources.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-grpclb/1.38.1/grpc-grpclb-1.38.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-grpclb/1.38.1/grpc-grpclb-1.38.1-sources.jar", + "https://jcenter.bintray.com/io/grpc/grpc-grpclb/1.38.1/grpc-grpclb-1.38.1-sources.jar", + "https://maven.google.com/io/grpc/grpc-grpclb/1.38.1/grpc-grpclb-1.38.1-sources.jar" ], - "sha256": "0a12e7b137e1566b0a07a994a2df0c51d2339de8aed3f3516eb309087b9fb924", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-grpclb/1.33.1/grpc-grpclb-1.33.1-sources.jar" + "sha256": "dd6b61e9b8128c90c8c1cfd51b0122db443d285ffbb4d7b39550d4935052b8f9", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-grpclb/1.38.1/grpc-grpclb-1.38.1-sources.jar" }, { - "coord": "io.grpc:grpc-netty-shaded:1.33.1", - "dependencies": [], - "directDependencies": [], + "coord": "io.grpc:grpc-netty-shaded:1.38.1", + "dependencies": [ + "io.grpc:grpc-core:1.38.1" + ], + "directDependencies": [ + "io.grpc:grpc-core:1.38.1" + ], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-netty-shaded/1.33.1/grpc-netty-shaded-1.33.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-netty-shaded/1.38.1/grpc-netty-shaded-1.38.1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-netty-shaded/1.33.1/grpc-netty-shaded-1.33.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-netty-shaded/1.33.1/grpc-netty-shaded-1.33.1.jar", - "https://jcenter.bintray.com/io/grpc/grpc-netty-shaded/1.33.1/grpc-netty-shaded-1.33.1.jar", - "https://maven.google.com/io/grpc/grpc-netty-shaded/1.33.1/grpc-netty-shaded-1.33.1.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-netty-shaded/1.38.1/grpc-netty-shaded-1.38.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-netty-shaded/1.38.1/grpc-netty-shaded-1.38.1.jar", + "https://jcenter.bintray.com/io/grpc/grpc-netty-shaded/1.38.1/grpc-netty-shaded-1.38.1.jar", + "https://maven.google.com/io/grpc/grpc-netty-shaded/1.38.1/grpc-netty-shaded-1.38.1.jar" ], - "sha256": "183fe65297f395bc2773f063f31a7000664e6d75a88bfa6292b1784a9798667b", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-netty-shaded/1.33.1/grpc-netty-shaded-1.33.1.jar" + "sha256": "e9b62167b8f69f0b7fc6409ec3d144bd925e127fc7c157f8374b12d7413c51d9", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-netty-shaded/1.38.1/grpc-netty-shaded-1.38.1.jar" }, { - "coord": "io.grpc:grpc-netty-shaded:jar:sources:1.33.1", - "dependencies": [], - "directDependencies": [], + "coord": "io.grpc:grpc-netty-shaded:jar:sources:1.38.1", + "dependencies": [ + "io.grpc:grpc-core:jar:sources:1.38.1" + ], + "directDependencies": [ + "io.grpc:grpc-core:jar:sources:1.38.1" + ], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-netty-shaded/1.33.1/grpc-netty-shaded-1.33.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-netty-shaded/1.38.1/grpc-netty-shaded-1.38.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-netty-shaded/1.33.1/grpc-netty-shaded-1.33.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-netty-shaded/1.33.1/grpc-netty-shaded-1.33.1-sources.jar", - "https://jcenter.bintray.com/io/grpc/grpc-netty-shaded/1.33.1/grpc-netty-shaded-1.33.1-sources.jar", - "https://maven.google.com/io/grpc/grpc-netty-shaded/1.33.1/grpc-netty-shaded-1.33.1-sources.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-netty-shaded/1.38.1/grpc-netty-shaded-1.38.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-netty-shaded/1.38.1/grpc-netty-shaded-1.38.1-sources.jar", + "https://jcenter.bintray.com/io/grpc/grpc-netty-shaded/1.38.1/grpc-netty-shaded-1.38.1-sources.jar", + "https://maven.google.com/io/grpc/grpc-netty-shaded/1.38.1/grpc-netty-shaded-1.38.1-sources.jar" ], - "sha256": "7e681876391fe2280405adfe85ca171cb9b403cfcdb3cda370e7c7eb78484a22", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-netty-shaded/1.33.1/grpc-netty-shaded-1.33.1-sources.jar" + "sha256": "ffea6a846b83c294dd0b8517d7f2a43ca1412cc818f0d1a84f96b46b5b28cf5b", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-netty-shaded/1.38.1/grpc-netty-shaded-1.38.1-sources.jar" }, { - "coord": "io.grpc:grpc-netty:1.33.1", + "coord": "io.grpc:grpc-protobuf-lite:1.38.1", "dependencies": [ - "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "io.netty:netty-codec-http2:4.1.53.Final", - "io.netty:netty-codec:4.1.53.Final", - "com.google.j2objc:j2objc-annotations:1.3", - "io.netty:netty-handler:4.1.53.Final", - "io.netty:netty-codec-socks:4.1.53.Final", "com.google.code.findbugs:jsr305:3.0.2", - "io.netty:netty-common:4.1.53.Final", - "com.google.android:annotations:4.1.1.4", - "io.netty:netty-buffer:4.1.53.Final", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "io.perfmark:perfmark-api:0.23.0", - "io.netty:netty-resolver:4.1.53.Final", - "com.google.guava:guava:30.0-android", - "io.netty:netty-transport:4.1.53.Final", - "com.google.code.gson:gson:2.8.6", - "io.netty:netty-codec-http:4.1.53.Final", - "io.grpc:grpc-core:1.33.1", - "com.google.guava:failureaccess:1.0.1", - "io.grpc:grpc-context:1.33.1", - "io.netty:netty-handler-proxy:4.1.53.Final", - "io.grpc:grpc-api:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "org.checkerframework:checker-compat-qual:2.5.5" + "com.google.guava:guava:30.1.1-android", + "io.grpc:grpc-context:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "io.grpc:grpc-api:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20" ], "directDependencies": [ - "io.netty:netty-codec-http2:4.1.53.Final", "com.google.code.findbugs:jsr305:3.0.2", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "io.perfmark:perfmark-api:0.23.0", - "com.google.guava:guava:30.0-android", - "io.grpc:grpc-core:1.33.1", - "io.netty:netty-handler-proxy:4.1.53.Final", - "com.google.errorprone:error_prone_annotations:2.4.0" + "com.google.guava:guava:30.1.1-android", + "com.google.errorprone:error_prone_annotations:2.9.0", + "io.grpc:grpc-api:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20" ], "exclusions": [ + "com.google.protobuf:protobuf-javalite", "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-netty/1.33.1/grpc-netty-1.33.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-protobuf-lite/1.38.1/grpc-protobuf-lite-1.38.1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-netty/1.33.1/grpc-netty-1.33.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-netty/1.33.1/grpc-netty-1.33.1.jar", - "https://jcenter.bintray.com/io/grpc/grpc-netty/1.33.1/grpc-netty-1.33.1.jar", - "https://maven.google.com/io/grpc/grpc-netty/1.33.1/grpc-netty-1.33.1.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-protobuf-lite/1.38.1/grpc-protobuf-lite-1.38.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-protobuf-lite/1.38.1/grpc-protobuf-lite-1.38.1.jar", + "https://jcenter.bintray.com/io/grpc/grpc-protobuf-lite/1.38.1/grpc-protobuf-lite-1.38.1.jar", + "https://maven.google.com/io/grpc/grpc-protobuf-lite/1.38.1/grpc-protobuf-lite-1.38.1.jar" ], - "sha256": "ad74fa483a423d95157914012e0500fb087a3e5bf752de6464ce352d7aa0f3f5", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-netty/1.33.1/grpc-netty-1.33.1.jar" + "sha256": "1ef88ad74b14598a8ffa03f2b0e738997fdb7b7091418d19e1cfee42a58c9dbf", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-protobuf-lite/1.38.1/grpc-protobuf-lite-1.38.1.jar" }, { - "coord": "io.grpc:grpc-netty:jar:sources:1.33.1", + "coord": "io.grpc:grpc-protobuf-lite:jar:sources:1.38.1", "dependencies": [ - "io.netty:netty-handler:jar:sources:4.1.53.Final", - "io.netty:netty-handler-proxy:jar:sources:4.1.53.Final", + "com.google.guava:guava:jar:sources:30.1.1-android", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.grpc:grpc-api:jar:sources:1.33.1", - "com.google.android:annotations:jar:sources:4.1.1.4", - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "io.netty:netty-codec-http2:jar:sources:4.1.53.Final", - "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.netty:netty-codec-socks:jar:sources:4.1.53.Final", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "io.netty:netty-resolver:jar:sources:4.1.53.Final", - "io.grpc:grpc-context:jar:sources:1.33.1", - "com.google.code.gson:gson:jar:sources:2.8.6", - "io.netty:netty-codec-http:jar:sources:4.1.53.Final", - "io.netty:netty-codec:jar:sources:4.1.53.Final", - "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "io.grpc:grpc-core:jar:sources:1.33.1", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.guava:guava:jar:sources:30.0-android", - "com.google.guava:failureaccess:jar:sources:1.0.1", - "io.perfmark:perfmark-api:jar:sources:0.23.0", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19" + "io.grpc:grpc-api:jar:sources:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-context:jar:sources:1.38.1" ], "directDependencies": [ - "io.netty:netty-handler-proxy:jar:sources:4.1.53.Final", + "com.google.guava:guava:jar:sources:30.1.1-android", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.netty:netty-codec-http2:jar:sources:4.1.53.Final", - "io.grpc:grpc-core:jar:sources:1.33.1", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.guava:guava:jar:sources:30.0-android", - "io.perfmark:perfmark-api:jar:sources:0.23.0", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19" + "io.grpc:grpc-api:jar:sources:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0" ], "exclusions": [ + "com.google.protobuf:protobuf-javalite", "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-netty/1.33.1/grpc-netty-1.33.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-protobuf-lite/1.38.1/grpc-protobuf-lite-1.38.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-netty/1.33.1/grpc-netty-1.33.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-netty/1.33.1/grpc-netty-1.33.1-sources.jar", - "https://jcenter.bintray.com/io/grpc/grpc-netty/1.33.1/grpc-netty-1.33.1-sources.jar", - "https://maven.google.com/io/grpc/grpc-netty/1.33.1/grpc-netty-1.33.1-sources.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-protobuf-lite/1.38.1/grpc-protobuf-lite-1.38.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-protobuf-lite/1.38.1/grpc-protobuf-lite-1.38.1-sources.jar", + "https://jcenter.bintray.com/io/grpc/grpc-protobuf-lite/1.38.1/grpc-protobuf-lite-1.38.1-sources.jar", + "https://maven.google.com/io/grpc/grpc-protobuf-lite/1.38.1/grpc-protobuf-lite-1.38.1-sources.jar" ], - "sha256": "d6e6367294569e446ccdf45fc76e5bd77374194322d3f76bac2b6423b3a3763f", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-netty/1.33.1/grpc-netty-1.33.1-sources.jar" + "sha256": "a122e8755336a22dacccd7dd7d735248e92bb1b0a09617ad52290f25ad0210de", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-protobuf-lite/1.38.1/grpc-protobuf-lite-1.38.1-sources.jar" }, { - "coord": "io.grpc:grpc-protobuf-lite:1.33.1", + "coord": "io.grpc:grpc-protobuf:1.38.1", "dependencies": [ - "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "com.google.guava:guava:30.0-android", - "com.google.guava:failureaccess:1.0.1", - "io.grpc:grpc-context:1.33.1", - "io.grpc:grpc-api:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "org.checkerframework:checker-compat-qual:2.5.5" + "com.google.guava:guava:30.1.1-android", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "io.grpc:grpc-protobuf-lite:1.38.1", + "io.grpc:grpc-context:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "io.grpc:grpc-api:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20" ], "directDependencies": [ "com.google.code.findbugs:jsr305:3.0.2", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "com.google.guava:guava:30.0-android", - "io.grpc:grpc-api:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0" + "com.google.guava:guava:30.1.1-android", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "io.grpc:grpc-protobuf-lite:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "io.grpc:grpc-api:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20" ], "exclusions": [ - "com.google.protobuf:protobuf-javalite", "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-protobuf-lite/1.33.1/grpc-protobuf-lite-1.33.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-protobuf/1.38.1/grpc-protobuf-1.38.1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-protobuf-lite/1.33.1/grpc-protobuf-lite-1.33.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-protobuf-lite/1.33.1/grpc-protobuf-lite-1.33.1.jar", - "https://jcenter.bintray.com/io/grpc/grpc-protobuf-lite/1.33.1/grpc-protobuf-lite-1.33.1.jar", - "https://maven.google.com/io/grpc/grpc-protobuf-lite/1.33.1/grpc-protobuf-lite-1.33.1.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-protobuf/1.38.1/grpc-protobuf-1.38.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-protobuf/1.38.1/grpc-protobuf-1.38.1.jar", + "https://jcenter.bintray.com/io/grpc/grpc-protobuf/1.38.1/grpc-protobuf-1.38.1.jar", + "https://maven.google.com/io/grpc/grpc-protobuf/1.38.1/grpc-protobuf-1.38.1.jar" ], - "sha256": "20c0bf79a298b31f6c17ff199f5c8510aed7b3b23518134d7803d2734fa6a638", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-protobuf-lite/1.33.1/grpc-protobuf-lite-1.33.1.jar" + "sha256": "e449e3f2a581c3cfec777d1401e260040ef3e98f30e512e44d4abfde8dcd1348", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-protobuf/1.38.1/grpc-protobuf-1.38.1.jar" }, { - "coord": "io.grpc:grpc-protobuf-lite:jar:sources:1.33.1", + "coord": "io.grpc:grpc-protobuf:jar:sources:1.38.1", "dependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.grpc:grpc-api:jar:sources:1.33.1", - "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-context:jar:sources:1.33.1", - "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.guava:guava:jar:sources:30.0-android", - "com.google.guava:failureaccess:jar:sources:1.0.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19" + "io.grpc:grpc-api:jar:sources:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "io.grpc:grpc-protobuf-lite:jar:sources:1.38.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-context:jar:sources:1.38.1" ], "directDependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.grpc:grpc-api:jar:sources:1.33.1", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.guava:guava:jar:sources:30.0-android", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19" + "io.grpc:grpc-api:jar:sources:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "io.grpc:grpc-protobuf-lite:jar:sources:1.38.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0" ], "exclusions": [ - "com.google.protobuf:protobuf-javalite", "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-protobuf-lite/1.33.1/grpc-protobuf-lite-1.33.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-protobuf/1.38.1/grpc-protobuf-1.38.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-protobuf-lite/1.33.1/grpc-protobuf-lite-1.33.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-protobuf-lite/1.33.1/grpc-protobuf-lite-1.33.1-sources.jar", - "https://jcenter.bintray.com/io/grpc/grpc-protobuf-lite/1.33.1/grpc-protobuf-lite-1.33.1-sources.jar", - "https://maven.google.com/io/grpc/grpc-protobuf-lite/1.33.1/grpc-protobuf-lite-1.33.1-sources.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-protobuf/1.38.1/grpc-protobuf-1.38.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-protobuf/1.38.1/grpc-protobuf-1.38.1-sources.jar", + "https://jcenter.bintray.com/io/grpc/grpc-protobuf/1.38.1/grpc-protobuf-1.38.1-sources.jar", + "https://maven.google.com/io/grpc/grpc-protobuf/1.38.1/grpc-protobuf-1.38.1-sources.jar" ], - "sha256": "ca5ba6e1186e0a7cb3a0e4d13ee30e7338f2f1a54e5b8fbef7d2d14fd1b70d16", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-protobuf-lite/1.33.1/grpc-protobuf-lite-1.33.1-sources.jar" + "sha256": "94606af1acc25eb33e033d3bd5168c5973f8d2b42e530957d00248eb5f85988a", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-protobuf/1.38.1/grpc-protobuf-1.38.1-sources.jar" }, { - "coord": "io.grpc:grpc-protobuf:1.33.1", + "coord": "io.grpc:grpc-services:1.38.1", "dependencies": [ - "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "com.google.guava:guava:30.0-android", - "io.grpc:grpc-protobuf-lite:1.33.1", - "com.google.guava:failureaccess:1.0.1", - "com.google.protobuf:protobuf-java:3.13.0", - "io.grpc:grpc-context:1.33.1", - "io.grpc:grpc-api:1.33.1", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "org.checkerframework:checker-compat-qual:2.5.5" + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.guava:guava:30.1.1-android", + "com.google.api.grpc:proto-google-common-protos:2.5.0", + "io.grpc:grpc-protobuf-lite:1.38.1", + "io.grpc:grpc-core:1.38.1", + "io.grpc:grpc-protobuf:1.38.1", + "io.grpc:grpc-context:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "com.google.protobuf:protobuf-java:3.17.3", + "io.grpc:grpc-api:1.38.1", + "io.grpc:grpc-stub:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20" ], "directDependencies": [ "com.google.code.findbugs:jsr305:3.0.2", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "com.google.guava:guava:30.0-android", - "io.grpc:grpc-protobuf-lite:1.33.1", - "com.google.protobuf:protobuf-java:3.13.0", - "io.grpc:grpc-api:1.33.1", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "com.google.errorprone:error_prone_annotations:2.4.0" + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.guava:guava:30.1.1-android", + "io.grpc:grpc-core:1.38.1", + "io.grpc:grpc-protobuf:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "io.grpc:grpc-stub:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-protobuf/1.33.1/grpc-protobuf-1.33.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-services/1.38.1/grpc-services-1.38.1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-protobuf/1.33.1/grpc-protobuf-1.33.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-protobuf/1.33.1/grpc-protobuf-1.33.1.jar", - "https://jcenter.bintray.com/io/grpc/grpc-protobuf/1.33.1/grpc-protobuf-1.33.1.jar", - "https://maven.google.com/io/grpc/grpc-protobuf/1.33.1/grpc-protobuf-1.33.1.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-services/1.38.1/grpc-services-1.38.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-services/1.38.1/grpc-services-1.38.1.jar", + "https://jcenter.bintray.com/io/grpc/grpc-services/1.38.1/grpc-services-1.38.1.jar", + "https://maven.google.com/io/grpc/grpc-services/1.38.1/grpc-services-1.38.1.jar" ], - "sha256": "8fcaac5f023b598e40231bd097e9c46106aa9170807f6393e6b5fd01396dcb74", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-protobuf/1.33.1/grpc-protobuf-1.33.1.jar" + "sha256": "efcb182a7557eac59ae558770cc46cb0af818292f2891c77232054f2f8a2306c", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-services/1.38.1/grpc-services-1.38.1.jar" }, { - "coord": "io.grpc:grpc-protobuf:jar:sources:1.33.1", + "coord": "io.grpc:grpc-services:jar:sources:1.38.1", "dependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.grpc:grpc-api:jar:sources:1.33.1", - "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-context:jar:sources:1.33.1", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.guava:guava:jar:sources:30.0-android", - "com.google.guava:failureaccess:jar:sources:1.0.1", - "io.grpc:grpc-protobuf-lite:jar:sources:1.33.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19" - ], - "directDependencies": [ + "io.grpc:grpc-stub:jar:sources:1.38.1", + "io.grpc:grpc-api:jar:sources:1.38.1", + "io.grpc:grpc-core:jar:sources:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "io.grpc:grpc-protobuf-lite:jar:sources:1.38.1", + "com.google.api.grpc:proto-google-common-protos:jar:sources:2.5.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-protobuf:jar:sources:1.38.1", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "io.grpc:grpc-context:jar:sources:1.38.1" + ], + "directDependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.grpc:grpc-api:jar:sources:1.33.1", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.guava:guava:jar:sources:30.0-android", - "io.grpc:grpc-protobuf-lite:jar:sources:1.33.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19" + "io.grpc:grpc-stub:jar:sources:1.38.1", + "io.grpc:grpc-core:jar:sources:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-protobuf:jar:sources:1.38.1", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-protobuf/1.33.1/grpc-protobuf-1.33.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-services/1.38.1/grpc-services-1.38.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-protobuf/1.33.1/grpc-protobuf-1.33.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-protobuf/1.33.1/grpc-protobuf-1.33.1-sources.jar", - "https://jcenter.bintray.com/io/grpc/grpc-protobuf/1.33.1/grpc-protobuf-1.33.1-sources.jar", - "https://maven.google.com/io/grpc/grpc-protobuf/1.33.1/grpc-protobuf-1.33.1-sources.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-services/1.38.1/grpc-services-1.38.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-services/1.38.1/grpc-services-1.38.1-sources.jar", + "https://jcenter.bintray.com/io/grpc/grpc-services/1.38.1/grpc-services-1.38.1-sources.jar", + "https://maven.google.com/io/grpc/grpc-services/1.38.1/grpc-services-1.38.1-sources.jar" ], - "sha256": "a5e6a78f6acb8de69392591512e4140bb7b174f3f8ef1ade36d8cdfe5c5d93a8", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-protobuf/1.33.1/grpc-protobuf-1.33.1-sources.jar" + "sha256": "ef1518489f2dc0e0000ae38575ed8bfb795c6d7be3b9870eb49f581dc281abb5", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-services/1.38.1/grpc-services-1.38.1-sources.jar" }, { - "coord": "io.grpc:grpc-services:1.33.1", + "coord": "io.grpc:grpc-stub:1.38.1", "dependencies": [ - "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "com.google.android:annotations:4.1.1.4", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "io.perfmark:perfmark-api:0.23.0", - "com.google.guava:guava:30.0-android", - "com.google.code.gson:gson:2.8.6", - "io.grpc:grpc-protobuf-lite:1.33.1", - "com.google.protobuf:protobuf-java-util:3.13.0", - "io.grpc:grpc-core:1.33.1", - "com.google.guava:failureaccess:1.0.1", - "com.google.protobuf:protobuf-java:3.13.0", - "io.grpc:grpc-stub:1.33.1", - "io.grpc:grpc-context:1.33.1", - "io.grpc:grpc-api:1.33.1", - "com.google.api.grpc:proto-google-common-protos:2.0.1", - "io.grpc:grpc-protobuf:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "org.checkerframework:checker-compat-qual:2.5.5" + "com.google.guava:guava:30.1.1-android", + "io.grpc:grpc-context:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "io.grpc:grpc-api:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20" ], "directDependencies": [ "com.google.code.findbugs:jsr305:3.0.2", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "com.google.guava:guava:30.0-android", - "com.google.protobuf:protobuf-java-util:3.13.0", - "io.grpc:grpc-core:1.33.1", - "io.grpc:grpc-stub:1.33.1", - "io.grpc:grpc-protobuf:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0" + "com.google.guava:guava:30.1.1-android", + "com.google.errorprone:error_prone_annotations:2.9.0", + "io.grpc:grpc-api:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-services/1.33.1/grpc-services-1.33.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-stub/1.38.1/grpc-stub-1.38.1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-services/1.33.1/grpc-services-1.33.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-services/1.33.1/grpc-services-1.33.1.jar", - "https://jcenter.bintray.com/io/grpc/grpc-services/1.33.1/grpc-services-1.33.1.jar", - "https://maven.google.com/io/grpc/grpc-services/1.33.1/grpc-services-1.33.1.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-stub/1.38.1/grpc-stub-1.38.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-stub/1.38.1/grpc-stub-1.38.1.jar", + "https://jcenter.bintray.com/io/grpc/grpc-stub/1.38.1/grpc-stub-1.38.1.jar", + "https://maven.google.com/io/grpc/grpc-stub/1.38.1/grpc-stub-1.38.1.jar" ], - "sha256": "2c11344b1da9c3e9b4efa1b28bf82e3877741dc03832594ce8e935a6c315ced6", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-services/1.33.1/grpc-services-1.33.1.jar" + "sha256": "0bc06113cdbfe7bf3d415d0187022b586014dc768adee9e0a675a3d5063a467a", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-stub/1.38.1/grpc-stub-1.38.1.jar" }, { - "coord": "io.grpc:grpc-services:jar:sources:1.33.1", + "coord": "io.grpc:grpc-stub:jar:sources:1.38.1", "dependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.grpc:grpc-api:jar:sources:1.33.1", - "com.google.android:annotations:jar:sources:4.1.1.4", - "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-context:jar:sources:1.33.1", - "com.google.code.gson:gson:jar:sources:2.8.6", - "io.grpc:grpc-protobuf:jar:sources:1.33.1", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "io.grpc:grpc-core:jar:sources:1.33.1", - "io.grpc:grpc-stub:jar:sources:1.33.1", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.api.grpc:proto-google-common-protos:jar:sources:2.0.1", - "com.google.guava:guava:jar:sources:30.0-android", - "com.google.guava:failureaccess:jar:sources:1.0.1", - "io.perfmark:perfmark-api:jar:sources:0.23.0", - "io.grpc:grpc-protobuf-lite:jar:sources:1.33.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19" + "io.grpc:grpc-api:jar:sources:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-context:jar:sources:1.38.1" ], "directDependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.grpc:grpc-protobuf:jar:sources:1.33.1", - "io.grpc:grpc-core:jar:sources:1.33.1", - "io.grpc:grpc-stub:jar:sources:1.33.1", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.guava:guava:jar:sources:30.0-android", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19" + "io.grpc:grpc-api:jar:sources:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-services/1.33.1/grpc-services-1.33.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-stub/1.38.1/grpc-stub-1.38.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-services/1.33.1/grpc-services-1.33.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-services/1.33.1/grpc-services-1.33.1-sources.jar", - "https://jcenter.bintray.com/io/grpc/grpc-services/1.33.1/grpc-services-1.33.1-sources.jar", - "https://maven.google.com/io/grpc/grpc-services/1.33.1/grpc-services-1.33.1-sources.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-stub/1.38.1/grpc-stub-1.38.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-stub/1.38.1/grpc-stub-1.38.1-sources.jar", + "https://jcenter.bintray.com/io/grpc/grpc-stub/1.38.1/grpc-stub-1.38.1-sources.jar", + "https://maven.google.com/io/grpc/grpc-stub/1.38.1/grpc-stub-1.38.1-sources.jar" ], - "sha256": "961b4b804456795426525254a7b47d215a97ce0edffbf4bac7ddce5beaaca40e", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-services/1.33.1/grpc-services-1.33.1-sources.jar" + "sha256": "2d0eb84e74cd8a0af1c4862f9b735bc9f9207b4e3b05eb1641ce251be477367e", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-stub/1.38.1/grpc-stub-1.38.1-sources.jar" }, { - "coord": "io.grpc:grpc-stub:1.33.1", + "coord": "io.grpc:grpc-testing:1.38.1", "dependencies": [ - "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.j2objc:j2objc-annotations:1.3", "com.google.code.findbugs:jsr305:3.0.2", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "com.google.guava:guava:30.0-android", - "com.google.guava:failureaccess:1.0.1", - "io.grpc:grpc-context:1.33.1", - "io.grpc:grpc-api:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0", - "org.checkerframework:checker-compat-qual:2.5.5" + "com.google.guava:guava:30.1.1-android", + "io.grpc:grpc-core:1.38.1", + "io.grpc:grpc-context:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "io.grpc:grpc-api:1.38.1", + "io.grpc:grpc-stub:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", + "junit:junit:4.13.2", + "io.opencensus:opencensus-api:0.28.0" ], "directDependencies": [ "com.google.code.findbugs:jsr305:3.0.2", - "org.codehaus.mojo:animal-sniffer-annotations:1.19", - "com.google.guava:guava:30.0-android", - "io.grpc:grpc-api:1.33.1", - "com.google.errorprone:error_prone_annotations:2.4.0" + "com.google.guava:guava:30.1.1-android", + "io.grpc:grpc-core:1.38.1", + "io.grpc:grpc-context:1.38.1", + "com.google.errorprone:error_prone_annotations:2.9.0", + "io.grpc:grpc-stub:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:1.20", + "junit:junit:4.13.2", + "io.opencensus:opencensus-api:0.28.0" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-stub/1.33.1/grpc-stub-1.33.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-testing/1.38.1/grpc-testing-1.38.1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-stub/1.33.1/grpc-stub-1.33.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-stub/1.33.1/grpc-stub-1.33.1.jar", - "https://jcenter.bintray.com/io/grpc/grpc-stub/1.33.1/grpc-stub-1.33.1.jar", - "https://maven.google.com/io/grpc/grpc-stub/1.33.1/grpc-stub-1.33.1.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-testing/1.38.1/grpc-testing-1.38.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-testing/1.38.1/grpc-testing-1.38.1.jar", + "https://jcenter.bintray.com/io/grpc/grpc-testing/1.38.1/grpc-testing-1.38.1.jar", + "https://maven.google.com/io/grpc/grpc-testing/1.38.1/grpc-testing-1.38.1.jar" ], - "sha256": "510aea72000828a80d9b005eb7c94008b25feb29333302d10a57e081b24ba590", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-stub/1.33.1/grpc-stub-1.33.1.jar" + "sha256": "013ec80076d3bc84b40b140b8213c7271b927c7899ae2a4f13ecd5fcae3ef3d3", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-testing/1.38.1/grpc-testing-1.38.1.jar" }, { - "coord": "io.grpc:grpc-stub:jar:sources:1.33.1", + "coord": "io.grpc:grpc-testing:jar:sources:1.38.1", "dependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.grpc:grpc-api:jar:sources:1.33.1", - "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.grpc:grpc-context:jar:sources:1.33.1", - "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.guava:guava:jar:sources:30.0-android", - "com.google.guava:failureaccess:jar:sources:1.0.1", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19" + "io.grpc:grpc-stub:jar:sources:1.38.1", + "io.grpc:grpc-api:jar:sources:1.38.1", + "junit:junit:jar:sources:4.13.2", + "io.grpc:grpc-core:jar:sources:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-context:jar:sources:1.38.1" ], "directDependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.grpc:grpc-api:jar:sources:1.33.1", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.guava:guava:jar:sources:30.0-android", - "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19" + "io.grpc:grpc-stub:jar:sources:1.38.1", + "junit:junit:jar:sources:4.13.2", + "io.grpc:grpc-core:jar:sources:1.38.1", + "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", + "io.opencensus:opencensus-api:jar:sources:0.28.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0", + "io.grpc:grpc-context:jar:sources:1.38.1" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-stub/1.33.1/grpc-stub-1.33.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/grpc/grpc-testing/1.38.1/grpc-testing-1.38.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/grpc/grpc-stub/1.33.1/grpc-stub-1.33.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-stub/1.33.1/grpc-stub-1.33.1-sources.jar", - "https://jcenter.bintray.com/io/grpc/grpc-stub/1.33.1/grpc-stub-1.33.1-sources.jar", - "https://maven.google.com/io/grpc/grpc-stub/1.33.1/grpc-stub-1.33.1-sources.jar" + "https://repo1.maven.org/maven2/io/grpc/grpc-testing/1.38.1/grpc-testing-1.38.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/grpc/grpc-testing/1.38.1/grpc-testing-1.38.1-sources.jar", + "https://jcenter.bintray.com/io/grpc/grpc-testing/1.38.1/grpc-testing-1.38.1-sources.jar", + "https://maven.google.com/io/grpc/grpc-testing/1.38.1/grpc-testing-1.38.1-sources.jar" ], - "sha256": "c33be9ab6c860a8634d73d19f1e3ed8b02f9b12f62065b304254cfbf1132a41f", - "url": "https://repo1.maven.org/maven2/io/grpc/grpc-stub/1.33.1/grpc-stub-1.33.1-sources.jar" + "sha256": "2492dd97f8afd076f4f513b877b5f8beb423b9294d627e001b80a787d2fcdd00", + "url": "https://repo1.maven.org/maven2/io/grpc/grpc-testing/1.38.1/grpc-testing-1.38.1-sources.jar" }, { "coord": "io.kindedj:kindedj:1.1.0", @@ -5856,7 +7489,6 @@ "coord": "io.kotlintest:kotlintest-assertions:3.4.2", "dependencies": [ "com.univocity:univocity-parsers:2.8.1", - "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10", "io.kindedj:kindedj:1.1.0", "org.eclipse.jgit:org.eclipse.jgit:4.4.1.201607150455-r", "io.arrow-kt:arrow-core-data:0.9.0", @@ -5864,8 +7496,7 @@ "org.jetbrains.kotlin:kotlin-reflect:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib:1.4.10", - "io.arrow-kt:arrow-annotations:0.9.0", - "org.jetbrains:annotations:13.0" + "io.arrow-kt:arrow-annotations:0.9.0" ], "directDependencies": [ "com.github.wumpz:diffutils:2.2", @@ -5892,9 +7523,7 @@ "dependencies": [ "com.github.wumpz:diffutils:jar:sources:2.2", "org.jetbrains.kotlin:kotlin-reflect:jar:sources:1.4.10", - "org.jetbrains:annotations:jar:sources:13.0", "com.univocity:univocity-parsers:jar:sources:2.8.1", - "org.jetbrains.kotlin:kotlin-stdlib-common:jar:sources:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:jar:sources:1.4.10", "org.eclipse.jgit:org.eclipse.jgit:jar:sources:4.4.1.201607150455-r", @@ -5925,32 +7554,28 @@ { "coord": "io.kotlintest:kotlintest-core:3.4.2", "dependencies": [ - "org.slf4j:slf4j-api:1.7.30", "com.univocity:univocity-parsers:2.8.1", "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10", "io.kindedj:kindedj:1.1.0", "org.eclipse.jgit:org.eclipse.jgit:4.4.1.201607150455-r", "org.junit.jupiter:junit-jupiter-api:5.7.0", - "org.opentest4j:opentest4j:1.2.0", "io.arrow-kt:arrow-core-data:0.9.0", "io.kotlintest:kotlintest-assertions:3.4.2", "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.1.1", - "org.apiguardian:apiguardian-api:1.1.0", "com.github.wumpz:diffutils:2.2", "org.jetbrains.kotlin:kotlin-reflect:1.4.10", - "org.junit.platform:junit-platform-commons:1.7.0", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib:1.4.10", "io.arrow-kt:arrow-annotations:0.9.0", "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1", - "org.jetbrains:annotations:13.0" + "org.slf4j:slf4j-api:1.7.31" ], "directDependencies": [ - "org.slf4j:slf4j-api:1.7.30", "org.junit.jupiter:junit-jupiter-api:5.7.0", "io.kotlintest:kotlintest-assertions:3.4.2", "org.jetbrains.kotlin:kotlin-reflect:1.4.10", - "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1" + "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", @@ -5969,15 +7594,12 @@ { "coord": "io.kotlintest:kotlintest-core:jar:sources:3.4.2", "dependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", "com.github.wumpz:diffutils:jar:sources:2.2", "org.jetbrains.kotlin:kotlin-reflect:jar:sources:1.4.10", - "org.jetbrains:annotations:jar:sources:13.0", "com.univocity:univocity-parsers:jar:sources:2.8.1", - "org.junit.platform:junit-platform-commons:jar:sources:1.7.0", "org.jetbrains.kotlin:kotlin-stdlib-common:jar:sources:1.4.10", - "org.slf4j:slf4j-api:jar:sources:1.7.30", "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.4.10", - "org.opentest4j:opentest4j:jar:sources:1.2.0", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:jar:sources:1.4.10", "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:jar:sources:1.1.1", "org.eclipse.jgit:org.eclipse.jgit:jar:sources:4.4.1.201607150455-r", @@ -5986,12 +7608,11 @@ "io.arrow-kt:arrow-annotations:jar:sources:0.9.0", "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0", "org.jetbrains.kotlinx:kotlinx-coroutines-core:jar:sources:1.1.1", - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", "io.kindedj:kindedj:jar:sources:1.1.0" ], "directDependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", "org.jetbrains.kotlin:kotlin-reflect:jar:sources:1.4.10", - "org.slf4j:slf4j-api:jar:sources:1.7.30", "io.kotlintest:kotlintest-assertions:jar:sources:3.4.2", "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0", "org.jetbrains.kotlinx:kotlinx-coroutines-core:jar:sources:1.1.1" @@ -6014,7 +7635,6 @@ "coord": "io.kotlintest:kotlintest-extensions:3.4.2", "dependencies": [ "commons-io:commons-io:2.6", - "org.slf4j:slf4j-api:1.7.30", "com.univocity:univocity-parsers:2.8.1", "io.mockk:mockk-agent-jvm:1.9.1", "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10", @@ -6023,7 +7643,6 @@ "org.eclipse.jgit:org.eclipse.jgit:4.4.1.201607150455-r", "net.bytebuddy:byte-buddy:1.9.3", "org.junit.jupiter:junit-jupiter-api:5.7.0", - "org.opentest4j:opentest4j:1.2.0", "io.mockk:mockk-agent-common:1.9.1", "io.mockk:mockk-agent-api:1.9.1", "io.arrow-kt:arrow-core-data:0.9.0", @@ -6031,11 +7650,9 @@ "io.mockk:mockk-common:1.9.1", "io.kotlintest:kotlintest-assertions:3.4.2", "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.1.1", - "org.apiguardian:apiguardian-api:1.1.0", "io.mockk:mockk-dsl:1.9.1", "com.github.wumpz:diffutils:2.2", "org.jetbrains.kotlin:kotlin-reflect:1.4.10", - "org.junit.platform:junit-platform-commons:1.7.0", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10", "io.mockk:mockk-dsl-jvm:1.9.1", "org.jetbrains.kotlin:kotlin-stdlib:1.4.10", @@ -6043,7 +7660,7 @@ "io.mockk:mockk:1.9.1", "io.arrow-kt:arrow-annotations:0.9.0", "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1", - "org.jetbrains:annotations:13.0" + "org.slf4j:slf4j-api:1.7.31" ], "directDependencies": [ "commons-io:commons-io:2.6", @@ -6068,20 +7685,17 @@ { "coord": "io.kotlintest:kotlintest-extensions:jar:sources:3.4.2", "dependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", "com.github.wumpz:diffutils:jar:sources:2.2", "org.jetbrains.kotlin:kotlin-reflect:jar:sources:1.4.10", - "org.jetbrains:annotations:jar:sources:13.0", "io.mockk:mockk-agent-api:jar:sources:1.9.1", "com.univocity:univocity-parsers:jar:sources:2.8.1", - "org.junit.platform:junit-platform-commons:jar:sources:1.7.0", "org.jetbrains.kotlin:kotlin-stdlib-common:jar:sources:1.4.10", "io.kotlintest:kotlintest-core:jar:sources:3.4.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", "io.mockk:mockk-common:jar:sources:1.9.1", "net.bytebuddy:byte-buddy:jar:sources:1.9.3", "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.4.10", "commons-io:commons-io:jar:sources:2.6", - "org.opentest4j:opentest4j:jar:sources:1.2.0", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:jar:sources:1.4.10", "net.bytebuddy:byte-buddy-agent:jar:sources:1.9.3", "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:jar:sources:1.1.1", @@ -6095,7 +7709,6 @@ "io.arrow-kt:arrow-annotations:jar:sources:0.9.0", "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0", "org.jetbrains.kotlinx:kotlinx-coroutines-core:jar:sources:1.1.1", - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", "io.mockk:mockk-agent-common:jar:sources:1.9.1", "org.objenesis:objenesis:jar:sources:2.6", "io.kindedj:kindedj:jar:sources:1.1.0" @@ -6124,7 +7737,6 @@ "coord": "io.kotlintest:kotlintest-runner-console:3.4.2", "dependencies": [ "commons-io:commons-io:2.6", - "org.slf4j:slf4j-api:1.7.30", "com.univocity:univocity-parsers:2.8.1", "io.mockk:mockk-agent-jvm:1.9.1", "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10", @@ -6134,7 +7746,6 @@ "net.bytebuddy:byte-buddy:1.9.3", "org.junit.jupiter:junit-jupiter-api:5.7.0", "io.github.classgraph:classgraph:4.8.1", - "org.opentest4j:opentest4j:1.2.0", "io.mockk:mockk-agent-common:1.9.1", "io.mockk:mockk-agent-api:1.9.1", "io.arrow-kt:arrow-core-data:0.9.0", @@ -6142,14 +7753,12 @@ "io.mockk:mockk-common:1.9.1", "io.kotlintest:kotlintest-assertions:3.4.2", "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.1.1", - "org.apiguardian:apiguardian-api:1.1.0", "net.sourceforge.argparse4j:argparse4j:0.8.1", "io.mockk:mockk-dsl:1.9.1", "com.github.wumpz:diffutils:2.2", "org.jetbrains.kotlin:kotlin-reflect:1.4.10", "com.github.ajalt:mordant:1.2.1", "com.github.ajalt:colormath:1.2.0", - "org.junit.platform:junit-platform-commons:1.7.0", "io.arrow-kt:arrow-core-extensions:0.9.0", "io.kotlintest:kotlintest-extensions:3.4.2", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10", @@ -6161,7 +7770,7 @@ "io.arrow-kt:arrow-annotations:0.9.0", "io.kotlintest:kotlintest-runner-jvm:3.4.2", "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1", - "org.jetbrains:annotations:13.0" + "org.slf4j:slf4j-api:1.7.31" ], "directDependencies": [ "com.github.ajalt:mordant:1.2.1", @@ -6186,22 +7795,19 @@ { "coord": "io.kotlintest:kotlintest-runner-console:jar:sources:3.4.2", "dependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", "com.github.wumpz:diffutils:jar:sources:2.2", "net.sourceforge.argparse4j:argparse4j:jar:sources:0.8.1", "org.jetbrains.kotlin:kotlin-reflect:jar:sources:1.4.10", - "org.jetbrains:annotations:jar:sources:13.0", "io.mockk:mockk-agent-api:jar:sources:1.9.1", "com.univocity:univocity-parsers:jar:sources:2.8.1", "io.kotlintest:kotlintest-runner-jvm:jar:sources:3.4.2", - "org.junit.platform:junit-platform-commons:jar:sources:1.7.0", "org.jetbrains.kotlin:kotlin-stdlib-common:jar:sources:1.4.10", "io.kotlintest:kotlintest-core:jar:sources:3.4.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", "io.mockk:mockk-common:jar:sources:1.9.1", "net.bytebuddy:byte-buddy:jar:sources:1.9.3", "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.4.10", "commons-io:commons-io:jar:sources:2.6", - "org.opentest4j:opentest4j:jar:sources:1.2.0", "io.arrow-kt:arrow-core-extensions:jar:sources:0.9.0", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:jar:sources:1.4.10", "net.bytebuddy:byte-buddy-agent:jar:sources:1.9.3", @@ -6221,7 +7827,6 @@ "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0", "org.jetbrains.kotlinx:kotlinx-coroutines-core:jar:sources:1.1.1", "com.github.ajalt:colormath:jar:sources:1.2.0", - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", "io.mockk:mockk-agent-common:jar:sources:1.9.1", "org.objenesis:objenesis:jar:sources:2.6", "io.kindedj:kindedj:jar:sources:1.1.0" @@ -6249,36 +7854,32 @@ { "coord": "io.kotlintest:kotlintest-runner-junit5:3.4.2", "dependencies": [ + "org.junit.platform:junit-platform-suite-api:1.8.0-M1", "commons-io:commons-io:2.6", - "org.junit.platform:junit-platform-engine:1.6.0", - "org.junit.platform:junit-platform-launcher:1.6.0", - "org.slf4j:slf4j-api:1.7.30", "com.univocity:univocity-parsers:2.8.1", "io.mockk:mockk-agent-jvm:1.9.1", "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10", "io.kindedj:kindedj:1.1.0", "io.kotlintest:kotlintest-core:3.4.2", + "org.junit.platform:junit-platform-launcher:1.8.0-M1", "org.eclipse.jgit:org.eclipse.jgit:4.4.1.201607150455-r", "net.bytebuddy:byte-buddy:1.9.3", "org.junit.jupiter:junit-jupiter-api:5.7.0", "io.github.classgraph:classgraph:4.8.1", - "org.opentest4j:opentest4j:1.2.0", "io.mockk:mockk-agent-common:1.9.1", "io.mockk:mockk-agent-api:1.9.1", - "org.junit.platform:junit-platform-suite-api:1.6.0", "io.arrow-kt:arrow-core-data:0.9.0", "org.objenesis:objenesis:2.6", "io.mockk:mockk-common:1.9.1", "io.kotlintest:kotlintest-assertions:3.4.2", "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.1.1", - "org.apiguardian:apiguardian-api:1.1.0", "net.sourceforge.argparse4j:argparse4j:0.8.1", "io.mockk:mockk-dsl:1.9.1", "com.github.wumpz:diffutils:2.2", + "org.junit.platform:junit-platform-engine:1.8.0-M1", "org.jetbrains.kotlin:kotlin-reflect:1.4.10", "com.github.ajalt:mordant:1.2.1", "com.github.ajalt:colormath:1.2.0", - "org.junit.platform:junit-platform-commons:1.7.0", "io.arrow-kt:arrow-core-extensions:0.9.0", "io.kotlintest:kotlintest-extensions:3.4.2", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10", @@ -6291,14 +7892,14 @@ "io.arrow-kt:arrow-annotations:0.9.0", "io.kotlintest:kotlintest-runner-jvm:3.4.2", "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1", - "org.jetbrains:annotations:13.0" + "org.slf4j:slf4j-api:1.7.31" ], "directDependencies": [ - "org.junit.platform:junit-platform-engine:1.6.0", - "org.junit.platform:junit-platform-launcher:1.6.0", + "org.junit.platform:junit-platform-suite-api:1.8.0-M1", "io.kotlintest:kotlintest-core:3.4.2", - "org.junit.platform:junit-platform-suite-api:1.6.0", + "org.junit.platform:junit-platform-launcher:1.8.0-M1", "io.kotlintest:kotlintest-assertions:3.4.2", + "org.junit.platform:junit-platform-engine:1.8.0-M1", "io.kotlintest:kotlintest-runner-console:3.4.2", "io.kotlintest:kotlintest-runner-jvm:3.4.2" ], @@ -6319,28 +7920,25 @@ { "coord": "io.kotlintest:kotlintest-runner-junit5:jar:sources:3.4.2", "dependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", "com.github.wumpz:diffutils:jar:sources:2.2", - "org.junit.platform:junit-platform-suite-api:jar:sources:1.6.0", "net.sourceforge.argparse4j:argparse4j:jar:sources:0.8.1", + "org.junit.platform:junit-platform-suite-api:jar:sources:1.8.0-M1", "org.jetbrains.kotlin:kotlin-reflect:jar:sources:1.4.10", - "org.jetbrains:annotations:jar:sources:13.0", "io.mockk:mockk-agent-api:jar:sources:1.9.1", "com.univocity:univocity-parsers:jar:sources:2.8.1", "io.kotlintest:kotlintest-runner-jvm:jar:sources:3.4.2", - "org.junit.platform:junit-platform-commons:jar:sources:1.7.0", "org.jetbrains.kotlin:kotlin-stdlib-common:jar:sources:1.4.10", "io.kotlintest:kotlintest-core:jar:sources:3.4.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", "io.mockk:mockk-common:jar:sources:1.9.1", "net.bytebuddy:byte-buddy:jar:sources:1.9.3", "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.4.10", "commons-io:commons-io:jar:sources:2.6", - "org.opentest4j:opentest4j:jar:sources:1.2.0", - "org.junit.platform:junit-platform-engine:jar:sources:1.6.0", "io.arrow-kt:arrow-core-extensions:jar:sources:0.9.0", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:jar:sources:1.4.10", "net.bytebuddy:byte-buddy-agent:jar:sources:1.9.3", "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:jar:sources:1.1.1", + "org.junit.platform:junit-platform-launcher:jar:sources:1.8.0-M1", "io.mockk:mockk-dsl-jvm:jar:sources:1.9.1", "io.github.classgraph:classgraph:jar:sources:4.8.1", "io.mockk:mockk:jar:sources:1.9.1", @@ -6353,24 +7951,23 @@ "io.mockk:mockk-dsl:jar:sources:1.9.1", "io.arrow-kt:arrow-core-data:jar:sources:0.9.0", "io.arrow-kt:arrow-annotations:jar:sources:0.9.0", - "org.junit.platform:junit-platform-launcher:jar:sources:1.6.0", + "org.junit.platform:junit-platform-engine:jar:sources:1.8.0-M1", "com.github.ajalt:mordant:jar:sources:1.2.1", "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0", "org.jetbrains.kotlinx:kotlinx-coroutines-core:jar:sources:1.1.1", "com.github.ajalt:colormath:jar:sources:1.2.0", - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", "io.mockk:mockk-agent-common:jar:sources:1.9.1", "org.objenesis:objenesis:jar:sources:2.6", "io.kindedj:kindedj:jar:sources:1.1.0" ], "directDependencies": [ - "org.junit.platform:junit-platform-suite-api:jar:sources:1.6.0", + "org.junit.platform:junit-platform-suite-api:jar:sources:1.8.0-M1", "io.kotlintest:kotlintest-runner-jvm:jar:sources:3.4.2", "io.kotlintest:kotlintest-core:jar:sources:3.4.2", - "org.junit.platform:junit-platform-engine:jar:sources:1.6.0", + "org.junit.platform:junit-platform-launcher:jar:sources:1.8.0-M1", "io.kotlintest:kotlintest-assertions:jar:sources:3.4.2", "io.kotlintest:kotlintest-runner-console:jar:sources:3.4.2", - "org.junit.platform:junit-platform-launcher:jar:sources:1.6.0" + "org.junit.platform:junit-platform-engine:jar:sources:1.8.0-M1" ], "exclusions": [ "com.google.template:soy", @@ -6390,7 +7987,6 @@ "coord": "io.kotlintest:kotlintest-runner-jvm:3.4.2", "dependencies": [ "commons-io:commons-io:2.6", - "org.slf4j:slf4j-api:1.7.30", "com.univocity:univocity-parsers:2.8.1", "io.mockk:mockk-agent-jvm:1.9.1", "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10", @@ -6400,7 +7996,6 @@ "net.bytebuddy:byte-buddy:1.9.3", "org.junit.jupiter:junit-jupiter-api:5.7.0", "io.github.classgraph:classgraph:4.8.1", - "org.opentest4j:opentest4j:1.2.0", "io.mockk:mockk-agent-common:1.9.1", "io.mockk:mockk-agent-api:1.9.1", "io.arrow-kt:arrow-core-data:0.9.0", @@ -6408,11 +8003,9 @@ "io.mockk:mockk-common:1.9.1", "io.kotlintest:kotlintest-assertions:3.4.2", "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.1.1", - "org.apiguardian:apiguardian-api:1.1.0", "io.mockk:mockk-dsl:1.9.1", "com.github.wumpz:diffutils:2.2", "org.jetbrains.kotlin:kotlin-reflect:1.4.10", - "org.junit.platform:junit-platform-commons:1.7.0", "io.arrow-kt:arrow-core-extensions:0.9.0", "io.kotlintest:kotlintest-extensions:3.4.2", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10", @@ -6423,15 +8016,15 @@ "io.arrow-kt:arrow-typeclasses:0.9.0", "io.arrow-kt:arrow-annotations:0.9.0", "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1", - "org.jetbrains:annotations:13.0" + "org.slf4j:slf4j-api:1.7.31" ], "directDependencies": [ - "org.slf4j:slf4j-api:1.7.30", "io.kotlintest:kotlintest-core:3.4.2", "io.github.classgraph:classgraph:4.8.1", "io.arrow-kt:arrow-core-extensions:0.9.0", "io.kotlintest:kotlintest-extensions:3.4.2", - "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1" + "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", @@ -6450,20 +8043,17 @@ { "coord": "io.kotlintest:kotlintest-runner-jvm:jar:sources:3.4.2", "dependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", "com.github.wumpz:diffutils:jar:sources:2.2", "org.jetbrains.kotlin:kotlin-reflect:jar:sources:1.4.10", - "org.jetbrains:annotations:jar:sources:13.0", "io.mockk:mockk-agent-api:jar:sources:1.9.1", "com.univocity:univocity-parsers:jar:sources:2.8.1", - "org.junit.platform:junit-platform-commons:jar:sources:1.7.0", "org.jetbrains.kotlin:kotlin-stdlib-common:jar:sources:1.4.10", "io.kotlintest:kotlintest-core:jar:sources:3.4.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", "io.mockk:mockk-common:jar:sources:1.9.1", "net.bytebuddy:byte-buddy:jar:sources:1.9.3", "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.4.10", "commons-io:commons-io:jar:sources:2.6", - "org.opentest4j:opentest4j:jar:sources:1.2.0", "io.arrow-kt:arrow-core-extensions:jar:sources:0.9.0", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:jar:sources:1.4.10", "net.bytebuddy:byte-buddy-agent:jar:sources:1.9.3", @@ -6481,14 +8071,13 @@ "io.arrow-kt:arrow-annotations:jar:sources:0.9.0", "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0", "org.jetbrains.kotlinx:kotlinx-coroutines-core:jar:sources:1.1.1", - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", "io.mockk:mockk-agent-common:jar:sources:1.9.1", "org.objenesis:objenesis:jar:sources:2.6", "io.kindedj:kindedj:jar:sources:1.1.0" ], "directDependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", "io.kotlintest:kotlintest-core:jar:sources:3.4.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", "io.arrow-kt:arrow-core-extensions:jar:sources:0.9.0", "io.github.classgraph:classgraph:jar:sources:4.8.1", "io.kotlintest:kotlintest-extensions:jar:sources:3.4.2", @@ -6509,3700 +8098,3481 @@ "url": "https://repo1.maven.org/maven2/io/kotlintest/kotlintest-runner-jvm/3.4.2/kotlintest-runner-jvm-3.4.2-sources.jar" }, { - "coord": "io.lettuce:lettuce-core:5.3.1.RELEASE", + "coord": "io.lettuce:lettuce-core:6.0.2.RELEASE", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3", - "io.netty:netty-codec:4.1.53.Final", - "io.netty:netty-handler:4.1.53.Final", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-resolver:4.1.53.Final", - "io.projectreactor:reactor-core:3.3.6.RELEASE", - "io.netty:netty-transport:4.1.53.Final" + "io.netty:netty-handler:4.1.66.Final", + "io.projectreactor:reactor-core:3.3.13.RELEASE", + "io.netty:netty-transport:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final" ], "directDependencies": [ - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-handler:4.1.53.Final", - "io.netty:netty-transport:4.1.53.Final", - "io.projectreactor:reactor-core:3.3.6.RELEASE" + "io.netty:netty-common:4.1.66.Final", + "io.netty:netty-handler:4.1.66.Final", + "io.netty:netty-transport:4.1.66.Final", + "io.projectreactor:reactor-core:3.3.13.RELEASE" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/lettuce/lettuce-core/5.3.1.RELEASE/lettuce-core-5.3.1.RELEASE.jar", + "file": "v1/https/repo1.maven.org/maven2/io/lettuce/lettuce-core/6.0.2.RELEASE/lettuce-core-6.0.2.RELEASE.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/lettuce/lettuce-core/5.3.1.RELEASE/lettuce-core-5.3.1.RELEASE.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/lettuce/lettuce-core/5.3.1.RELEASE/lettuce-core-5.3.1.RELEASE.jar", - "https://jcenter.bintray.com/io/lettuce/lettuce-core/5.3.1.RELEASE/lettuce-core-5.3.1.RELEASE.jar", - "https://maven.google.com/io/lettuce/lettuce-core/5.3.1.RELEASE/lettuce-core-5.3.1.RELEASE.jar" + "https://repo1.maven.org/maven2/io/lettuce/lettuce-core/6.0.2.RELEASE/lettuce-core-6.0.2.RELEASE.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/lettuce/lettuce-core/6.0.2.RELEASE/lettuce-core-6.0.2.RELEASE.jar", + "https://jcenter.bintray.com/io/lettuce/lettuce-core/6.0.2.RELEASE/lettuce-core-6.0.2.RELEASE.jar", + "https://maven.google.com/io/lettuce/lettuce-core/6.0.2.RELEASE/lettuce-core-6.0.2.RELEASE.jar" ], - "sha256": "ec5903a2c80b4eeab718966855f315f64aac3614c7c567849cad9f5bfb0ffa0b", - "url": "https://repo1.maven.org/maven2/io/lettuce/lettuce-core/5.3.1.RELEASE/lettuce-core-5.3.1.RELEASE.jar" + "sha256": "dab8524a55c7a14915aab15d87bc08f89c27655f091a0be3f1a9f8a0ea8d94c5", + "url": "https://repo1.maven.org/maven2/io/lettuce/lettuce-core/6.0.2.RELEASE/lettuce-core-6.0.2.RELEASE.jar" }, { - "coord": "io.lettuce:lettuce-core:jar:sources:5.3.1.RELEASE", + "coord": "io.lettuce:lettuce-core:jar:sources:6.0.2.RELEASE", "dependencies": [ - "io.netty:netty-handler:jar:sources:4.1.53.Final", - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "io.netty:netty-resolver:jar:sources:4.1.53.Final", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.netty:netty-codec:jar:sources:4.1.53.Final", - "io.projectreactor:reactor-core:jar:sources:3.3.6.RELEASE" + "io.netty:netty-transport:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final", + "io.netty:netty-handler:jar:sources:4.1.66.Final", + "io.projectreactor:reactor-core:jar:sources:3.3.13.RELEASE" ], "directDependencies": [ - "io.netty:netty-common:jar:sources:4.1.53.Final", - "io.netty:netty-handler:jar:sources:4.1.53.Final", - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "io.projectreactor:reactor-core:jar:sources:3.3.6.RELEASE" + "io.netty:netty-common:jar:sources:4.1.66.Final", + "io.netty:netty-handler:jar:sources:4.1.66.Final", + "io.netty:netty-transport:jar:sources:4.1.66.Final", + "io.projectreactor:reactor-core:jar:sources:3.3.13.RELEASE" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/lettuce/lettuce-core/5.3.1.RELEASE/lettuce-core-5.3.1.RELEASE-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/lettuce/lettuce-core/6.0.2.RELEASE/lettuce-core-6.0.2.RELEASE-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/lettuce/lettuce-core/5.3.1.RELEASE/lettuce-core-5.3.1.RELEASE-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/lettuce/lettuce-core/5.3.1.RELEASE/lettuce-core-5.3.1.RELEASE-sources.jar", - "https://jcenter.bintray.com/io/lettuce/lettuce-core/5.3.1.RELEASE/lettuce-core-5.3.1.RELEASE-sources.jar", - "https://maven.google.com/io/lettuce/lettuce-core/5.3.1.RELEASE/lettuce-core-5.3.1.RELEASE-sources.jar" + "https://repo1.maven.org/maven2/io/lettuce/lettuce-core/6.0.2.RELEASE/lettuce-core-6.0.2.RELEASE-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/lettuce/lettuce-core/6.0.2.RELEASE/lettuce-core-6.0.2.RELEASE-sources.jar", + "https://jcenter.bintray.com/io/lettuce/lettuce-core/6.0.2.RELEASE/lettuce-core-6.0.2.RELEASE-sources.jar", + "https://maven.google.com/io/lettuce/lettuce-core/6.0.2.RELEASE/lettuce-core-6.0.2.RELEASE-sources.jar" ], - "sha256": "9fd0a66b4efcd892f3701b0b336427923fad511573db1d8a036afc7574b3014b", - "url": "https://repo1.maven.org/maven2/io/lettuce/lettuce-core/5.3.1.RELEASE/lettuce-core-5.3.1.RELEASE-sources.jar" + "sha256": "ec47720936fe568f9d981c28edf5abadd7020ae2d38e2c35c261105b7b2e3e6e", + "url": "https://repo1.maven.org/maven2/io/lettuce/lettuce-core/6.0.2.RELEASE/lettuce-core-6.0.2.RELEASE-sources.jar" }, { - "coord": "io.micronaut.cache:micronaut-cache-caffeine:2.1.0", + "coord": "io.micronaut.cache:micronaut-cache-caffeine:2.4.0", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", - "org.yaml:snakeyaml:1.26", - "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "com.github.ben-manes.caffeine:caffeine:2.8.5", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "org.checkerframework:checker-qual:3.4.1", - "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "io.micronaut.cache:micronaut-cache-core:2.0.0", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", - "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.google.errorprone:error_prone_annotations:2.4.0", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut.cache:micronaut-cache-core:2.3.0", + "org.checkerframework:checker-qual:3.13.0", + "com.google.errorprone:error_prone_annotations:2.9.0", + "io.micronaut:micronaut-runtime:2.5.12", + "io.micronaut:micronaut-aop:2.5.12", + "com.github.ben-manes.caffeine:caffeine:2.8.8", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "com.github.ben-manes.caffeine:caffeine:2.8.5", - "io.micronaut.cache:micronaut-cache-core:2.0.0" + "com.github.ben-manes.caffeine:caffeine:2.8.8", + "io.micronaut.cache:micronaut-cache-core:2.3.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-caffeine/2.1.0/micronaut-cache-caffeine-2.1.0.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-caffeine/2.4.0/micronaut-cache-caffeine-2.4.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-caffeine/2.1.0/micronaut-cache-caffeine-2.1.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/cache/micronaut-cache-caffeine/2.1.0/micronaut-cache-caffeine-2.1.0.jar", - "https://jcenter.bintray.com/io/micronaut/cache/micronaut-cache-caffeine/2.1.0/micronaut-cache-caffeine-2.1.0.jar", - "https://maven.google.com/io/micronaut/cache/micronaut-cache-caffeine/2.1.0/micronaut-cache-caffeine-2.1.0.jar" + "https://repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-caffeine/2.4.0/micronaut-cache-caffeine-2.4.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/cache/micronaut-cache-caffeine/2.4.0/micronaut-cache-caffeine-2.4.0.jar", + "https://jcenter.bintray.com/io/micronaut/cache/micronaut-cache-caffeine/2.4.0/micronaut-cache-caffeine-2.4.0.jar", + "https://maven.google.com/io/micronaut/cache/micronaut-cache-caffeine/2.4.0/micronaut-cache-caffeine-2.4.0.jar" ], - "sha256": "606b748178e3ca56f76a0a5f1067fe6e3b0b1279e9e9d60cb795c57a2bdbb373", - "url": "https://repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-caffeine/2.1.0/micronaut-cache-caffeine-2.1.0.jar" + "sha256": "2b7f7a30d1a0f7a524c4509341c1596d541bbe57aa197b9c7719d5aed6415710", + "url": "https://repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-caffeine/2.4.0/micronaut-cache-caffeine-2.4.0.jar" }, { - "coord": "io.micronaut.cache:micronaut-cache-caffeine:jar:sources:2.1.0", + "coord": "io.micronaut.cache:micronaut-cache-caffeine:jar:sources:2.4.0", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "javax.inject:javax.inject:jar:sources:1", - "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "org.checkerframework:checker-qual:jar:sources:3.4.1", - "io.micronaut.cache:micronaut-cache-core:jar:sources:2.0.0", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "com.github.ben-manes.caffeine:caffeine:jar:sources:2.8.5", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "com.github.ben-manes.caffeine:caffeine:jar:sources:2.8.8", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "org.checkerframework:checker-qual:jar:sources:3.13.0", + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.micronaut.cache:micronaut-cache-core:jar:sources:2.3.0", + "com.google.errorprone:error_prone_annotations:jar:sources:2.9.0" ], "directDependencies": [ - "com.github.ben-manes.caffeine:caffeine:jar:sources:2.8.5", - "io.micronaut.cache:micronaut-cache-core:jar:sources:2.0.0" + "com.github.ben-manes.caffeine:caffeine:jar:sources:2.8.8", + "io.micronaut.cache:micronaut-cache-core:jar:sources:2.3.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-caffeine/2.1.0/micronaut-cache-caffeine-2.1.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-caffeine/2.4.0/micronaut-cache-caffeine-2.4.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-caffeine/2.1.0/micronaut-cache-caffeine-2.1.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/cache/micronaut-cache-caffeine/2.1.0/micronaut-cache-caffeine-2.1.0-sources.jar", - "https://jcenter.bintray.com/io/micronaut/cache/micronaut-cache-caffeine/2.1.0/micronaut-cache-caffeine-2.1.0-sources.jar", - "https://maven.google.com/io/micronaut/cache/micronaut-cache-caffeine/2.1.0/micronaut-cache-caffeine-2.1.0-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-caffeine/2.4.0/micronaut-cache-caffeine-2.4.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/cache/micronaut-cache-caffeine/2.4.0/micronaut-cache-caffeine-2.4.0-sources.jar", + "https://jcenter.bintray.com/io/micronaut/cache/micronaut-cache-caffeine/2.4.0/micronaut-cache-caffeine-2.4.0-sources.jar", + "https://maven.google.com/io/micronaut/cache/micronaut-cache-caffeine/2.4.0/micronaut-cache-caffeine-2.4.0-sources.jar" ], - "sha256": "0c2eb2794703a72ee69db5166d9e63d34f3e17c6d6ef101dd72218799236d4c8", - "url": "https://repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-caffeine/2.1.0/micronaut-cache-caffeine-2.1.0-sources.jar" + "sha256": "218644f55fb6dd53e1ef20d4542dd5f31eed15fd6a608b94f328d6df13ab5784", + "url": "https://repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-caffeine/2.4.0/micronaut-cache-caffeine-2.4.0-sources.jar" }, { - "coord": "io.micronaut.cache:micronaut-cache-core:2.0.0", + "coord": "io.micronaut.cache:micronaut-cache-core:2.3.0", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", - "org.yaml:snakeyaml:1.26", - "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", - "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut:micronaut-runtime:2.5.12", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-aop:2.1.3", - "io.micronaut:micronaut-http:2.1.3", - "io.micronaut:micronaut-inject:2.1.3", - "io.micronaut:micronaut-runtime:2.1.3" + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut:micronaut-inject:2.5.12", + "io.micronaut:micronaut-runtime:2.5.12" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-core/2.0.0/micronaut-cache-core-2.0.0.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-core/2.3.0/micronaut-cache-core-2.3.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-core/2.0.0/micronaut-cache-core-2.0.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/cache/micronaut-cache-core/2.0.0/micronaut-cache-core-2.0.0.jar", - "https://jcenter.bintray.com/io/micronaut/cache/micronaut-cache-core/2.0.0/micronaut-cache-core-2.0.0.jar", - "https://maven.google.com/io/micronaut/cache/micronaut-cache-core/2.0.0/micronaut-cache-core-2.0.0.jar" + "https://repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-core/2.3.0/micronaut-cache-core-2.3.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/cache/micronaut-cache-core/2.3.0/micronaut-cache-core-2.3.0.jar", + "https://jcenter.bintray.com/io/micronaut/cache/micronaut-cache-core/2.3.0/micronaut-cache-core-2.3.0.jar", + "https://maven.google.com/io/micronaut/cache/micronaut-cache-core/2.3.0/micronaut-cache-core-2.3.0.jar" ], - "sha256": "840d8b1acdc56c64ebc9a331ff41978792035895a1b50555822790fd0dd72eaf", - "url": "https://repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-core/2.0.0/micronaut-cache-core-2.0.0.jar" + "sha256": "144eb8c124e81379807c81bb5faad7934aee02707d61e4125f0290780384f9ee", + "url": "https://repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-core/2.3.0/micronaut-cache-core-2.3.0.jar" }, { - "coord": "io.micronaut.cache:micronaut-cache-core:jar:sources:2.0.0", + "coord": "io.micronaut.cache:micronaut-cache-core:jar:sources:2.3.0", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "javax.inject:javax.inject:jar:sources:1", - "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-runtime:jar:sources:2.1.3" + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-core/2.0.0/micronaut-cache-core-2.0.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-core/2.3.0/micronaut-cache-core-2.3.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-core/2.0.0/micronaut-cache-core-2.0.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/cache/micronaut-cache-core/2.0.0/micronaut-cache-core-2.0.0-sources.jar", - "https://jcenter.bintray.com/io/micronaut/cache/micronaut-cache-core/2.0.0/micronaut-cache-core-2.0.0-sources.jar", - "https://maven.google.com/io/micronaut/cache/micronaut-cache-core/2.0.0/micronaut-cache-core-2.0.0-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-core/2.3.0/micronaut-cache-core-2.3.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/cache/micronaut-cache-core/2.3.0/micronaut-cache-core-2.3.0-sources.jar", + "https://jcenter.bintray.com/io/micronaut/cache/micronaut-cache-core/2.3.0/micronaut-cache-core-2.3.0-sources.jar", + "https://maven.google.com/io/micronaut/cache/micronaut-cache-core/2.3.0/micronaut-cache-core-2.3.0-sources.jar" ], - "sha256": "098a0a72f84fc3584590437ad42e2f00950f6b0b57107bbe2053f8aa0b627273", - "url": "https://repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-core/2.0.0/micronaut-cache-core-2.0.0-sources.jar" + "sha256": "d708029c08871d776885399f7c95c239f1ed766df3b587610264d18dac72e89f", + "url": "https://repo1.maven.org/maven2/io/micronaut/cache/micronaut-cache-core/2.3.0/micronaut-cache-core-2.3.0-sources.jar" }, { - "coord": "io.micronaut.data:micronaut-data-model:2.0.1", + "coord": "io.micronaut.data:micronaut-data-model:2.4.7", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", "jakarta.transaction:jakarta.transaction-api:1.3.3", - "org.yaml:snakeyaml:1.26", + "io.micronaut.data:micronaut-data-tx:2.3.1", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut.data:micronaut-data-tx:1.1.3", - "io.micronaut:micronaut-aop:2.1.3", - "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "javax.inject:javax.inject:1", - "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-validation:2.1.3", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6" + "io.reactivex.rxjava2:rxjava:2.2.21", + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-validation:2.5.12", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-inject:2.1.3", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut.data:micronaut-data-tx:1.1.3", - "io.micronaut:micronaut-aop:2.1.3", - "io.micronaut:micronaut-validation:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6" + "io.micronaut.data:micronaut-data-tx:2.3.1", + "io.reactivex.rxjava2:rxjava:2.2.21", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-validation:2.5.12", + "io.micronaut:micronaut-inject:2.5.12" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/data/micronaut-data-model/2.0.1/micronaut-data-model-2.0.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/data/micronaut-data-model/2.4.7/micronaut-data-model-2.4.7.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-model/2.0.1/micronaut-data-model-2.0.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/data/micronaut-data-model/2.0.1/micronaut-data-model-2.0.1.jar", - "https://jcenter.bintray.com/io/micronaut/data/micronaut-data-model/2.0.1/micronaut-data-model-2.0.1.jar", - "https://maven.google.com/io/micronaut/data/micronaut-data-model/2.0.1/micronaut-data-model-2.0.1.jar" + "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-model/2.4.7/micronaut-data-model-2.4.7.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/data/micronaut-data-model/2.4.7/micronaut-data-model-2.4.7.jar", + "https://jcenter.bintray.com/io/micronaut/data/micronaut-data-model/2.4.7/micronaut-data-model-2.4.7.jar", + "https://maven.google.com/io/micronaut/data/micronaut-data-model/2.4.7/micronaut-data-model-2.4.7.jar" ], - "sha256": "1dabda68546515f1450aa02f914a4ed6109f0d7eef59a4a45b64e3ec519d5b44", - "url": "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-model/2.0.1/micronaut-data-model-2.0.1.jar" + "sha256": "0aa62ab6e824f3439d18b3916aa41c6e0cca2e3373c899a031b45dbb894ee1fc", + "url": "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-model/2.4.7/micronaut-data-model-2.4.7.jar" }, { - "coord": "io.micronaut.data:micronaut-data-model:jar:sources:2.0.1", + "coord": "io.micronaut.data:micronaut-data-model:jar:sources:2.4.7", "dependencies": [ - "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", + "io.micronaut.data:micronaut-data-tx:jar:sources:2.3.1", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.micronaut.data:micronaut-data-tx:jar:sources:1.1.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "javax.inject:javax.inject:jar:sources:1", - "org.yaml:snakeyaml:jar:sources:1.26", - "io.micronaut:micronaut-validation:jar:sources:2.1.3", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "jakarta.transaction:jakarta.transaction-api:jar:sources:1.3.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", + "io.micronaut:micronaut-validation:jar:sources:2.5.12", + "jakarta.transaction:jakarta.transaction-api:jar:sources:1.3.3" ], "directDependencies": [ - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "io.micronaut.data:micronaut-data-tx:jar:sources:1.1.3", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "io.micronaut:micronaut-validation:jar:sources:2.1.3", - "io.micronaut:micronaut-inject:jar:sources:2.1.3" + "io.micronaut.data:micronaut-data-tx:jar:sources:2.3.1", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", + "io.micronaut:micronaut-validation:jar:sources:2.5.12" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/data/micronaut-data-model/2.0.1/micronaut-data-model-2.0.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/data/micronaut-data-model/2.4.7/micronaut-data-model-2.4.7-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-model/2.0.1/micronaut-data-model-2.0.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/data/micronaut-data-model/2.0.1/micronaut-data-model-2.0.1-sources.jar", - "https://jcenter.bintray.com/io/micronaut/data/micronaut-data-model/2.0.1/micronaut-data-model-2.0.1-sources.jar", - "https://maven.google.com/io/micronaut/data/micronaut-data-model/2.0.1/micronaut-data-model-2.0.1-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-model/2.4.7/micronaut-data-model-2.4.7-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/data/micronaut-data-model/2.4.7/micronaut-data-model-2.4.7-sources.jar", + "https://jcenter.bintray.com/io/micronaut/data/micronaut-data-model/2.4.7/micronaut-data-model-2.4.7-sources.jar", + "https://maven.google.com/io/micronaut/data/micronaut-data-model/2.4.7/micronaut-data-model-2.4.7-sources.jar" ], - "sha256": "7b30ff87d3f62eed3c80e46d86badc47775bd2d6e869369d460518bd4c386543", - "url": "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-model/2.0.1/micronaut-data-model-2.0.1-sources.jar" + "sha256": "d9b65369b2a878c85b9dd47c3eb0c77a11b050d141817fcf7a92427a38b7e6db", + "url": "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-model/2.4.7/micronaut-data-model-2.4.7-sources.jar" }, { - "coord": "io.micronaut.data:micronaut-data-processor:2.1.1", + "coord": "io.micronaut.data:micronaut-data-processor:2.5.0", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", "jakarta.transaction:jakarta.transaction-api:1.3.3", - "org.yaml:snakeyaml:1.26", + "io.micronaut.data:micronaut-data-tx:2.3.1", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "io.micronaut.data:micronaut-data-model:2.0.1", - "org.slf4j:slf4j-api:1.7.30", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut.data:micronaut-data-tx:1.1.3", - "io.micronaut:micronaut-aop:2.1.3", - "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "javax.inject:javax.inject:1", - "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-validation:2.1.3", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6" + "io.reactivex.rxjava2:rxjava:2.2.21", + "io.micronaut.data:micronaut-data-model:2.4.7", + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-validation:2.5.12", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "io.micronaut.data:micronaut-data-model:2.0.1" + "io.micronaut.data:micronaut-data-model:2.4.7" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/data/micronaut-data-processor/2.1.1/micronaut-data-processor-2.1.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/data/micronaut-data-processor/2.5.0/micronaut-data-processor-2.5.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-processor/2.1.1/micronaut-data-processor-2.1.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/data/micronaut-data-processor/2.1.1/micronaut-data-processor-2.1.1.jar", - "https://jcenter.bintray.com/io/micronaut/data/micronaut-data-processor/2.1.1/micronaut-data-processor-2.1.1.jar", - "https://maven.google.com/io/micronaut/data/micronaut-data-processor/2.1.1/micronaut-data-processor-2.1.1.jar" + "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-processor/2.5.0/micronaut-data-processor-2.5.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/data/micronaut-data-processor/2.5.0/micronaut-data-processor-2.5.0.jar", + "https://jcenter.bintray.com/io/micronaut/data/micronaut-data-processor/2.5.0/micronaut-data-processor-2.5.0.jar", + "https://maven.google.com/io/micronaut/data/micronaut-data-processor/2.5.0/micronaut-data-processor-2.5.0.jar" ], - "sha256": "1dd8a79d46cc71d668e099830027bc119f4c609be0457471f1030161f9e61282", - "url": "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-processor/2.1.1/micronaut-data-processor-2.1.1.jar" + "sha256": "f6c5a352024ea578c53929b028cd11cfc13a46bac1fadbd501ffb406a50eb1b3", + "url": "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-processor/2.5.0/micronaut-data-processor-2.5.0.jar" }, { - "coord": "io.micronaut.data:micronaut-data-processor:jar:sources:2.1.1", + "coord": "io.micronaut.data:micronaut-data-processor:jar:sources:2.5.0", "dependencies": [ - "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", + "io.micronaut.data:micronaut-data-tx:jar:sources:2.3.1", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.micronaut.data:micronaut-data-tx:jar:sources:1.1.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "javax.inject:javax.inject:jar:sources:1", - "org.yaml:snakeyaml:jar:sources:1.26", - "io.micronaut.data:micronaut-data-model:jar:sources:2.0.1", - "io.micronaut:micronaut-validation:jar:sources:2.1.3", - "io.micronaut:micronaut-core:jar:sources:2.1.3", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", + "io.micronaut:micronaut-validation:jar:sources:2.5.12", "jakarta.transaction:jakarta.transaction-api:jar:sources:1.3.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut.data:micronaut-data-model:jar:sources:2.4.7" ], "directDependencies": [ - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "io.micronaut.data:micronaut-data-model:jar:sources:2.0.1" + "io.micronaut.data:micronaut-data-model:jar:sources:2.4.7" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/data/micronaut-data-processor/2.1.1/micronaut-data-processor-2.1.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/data/micronaut-data-processor/2.5.0/micronaut-data-processor-2.5.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-processor/2.1.1/micronaut-data-processor-2.1.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/data/micronaut-data-processor/2.1.1/micronaut-data-processor-2.1.1-sources.jar", - "https://jcenter.bintray.com/io/micronaut/data/micronaut-data-processor/2.1.1/micronaut-data-processor-2.1.1-sources.jar", - "https://maven.google.com/io/micronaut/data/micronaut-data-processor/2.1.1/micronaut-data-processor-2.1.1-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-processor/2.5.0/micronaut-data-processor-2.5.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/data/micronaut-data-processor/2.5.0/micronaut-data-processor-2.5.0-sources.jar", + "https://jcenter.bintray.com/io/micronaut/data/micronaut-data-processor/2.5.0/micronaut-data-processor-2.5.0-sources.jar", + "https://maven.google.com/io/micronaut/data/micronaut-data-processor/2.5.0/micronaut-data-processor-2.5.0-sources.jar" ], - "sha256": "72ab68a728f6078d3bb4bb525a3cc28e7a4734fd7c8478a6042644f5710773c8", - "url": "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-processor/2.1.1/micronaut-data-processor-2.1.1-sources.jar" + "sha256": "cd541f2b778f034735f98cc703cf18ae61169dc02e68d71ab3f590ec0f2a1de6", + "url": "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-processor/2.5.0/micronaut-data-processor-2.5.0-sources.jar" }, { - "coord": "io.micronaut.data:micronaut-data-tx:1.1.3", + "coord": "io.micronaut.data:micronaut-data-tx:2.3.1", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", "jakarta.transaction:jakarta.transaction-api:1.3.3", - "org.yaml:snakeyaml:1.26", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "io.micronaut:micronaut-aop:2.1.3", - "javax.annotation:javax.annotation-api:1.3.2", - "javax.inject:javax.inject:1", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6" + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "io.micronaut:micronaut-aop:2.1.3", - "io.micronaut:micronaut-inject:2.1.3", + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-inject:2.5.12", "jakarta.transaction:jakarta.transaction-api:1.3.3" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/data/micronaut-data-tx/1.1.3/micronaut-data-tx-1.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/data/micronaut-data-tx/2.3.1/micronaut-data-tx-2.3.1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-tx/1.1.3/micronaut-data-tx-1.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/data/micronaut-data-tx/1.1.3/micronaut-data-tx-1.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/data/micronaut-data-tx/1.1.3/micronaut-data-tx-1.1.3.jar", - "https://maven.google.com/io/micronaut/data/micronaut-data-tx/1.1.3/micronaut-data-tx-1.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-tx/2.3.1/micronaut-data-tx-2.3.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/data/micronaut-data-tx/2.3.1/micronaut-data-tx-2.3.1.jar", + "https://jcenter.bintray.com/io/micronaut/data/micronaut-data-tx/2.3.1/micronaut-data-tx-2.3.1.jar", + "https://maven.google.com/io/micronaut/data/micronaut-data-tx/2.3.1/micronaut-data-tx-2.3.1.jar" ], - "sha256": "b63672911065118da103fa7551626557adcef5d27bbc6db510445f9b9db649a4", - "url": "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-tx/1.1.3/micronaut-data-tx-1.1.3.jar" + "sha256": "61023640074ba5e967a253033d272a55717cea1bef62e63e7bdfeaa09b38c76a", + "url": "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-tx/2.3.1/micronaut-data-tx-2.3.1.jar" }, { - "coord": "io.micronaut.data:micronaut-data-tx:jar:sources:1.1.3", + "coord": "io.micronaut.data:micronaut-data-tx:jar:sources:2.3.1", "dependencies": [ - "io.micronaut:micronaut-aop:jar:sources:2.1.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "javax.inject:javax.inject:jar:sources:1", - "org.yaml:snakeyaml:jar:sources:1.26", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "jakarta.transaction:jakarta.transaction-api:jar:sources:1.3.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3" + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "jakarta.transaction:jakarta.transaction-api:jar:sources:1.3.3" ], "directDependencies": [ - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", "jakarta.transaction:jakarta.transaction-api:jar:sources:1.3.3" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/data/micronaut-data-tx/1.1.3/micronaut-data-tx-1.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/data/micronaut-data-tx/2.3.1/micronaut-data-tx-2.3.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-tx/1.1.3/micronaut-data-tx-1.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/data/micronaut-data-tx/1.1.3/micronaut-data-tx-1.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/data/micronaut-data-tx/1.1.3/micronaut-data-tx-1.1.3-sources.jar", - "https://maven.google.com/io/micronaut/data/micronaut-data-tx/1.1.3/micronaut-data-tx-1.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-tx/2.3.1/micronaut-data-tx-2.3.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/data/micronaut-data-tx/2.3.1/micronaut-data-tx-2.3.1-sources.jar", + "https://jcenter.bintray.com/io/micronaut/data/micronaut-data-tx/2.3.1/micronaut-data-tx-2.3.1-sources.jar", + "https://maven.google.com/io/micronaut/data/micronaut-data-tx/2.3.1/micronaut-data-tx-2.3.1-sources.jar" ], - "sha256": "84891b11c76f8dd6eb4308b134b0bd0639719f6e228d6ae5bddd78b84735f04e", - "url": "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-tx/1.1.3/micronaut-data-tx-1.1.3-sources.jar" + "sha256": "8e66c6d2d3bf1b9d2493120fe0f7e22f6681351053912c76a01723828b38755c", + "url": "https://repo1.maven.org/maven2/io/micronaut/data/micronaut-data-tx/2.3.1/micronaut-data-tx-2.3.1-sources.jar" }, { - "coord": "io.micronaut.grpc:micronaut-grpc-annotation:2.1.0", + "coord": "io.micronaut.grpc:micronaut-grpc-annotation:2.5.0", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", - "org.yaml:snakeyaml:1.26", - "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "javax.annotation:javax.annotation-api:1.3.2", - "javax.inject:javax.inject:1", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6" + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-inject:2.1.3" + "io.micronaut:micronaut-inject:2.5.12" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-annotation/2.1.0/micronaut-grpc-annotation-2.1.0.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-annotation/2.5.0/micronaut-grpc-annotation-2.5.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-annotation/2.1.0/micronaut-grpc-annotation-2.1.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-grpc-annotation/2.1.0/micronaut-grpc-annotation-2.1.0.jar", - "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-grpc-annotation/2.1.0/micronaut-grpc-annotation-2.1.0.jar", - "https://maven.google.com/io/micronaut/grpc/micronaut-grpc-annotation/2.1.0/micronaut-grpc-annotation-2.1.0.jar" + "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-annotation/2.5.0/micronaut-grpc-annotation-2.5.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-grpc-annotation/2.5.0/micronaut-grpc-annotation-2.5.0.jar", + "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-grpc-annotation/2.5.0/micronaut-grpc-annotation-2.5.0.jar", + "https://maven.google.com/io/micronaut/grpc/micronaut-grpc-annotation/2.5.0/micronaut-grpc-annotation-2.5.0.jar" ], - "sha256": "5e3424fa7e3d360d9f1b499126b1c2a2fd1f4789e38cda42756e205ffed249e1", - "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-annotation/2.1.0/micronaut-grpc-annotation-2.1.0.jar" + "sha256": "816a1e589381ef12eb855f27946b1f520d1b796d60e01a56cbd35f40be983728", + "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-annotation/2.5.0/micronaut-grpc-annotation-2.5.0.jar" }, { - "coord": "io.micronaut.grpc:micronaut-grpc-annotation:jar:sources:2.1.0", + "coord": "io.micronaut.grpc:micronaut-grpc-annotation:jar:sources:2.5.0", "dependencies": [ - "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "javax.inject:javax.inject:jar:sources:1", - "org.yaml:snakeyaml:jar:sources:1.26", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3" + "io.micronaut:micronaut-inject:jar:sources:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-inject:jar:sources:2.1.3" + "io.micronaut:micronaut-inject:jar:sources:2.5.12" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-annotation/2.1.0/micronaut-grpc-annotation-2.1.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-annotation/2.5.0/micronaut-grpc-annotation-2.5.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-annotation/2.1.0/micronaut-grpc-annotation-2.1.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-grpc-annotation/2.1.0/micronaut-grpc-annotation-2.1.0-sources.jar", - "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-grpc-annotation/2.1.0/micronaut-grpc-annotation-2.1.0-sources.jar", - "https://maven.google.com/io/micronaut/grpc/micronaut-grpc-annotation/2.1.0/micronaut-grpc-annotation-2.1.0-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-annotation/2.5.0/micronaut-grpc-annotation-2.5.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-grpc-annotation/2.5.0/micronaut-grpc-annotation-2.5.0-sources.jar", + "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-grpc-annotation/2.5.0/micronaut-grpc-annotation-2.5.0-sources.jar", + "https://maven.google.com/io/micronaut/grpc/micronaut-grpc-annotation/2.5.0/micronaut-grpc-annotation-2.5.0-sources.jar" ], - "sha256": "ee21442e3d767b4cadd4fdec206a66b3703787e3f3d34cdc17edafa41bb04b79", - "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-annotation/2.1.0/micronaut-grpc-annotation-2.1.0-sources.jar" + "sha256": "c23c4f06c74ce65f2b456ecb82aef726e7b352c646d3b6d60666f2bbce6ffeb1", + "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-annotation/2.5.0/micronaut-grpc-annotation-2.5.0-sources.jar" }, { - "coord": "io.micronaut.grpc:micronaut-grpc-client-runtime:2.1.0", + "coord": "io.micronaut.grpc:micronaut-grpc-client-runtime:2.4.0", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", - "io.micronaut.grpc:micronaut-grpc-annotation:2.1.0", - "org.yaml:snakeyaml:1.26", - "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", - "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "io.micronaut.grpc:micronaut-grpc-annotation:2.5.0", + "io.micronaut:micronaut-runtime:2.5.12", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-inject:2.1.3", - "io.micronaut:micronaut-runtime:2.1.3", - "io.micronaut.grpc:micronaut-grpc-annotation:2.1.0" + "io.micronaut:micronaut-inject:2.5.12", + "io.micronaut:micronaut-runtime:2.5.12", + "io.micronaut.grpc:micronaut-grpc-annotation:2.5.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-client-runtime/2.1.0/micronaut-grpc-client-runtime-2.1.0.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-client-runtime/2.4.0/micronaut-grpc-client-runtime-2.4.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-client-runtime/2.1.0/micronaut-grpc-client-runtime-2.1.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-grpc-client-runtime/2.1.0/micronaut-grpc-client-runtime-2.1.0.jar", - "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-grpc-client-runtime/2.1.0/micronaut-grpc-client-runtime-2.1.0.jar", - "https://maven.google.com/io/micronaut/grpc/micronaut-grpc-client-runtime/2.1.0/micronaut-grpc-client-runtime-2.1.0.jar" + "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-client-runtime/2.4.0/micronaut-grpc-client-runtime-2.4.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-grpc-client-runtime/2.4.0/micronaut-grpc-client-runtime-2.4.0.jar", + "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-grpc-client-runtime/2.4.0/micronaut-grpc-client-runtime-2.4.0.jar", + "https://maven.google.com/io/micronaut/grpc/micronaut-grpc-client-runtime/2.4.0/micronaut-grpc-client-runtime-2.4.0.jar" ], - "sha256": "31ae39a374fd2ae2a5b524a9557d3c03f7062b1c15b9727a06b7c12b2e20859b", - "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-client-runtime/2.1.0/micronaut-grpc-client-runtime-2.1.0.jar" + "sha256": "2ea4db5f067f03b205326ac5eccac9441a6f874a930e7cb1aca7604c80e7b3ec", + "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-client-runtime/2.4.0/micronaut-grpc-client-runtime-2.4.0.jar" }, { - "coord": "io.micronaut.grpc:micronaut-grpc-client-runtime:jar:sources:2.1.0", + "coord": "io.micronaut.grpc:micronaut-grpc-client-runtime:jar:sources:2.4.0", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "javax.inject:javax.inject:jar:sources:1", - "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "io.micronaut.grpc:micronaut-grpc-annotation:jar:sources:2.1.0", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "io.micronaut.grpc:micronaut-grpc-annotation:jar:sources:2.5.0" ], "directDependencies": [ - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "io.micronaut.grpc:micronaut-grpc-annotation:jar:sources:2.1.0" + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "io.micronaut.grpc:micronaut-grpc-annotation:jar:sources:2.5.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-client-runtime/2.1.0/micronaut-grpc-client-runtime-2.1.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-client-runtime/2.4.0/micronaut-grpc-client-runtime-2.4.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-client-runtime/2.1.0/micronaut-grpc-client-runtime-2.1.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-grpc-client-runtime/2.1.0/micronaut-grpc-client-runtime-2.1.0-sources.jar", - "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-grpc-client-runtime/2.1.0/micronaut-grpc-client-runtime-2.1.0-sources.jar", - "https://maven.google.com/io/micronaut/grpc/micronaut-grpc-client-runtime/2.1.0/micronaut-grpc-client-runtime-2.1.0-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-client-runtime/2.4.0/micronaut-grpc-client-runtime-2.4.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-grpc-client-runtime/2.4.0/micronaut-grpc-client-runtime-2.4.0-sources.jar", + "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-grpc-client-runtime/2.4.0/micronaut-grpc-client-runtime-2.4.0-sources.jar", + "https://maven.google.com/io/micronaut/grpc/micronaut-grpc-client-runtime/2.4.0/micronaut-grpc-client-runtime-2.4.0-sources.jar" ], - "sha256": "11951e8cced75df6f5777b7776fa1d9c976dcdb3cce7c8bd88acaa40d23d75af", - "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-client-runtime/2.1.0/micronaut-grpc-client-runtime-2.1.0-sources.jar" + "sha256": "9bce4db04b90139b8533bf62f1bb0a9f0f76b9d60a73cf596c9da5c1eebdc419", + "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-client-runtime/2.4.0/micronaut-grpc-client-runtime-2.4.0-sources.jar" }, { - "coord": "io.micronaut.grpc:micronaut-grpc-runtime:2.1.0", + "coord": "io.micronaut.grpc:micronaut-grpc-runtime:2.5.0", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", - "io.micronaut.grpc:micronaut-grpc-annotation:2.1.0", - "org.yaml:snakeyaml:1.26", - "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "io.micronaut.grpc:micronaut-grpc-client-runtime:2.1.0", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", - "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "javax.validation:validation-api:2.0.1.Final", - "io.micronaut.grpc:micronaut-grpc-server-runtime:2.1.0", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "io.micronaut.grpc:micronaut-grpc-client-runtime:2.4.0", + "io.micronaut.grpc:micronaut-grpc-server-runtime:2.4.0", + "io.micronaut:micronaut-runtime:2.5.12", + "io.micronaut.grpc:micronaut-grpc-annotation:2.5.0", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut.grpc:micronaut-grpc-client-runtime:2.1.0", - "io.micronaut.grpc:micronaut-grpc-server-runtime:2.1.0" + "io.micronaut.grpc:micronaut-grpc-client-runtime:2.4.0", + "io.micronaut.grpc:micronaut-grpc-server-runtime:2.4.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-runtime/2.1.0/micronaut-grpc-runtime-2.1.0.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-runtime/2.5.0/micronaut-grpc-runtime-2.5.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-runtime/2.1.0/micronaut-grpc-runtime-2.1.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-grpc-runtime/2.1.0/micronaut-grpc-runtime-2.1.0.jar", - "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-grpc-runtime/2.1.0/micronaut-grpc-runtime-2.1.0.jar", - "https://maven.google.com/io/micronaut/grpc/micronaut-grpc-runtime/2.1.0/micronaut-grpc-runtime-2.1.0.jar" + "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-runtime/2.5.0/micronaut-grpc-runtime-2.5.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-grpc-runtime/2.5.0/micronaut-grpc-runtime-2.5.0.jar", + "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-grpc-runtime/2.5.0/micronaut-grpc-runtime-2.5.0.jar", + "https://maven.google.com/io/micronaut/grpc/micronaut-grpc-runtime/2.5.0/micronaut-grpc-runtime-2.5.0.jar" ], - "sha256": "0b51e38cbe1a6e12511a964549eada4e2a26053507dd1ab2de8dde0adabb57e3", - "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-runtime/2.1.0/micronaut-grpc-runtime-2.1.0.jar" + "sha256": "dc21d1434f008affb50e24e0e934a45c18f37b69ee82af9c3c6cca5e61ce2805", + "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-runtime/2.5.0/micronaut-grpc-runtime-2.5.0.jar" }, { - "coord": "io.micronaut.grpc:micronaut-grpc-runtime:jar:sources:2.1.0", + "coord": "io.micronaut.grpc:micronaut-grpc-runtime:jar:sources:2.5.0", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "io.micronaut.grpc:micronaut-grpc-client-runtime:jar:sources:2.1.0", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "javax.inject:javax.inject:jar:sources:1", - "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "io.micronaut.grpc:micronaut-grpc-annotation:jar:sources:2.1.0", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut.grpc:micronaut-grpc-server-runtime:jar:sources:2.1.0", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "io.micronaut.grpc:micronaut-grpc-server-runtime:jar:sources:2.4.0", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.micronaut.grpc:micronaut-grpc-annotation:jar:sources:2.5.0", + "io.micronaut.grpc:micronaut-grpc-client-runtime:jar:sources:2.4.0" ], "directDependencies": [ - "io.micronaut.grpc:micronaut-grpc-client-runtime:jar:sources:2.1.0", - "io.micronaut.grpc:micronaut-grpc-server-runtime:jar:sources:2.1.0" + "io.micronaut.grpc:micronaut-grpc-client-runtime:jar:sources:2.4.0", + "io.micronaut.grpc:micronaut-grpc-server-runtime:jar:sources:2.4.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-runtime/2.1.0/micronaut-grpc-runtime-2.1.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-runtime/2.5.0/micronaut-grpc-runtime-2.5.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-runtime/2.1.0/micronaut-grpc-runtime-2.1.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-grpc-runtime/2.1.0/micronaut-grpc-runtime-2.1.0-sources.jar", - "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-grpc-runtime/2.1.0/micronaut-grpc-runtime-2.1.0-sources.jar", - "https://maven.google.com/io/micronaut/grpc/micronaut-grpc-runtime/2.1.0/micronaut-grpc-runtime-2.1.0-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-runtime/2.5.0/micronaut-grpc-runtime-2.5.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-grpc-runtime/2.5.0/micronaut-grpc-runtime-2.5.0-sources.jar", + "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-grpc-runtime/2.5.0/micronaut-grpc-runtime-2.5.0-sources.jar", + "https://maven.google.com/io/micronaut/grpc/micronaut-grpc-runtime/2.5.0/micronaut-grpc-runtime-2.5.0-sources.jar" ], - "sha256": "0b51e38cbe1a6e12511a964549eada4e2a26053507dd1ab2de8dde0adabb57e3", - "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-runtime/2.1.0/micronaut-grpc-runtime-2.1.0-sources.jar" + "sha256": "dc21d1434f008affb50e24e0e934a45c18f37b69ee82af9c3c6cca5e61ce2805", + "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-runtime/2.5.0/micronaut-grpc-runtime-2.5.0-sources.jar" }, { - "coord": "io.micronaut.grpc:micronaut-grpc-server-runtime:2.1.0", + "coord": "io.micronaut.grpc:micronaut-grpc-server-runtime:2.4.0", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", - "io.micronaut.grpc:micronaut-grpc-annotation:2.1.0", - "org.yaml:snakeyaml:1.26", - "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", - "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "io.micronaut.grpc:micronaut-grpc-annotation:2.5.0", + "io.micronaut:micronaut-runtime:2.5.12", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-inject:2.1.3", - "io.micronaut:micronaut-runtime:2.1.3", - "io.micronaut.grpc:micronaut-grpc-annotation:2.1.0" + "io.micronaut:micronaut-inject:2.5.12", + "io.micronaut:micronaut-runtime:2.5.12", + "io.micronaut.grpc:micronaut-grpc-annotation:2.5.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-server-runtime/2.1.0/micronaut-grpc-server-runtime-2.1.0.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-server-runtime/2.4.0/micronaut-grpc-server-runtime-2.4.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-server-runtime/2.1.0/micronaut-grpc-server-runtime-2.1.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-grpc-server-runtime/2.1.0/micronaut-grpc-server-runtime-2.1.0.jar", - "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-grpc-server-runtime/2.1.0/micronaut-grpc-server-runtime-2.1.0.jar", - "https://maven.google.com/io/micronaut/grpc/micronaut-grpc-server-runtime/2.1.0/micronaut-grpc-server-runtime-2.1.0.jar" + "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-server-runtime/2.4.0/micronaut-grpc-server-runtime-2.4.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-grpc-server-runtime/2.4.0/micronaut-grpc-server-runtime-2.4.0.jar", + "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-grpc-server-runtime/2.4.0/micronaut-grpc-server-runtime-2.4.0.jar", + "https://maven.google.com/io/micronaut/grpc/micronaut-grpc-server-runtime/2.4.0/micronaut-grpc-server-runtime-2.4.0.jar" ], - "sha256": "f0ba07f74d2dfe370f703f27b83118f45a77780c449d893610d72f419daf9e56", - "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-server-runtime/2.1.0/micronaut-grpc-server-runtime-2.1.0.jar" + "sha256": "668306e4cf949d39c3a71ebd66f7f75e97e77825d02d52a10f38c01e94db30d4", + "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-server-runtime/2.4.0/micronaut-grpc-server-runtime-2.4.0.jar" }, { - "coord": "io.micronaut.grpc:micronaut-grpc-server-runtime:jar:sources:2.1.0", + "coord": "io.micronaut.grpc:micronaut-grpc-server-runtime:jar:sources:2.4.0", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "javax.inject:javax.inject:jar:sources:1", - "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "io.micronaut.grpc:micronaut-grpc-annotation:jar:sources:2.1.0", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "io.micronaut.grpc:micronaut-grpc-annotation:jar:sources:2.5.0" ], "directDependencies": [ - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "io.micronaut.grpc:micronaut-grpc-annotation:jar:sources:2.1.0" + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "io.micronaut.grpc:micronaut-grpc-annotation:jar:sources:2.5.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-server-runtime/2.1.0/micronaut-grpc-server-runtime-2.1.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-server-runtime/2.4.0/micronaut-grpc-server-runtime-2.4.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-server-runtime/2.1.0/micronaut-grpc-server-runtime-2.1.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-grpc-server-runtime/2.1.0/micronaut-grpc-server-runtime-2.1.0-sources.jar", - "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-grpc-server-runtime/2.1.0/micronaut-grpc-server-runtime-2.1.0-sources.jar", - "https://maven.google.com/io/micronaut/grpc/micronaut-grpc-server-runtime/2.1.0/micronaut-grpc-server-runtime-2.1.0-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-server-runtime/2.4.0/micronaut-grpc-server-runtime-2.4.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-grpc-server-runtime/2.4.0/micronaut-grpc-server-runtime-2.4.0-sources.jar", + "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-grpc-server-runtime/2.4.0/micronaut-grpc-server-runtime-2.4.0-sources.jar", + "https://maven.google.com/io/micronaut/grpc/micronaut-grpc-server-runtime/2.4.0/micronaut-grpc-server-runtime-2.4.0-sources.jar" ], - "sha256": "fe11f95fa38a04be0601bc4f42a5626e56fc17170134d8e9d566c50b0b2b19d6", - "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-server-runtime/2.1.0/micronaut-grpc-server-runtime-2.1.0-sources.jar" + "sha256": "8d98ffe0eb9c59888f898e5be25e4eeb45126fc2bf2aeecdae1c694c5f252cbb", + "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-grpc-server-runtime/2.4.0/micronaut-grpc-server-runtime-2.4.0-sources.jar" }, { - "coord": "io.micronaut.grpc:micronaut-protobuff-support:2.1.0", + "coord": "io.micronaut.grpc:micronaut-protobuff-support:2.5.0", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", - "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", - "io.netty:netty-codec-http2:4.1.53.Final", - "io.netty:netty-codec:4.1.53.Final", - "com.google.j2objc:j2objc-annotations:1.3", - "io.netty:netty-handler:4.1.53.Final", - "org.yaml:snakeyaml:1.26", - "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-resolver:4.1.53.Final", - "com.google.guava:guava:30.0-android", - "io.netty:netty-transport:4.1.53.Final", - "com.google.code.gson:gson:2.8.6", - "io.micronaut:micronaut-http-netty:2.1.3", - "io.micronaut:micronaut-websocket:2.1.3", - "io.micronaut:micronaut-buffer-netty:2.1.3", - "io.netty:netty-codec-http:4.1.53.Final", - "io.reactivex.rxjava2:rxjava:2.2.20", - "com.google.protobuf:protobuf-java-util:3.13.0", - "io.micronaut:micronaut-aop:2.1.3", + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut:micronaut-http-netty:2.5.12", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.protobuf:protobuf-java:3.17.3", "javax.annotation:javax.annotation-api:1.3.2", - "com.google.guava:failureaccess:1.0.1", - "io.micronaut:micronaut-http:2.1.3", - "com.google.protobuf:protobuf-java:3.13.0", - "javax.inject:javax.inject:1", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.google.errorprone:error_prone_annotations:2.4.0", - "org.checkerframework:checker-compat-qual:2.5.5" + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-inject:2.1.3", - "io.micronaut:micronaut-http-netty:2.1.3", - "com.google.protobuf:protobuf-java-util:3.13.0", + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut:micronaut-http-netty:2.5.12", + "com.google.protobuf:protobuf-java-util:3.17.3", + "com.google.protobuf:protobuf-java:3.17.3", "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "com.google.protobuf:protobuf-java:3.13.0" + "io.micronaut:micronaut-inject:2.5.12" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-protobuff-support/2.1.0/micronaut-protobuff-support-2.1.0.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-protobuff-support/2.5.0/micronaut-protobuff-support-2.5.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-protobuff-support/2.1.0/micronaut-protobuff-support-2.1.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-protobuff-support/2.1.0/micronaut-protobuff-support-2.1.0.jar", - "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-protobuff-support/2.1.0/micronaut-protobuff-support-2.1.0.jar", - "https://maven.google.com/io/micronaut/grpc/micronaut-protobuff-support/2.1.0/micronaut-protobuff-support-2.1.0.jar" + "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-protobuff-support/2.5.0/micronaut-protobuff-support-2.5.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-protobuff-support/2.5.0/micronaut-protobuff-support-2.5.0.jar", + "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-protobuff-support/2.5.0/micronaut-protobuff-support-2.5.0.jar", + "https://maven.google.com/io/micronaut/grpc/micronaut-protobuff-support/2.5.0/micronaut-protobuff-support-2.5.0.jar" ], - "sha256": "ceef4e679c7d78e49ee32c88c1fa78504d8afe4ec0a408c143a5c047e9806ed5", - "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-protobuff-support/2.1.0/micronaut-protobuff-support-2.1.0.jar" + "sha256": "edbbb9bd1c0ceae1c6f97fd00a62cd918dac1ddb22796d9c8058d27edf718222", + "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-protobuff-support/2.5.0/micronaut-protobuff-support-2.5.0.jar" }, { - "coord": "io.micronaut.grpc:micronaut-protobuff-support:jar:sources:2.1.0", + "coord": "io.micronaut.grpc:micronaut-protobuff-support:jar:sources:2.5.0", "dependencies": [ - "io.netty:netty-handler:jar:sources:4.1.53.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.micronaut:micronaut-websocket:jar:sources:2.1.3", - "io.micronaut:micronaut-http-netty:jar:sources:2.1.3", - "io.micronaut:micronaut-buffer-netty:jar:sources:2.1.3", - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "io.netty:netty-codec-http2:jar:sources:4.1.53.Final", - "com.google.j2objc:j2objc-annotations:jar:sources:1.3", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "io.netty:netty-resolver:jar:sources:4.1.53.Final", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "javax.inject:javax.inject:jar:sources:1", - "org.yaml:snakeyaml:jar:sources:1.26", - "com.google.code.gson:gson:jar:sources:2.8.6", - "io.netty:netty-codec-http:jar:sources:4.1.53.Final", - "io.netty:netty-codec:jar:sources:4.1.53.Final", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "com.google.guava:guava:jar:sources:30.0-android", - "com.google.guava:failureaccess:jar:sources:1.0.1", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.micronaut:micronaut-http-netty:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "io.micronaut:micronaut-http-netty:jar:sources:2.1.3", - "com.google.protobuf:protobuf-java:jar:sources:3.13.0", - "com.google.protobuf:protobuf-java-util:jar:sources:3.13.0", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "com.google.protobuf:protobuf-java:jar:sources:3.17.3", + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.micronaut:micronaut-http-netty:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "com.google.protobuf:protobuf-java-util:jar:sources:3.17.3", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-protobuff-support/2.1.0/micronaut-protobuff-support-2.1.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/grpc/micronaut-protobuff-support/2.5.0/micronaut-protobuff-support-2.5.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-protobuff-support/2.1.0/micronaut-protobuff-support-2.1.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-protobuff-support/2.1.0/micronaut-protobuff-support-2.1.0-sources.jar", - "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-protobuff-support/2.1.0/micronaut-protobuff-support-2.1.0-sources.jar", - "https://maven.google.com/io/micronaut/grpc/micronaut-protobuff-support/2.1.0/micronaut-protobuff-support-2.1.0-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-protobuff-support/2.5.0/micronaut-protobuff-support-2.5.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/grpc/micronaut-protobuff-support/2.5.0/micronaut-protobuff-support-2.5.0-sources.jar", + "https://jcenter.bintray.com/io/micronaut/grpc/micronaut-protobuff-support/2.5.0/micronaut-protobuff-support-2.5.0-sources.jar", + "https://maven.google.com/io/micronaut/grpc/micronaut-protobuff-support/2.5.0/micronaut-protobuff-support-2.5.0-sources.jar" ], - "sha256": "6661c4c990ae564a661b2f087ca32cf55fe1f3f3ac100cd95b5dc7482454b08d", - "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-protobuff-support/2.1.0/micronaut-protobuff-support-2.1.0-sources.jar" + "sha256": "c6d89127a9230dddbcde2ffa95082141ef2cd00ad2aac3f8601b2eb53af01597", + "url": "https://repo1.maven.org/maven2/io/micronaut/grpc/micronaut-protobuff-support/2.5.0/micronaut-protobuff-support-2.5.0-sources.jar" }, { - "coord": "io.micronaut.redis:micronaut-redis-lettuce:3.0.0", + "coord": "io.micronaut.redis:micronaut-redis-lettuce:4.0.3", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3", - "io.netty:netty-codec:4.1.53.Final", - "io.netty:netty-handler:4.1.53.Final", - "org.yaml:snakeyaml:1.26", - "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-buffer:4.1.53.Final", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "io.netty:netty-resolver:4.1.53.Final", - "io.projectreactor:reactor-core:3.3.6.RELEASE", - "io.netty:netty-transport:4.1.53.Final", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.lettuce:lettuce-core:5.3.1.RELEASE", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "io.micronaut.cache:micronaut-cache-core:2.0.0", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", - "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "io.lettuce:lettuce-core:6.0.2.RELEASE", + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut.cache:micronaut-cache-core:2.3.0", + "jakarta.inject:jakarta.inject-api:2.0.0", + "io.netty:netty-handler:4.1.66.Final", + "io.projectreactor:reactor-core:3.3.13.RELEASE", + "io.netty:netty-transport:4.1.66.Final", + "io.micronaut:micronaut-runtime:2.5.12", + "io.netty:netty-common:4.1.66.Final", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.lettuce:lettuce-core:5.3.1.RELEASE", - "io.micronaut.cache:micronaut-cache-core:2.0.0" + "io.lettuce:lettuce-core:6.0.2.RELEASE", + "io.micronaut.cache:micronaut-cache-core:2.3.0", + "jakarta.inject:jakarta.inject-api:2.0.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/redis/micronaut-redis-lettuce/3.0.0/micronaut-redis-lettuce-3.0.0.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/redis/micronaut-redis-lettuce/4.0.3/micronaut-redis-lettuce-4.0.3.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/redis/micronaut-redis-lettuce/3.0.0/micronaut-redis-lettuce-3.0.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/redis/micronaut-redis-lettuce/3.0.0/micronaut-redis-lettuce-3.0.0.jar", - "https://jcenter.bintray.com/io/micronaut/redis/micronaut-redis-lettuce/3.0.0/micronaut-redis-lettuce-3.0.0.jar", - "https://maven.google.com/io/micronaut/redis/micronaut-redis-lettuce/3.0.0/micronaut-redis-lettuce-3.0.0.jar" + "https://repo1.maven.org/maven2/io/micronaut/redis/micronaut-redis-lettuce/4.0.3/micronaut-redis-lettuce-4.0.3.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/redis/micronaut-redis-lettuce/4.0.3/micronaut-redis-lettuce-4.0.3.jar", + "https://jcenter.bintray.com/io/micronaut/redis/micronaut-redis-lettuce/4.0.3/micronaut-redis-lettuce-4.0.3.jar", + "https://maven.google.com/io/micronaut/redis/micronaut-redis-lettuce/4.0.3/micronaut-redis-lettuce-4.0.3.jar" ], - "sha256": "09f3ae3ce919cafe36599a146db7425a45485ff40010a702d177e5ddca8ccaac", - "url": "https://repo1.maven.org/maven2/io/micronaut/redis/micronaut-redis-lettuce/3.0.0/micronaut-redis-lettuce-3.0.0.jar" + "sha256": "bf4e10a03537c0c4bf6be25de1f3a42ac8f8dfdbefaf86d23e5094d40da7fae1", + "url": "https://repo1.maven.org/maven2/io/micronaut/redis/micronaut-redis-lettuce/4.0.3/micronaut-redis-lettuce-4.0.3.jar" }, { - "coord": "io.micronaut.redis:micronaut-redis-lettuce:jar:sources:3.0.0", + "coord": "io.micronaut.redis:micronaut-redis-lettuce:jar:sources:4.0.3", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "io.netty:netty-handler:jar:sources:4.1.53.Final", - "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "io.netty:netty-resolver:jar:sources:4.1.53.Final", + "io.lettuce:lettuce-core:jar:sources:6.0.2.RELEASE", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "javax.inject:javax.inject:jar:sources:1", - "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "io.lettuce:lettuce-core:jar:sources:5.3.1.RELEASE", - "io.netty:netty-codec:jar:sources:4.1.53.Final", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut.cache:micronaut-cache-core:jar:sources:2.0.0", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "io.projectreactor:reactor-core:jar:sources:3.3.6.RELEASE", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "jakarta.inject:jakarta.inject-api:jar:sources:2.0.0", + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.netty:netty-transport:jar:sources:4.1.66.Final", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.micronaut.cache:micronaut-cache-core:jar:sources:2.3.0", + "io.netty:netty-common:jar:sources:4.1.66.Final", + "io.netty:netty-handler:jar:sources:4.1.66.Final", + "io.projectreactor:reactor-core:jar:sources:3.3.13.RELEASE" ], "directDependencies": [ - "io.lettuce:lettuce-core:jar:sources:5.3.1.RELEASE", - "io.micronaut.cache:micronaut-cache-core:jar:sources:2.0.0" + "io.lettuce:lettuce-core:jar:sources:6.0.2.RELEASE", + "io.micronaut.cache:micronaut-cache-core:jar:sources:2.3.0", + "jakarta.inject:jakarta.inject-api:jar:sources:2.0.0" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/redis/micronaut-redis-lettuce/3.0.0/micronaut-redis-lettuce-3.0.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/redis/micronaut-redis-lettuce/4.0.3/micronaut-redis-lettuce-4.0.3-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/redis/micronaut-redis-lettuce/3.0.0/micronaut-redis-lettuce-3.0.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/redis/micronaut-redis-lettuce/3.0.0/micronaut-redis-lettuce-3.0.0-sources.jar", - "https://jcenter.bintray.com/io/micronaut/redis/micronaut-redis-lettuce/3.0.0/micronaut-redis-lettuce-3.0.0-sources.jar", - "https://maven.google.com/io/micronaut/redis/micronaut-redis-lettuce/3.0.0/micronaut-redis-lettuce-3.0.0-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/redis/micronaut-redis-lettuce/4.0.3/micronaut-redis-lettuce-4.0.3-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/redis/micronaut-redis-lettuce/4.0.3/micronaut-redis-lettuce-4.0.3-sources.jar", + "https://jcenter.bintray.com/io/micronaut/redis/micronaut-redis-lettuce/4.0.3/micronaut-redis-lettuce-4.0.3-sources.jar", + "https://maven.google.com/io/micronaut/redis/micronaut-redis-lettuce/4.0.3/micronaut-redis-lettuce-4.0.3-sources.jar" ], - "sha256": "5b7d4af6d0d9ae529d1c5f8d7cdae42e0500c794ba710a1500cf2b798dda3105", - "url": "https://repo1.maven.org/maven2/io/micronaut/redis/micronaut-redis-lettuce/3.0.0/micronaut-redis-lettuce-3.0.0-sources.jar" + "sha256": "52c79fcd61e6e6e9164b5a22fc66bdf294e8c64449c38f146afca273c6591bc1", + "url": "https://repo1.maven.org/maven2/io/micronaut/redis/micronaut-redis-lettuce/4.0.3/micronaut-redis-lettuce-4.0.3-sources.jar" }, { - "coord": "io.micronaut.security:micronaut-security-annotations:2.1.2", + "coord": "io.micronaut.security:micronaut-security-annotations:2.5.0", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", - "org.yaml:snakeyaml:1.26", - "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "javax.annotation:javax.annotation-api:1.3.2", - "javax.inject:javax.inject:1", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6" + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-inject:2.1.3" + "io.micronaut:micronaut-inject:2.5.12" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/security/micronaut-security-annotations/2.1.2/micronaut-security-annotations-2.1.2.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/security/micronaut-security-annotations/2.5.0/micronaut-security-annotations-2.5.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security-annotations/2.1.2/micronaut-security-annotations-2.1.2.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/security/micronaut-security-annotations/2.1.2/micronaut-security-annotations-2.1.2.jar", - "https://jcenter.bintray.com/io/micronaut/security/micronaut-security-annotations/2.1.2/micronaut-security-annotations-2.1.2.jar", - "https://maven.google.com/io/micronaut/security/micronaut-security-annotations/2.1.2/micronaut-security-annotations-2.1.2.jar" + "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security-annotations/2.5.0/micronaut-security-annotations-2.5.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/security/micronaut-security-annotations/2.5.0/micronaut-security-annotations-2.5.0.jar", + "https://jcenter.bintray.com/io/micronaut/security/micronaut-security-annotations/2.5.0/micronaut-security-annotations-2.5.0.jar", + "https://maven.google.com/io/micronaut/security/micronaut-security-annotations/2.5.0/micronaut-security-annotations-2.5.0.jar" ], - "sha256": "d6671d40e071c0ff1d814c1d6e4f3d2c1a8431c0c749f2c13b7621fadc2e5f08", - "url": "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security-annotations/2.1.2/micronaut-security-annotations-2.1.2.jar" + "sha256": "31ec4c933d27069efcbe597cd7a33e9b077190f115382209d3c7f7e689769b07", + "url": "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security-annotations/2.5.0/micronaut-security-annotations-2.5.0.jar" }, { - "coord": "io.micronaut.security:micronaut-security-annotations:jar:sources:2.1.2", + "coord": "io.micronaut.security:micronaut-security-annotations:jar:sources:2.5.0", "dependencies": [ - "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "javax.inject:javax.inject:jar:sources:1", - "org.yaml:snakeyaml:jar:sources:1.26", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3" + "io.micronaut:micronaut-inject:jar:sources:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-inject:jar:sources:2.1.3" + "io.micronaut:micronaut-inject:jar:sources:2.5.12" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/security/micronaut-security-annotations/2.1.2/micronaut-security-annotations-2.1.2-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/security/micronaut-security-annotations/2.5.0/micronaut-security-annotations-2.5.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security-annotations/2.1.2/micronaut-security-annotations-2.1.2-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/security/micronaut-security-annotations/2.1.2/micronaut-security-annotations-2.1.2-sources.jar", - "https://jcenter.bintray.com/io/micronaut/security/micronaut-security-annotations/2.1.2/micronaut-security-annotations-2.1.2-sources.jar", - "https://maven.google.com/io/micronaut/security/micronaut-security-annotations/2.1.2/micronaut-security-annotations-2.1.2-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security-annotations/2.5.0/micronaut-security-annotations-2.5.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/security/micronaut-security-annotations/2.5.0/micronaut-security-annotations-2.5.0-sources.jar", + "https://jcenter.bintray.com/io/micronaut/security/micronaut-security-annotations/2.5.0/micronaut-security-annotations-2.5.0-sources.jar", + "https://maven.google.com/io/micronaut/security/micronaut-security-annotations/2.5.0/micronaut-security-annotations-2.5.0-sources.jar" ], - "sha256": "6edaf42a348b195e377734c3932e52aa5c3096823634685088f5c801a526e235", - "url": "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security-annotations/2.1.2/micronaut-security-annotations-2.1.2-sources.jar" + "sha256": "a4fed43988b546004cf669e8e74e04dd4657795ee69484ced2b4fcfe8931c9e4", + "url": "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security-annotations/2.5.0/micronaut-security-annotations-2.5.0-sources.jar" }, { - "coord": "io.micronaut.security:micronaut-security-session:2.1.2", + "coord": "io.micronaut.security:micronaut-security-session:2.5.0", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", - "org.yaml:snakeyaml:1.26", - "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "io.micronaut:micronaut-router:2.1.3", - "io.micronaut:micronaut-websocket:2.1.3", - "io.micronaut.security:micronaut-security:2.1.2", - "io.micronaut:micronaut-management:2.0.1", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut.security:micronaut-security-annotations:2.1.2", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "io.micronaut:micronaut-http-server:2.1.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", - "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-validation:2.1.3", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "io.micronaut:micronaut-session:2.1.3", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut:micronaut-session:2.5.12", + "io.micronaut:micronaut-http-server:2.5.12", + "io.micronaut.security:micronaut-security:2.5.0", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-inject:2.1.3", - "io.micronaut.security:micronaut-security:2.1.2", - "io.micronaut:micronaut-http:2.1.3", - "io.micronaut:micronaut-http-server:2.1.3", - "io.micronaut:micronaut-session:2.1.3" + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut:micronaut-session:2.5.12", + "io.micronaut:micronaut-http-server:2.5.12", + "io.micronaut.security:micronaut-security:2.5.0", + "io.micronaut:micronaut-inject:2.5.12" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/security/micronaut-security-session/2.1.2/micronaut-security-session-2.1.2.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/security/micronaut-security-session/2.5.0/micronaut-security-session-2.5.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security-session/2.1.2/micronaut-security-session-2.1.2.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/security/micronaut-security-session/2.1.2/micronaut-security-session-2.1.2.jar", - "https://jcenter.bintray.com/io/micronaut/security/micronaut-security-session/2.1.2/micronaut-security-session-2.1.2.jar", - "https://maven.google.com/io/micronaut/security/micronaut-security-session/2.1.2/micronaut-security-session-2.1.2.jar" + "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security-session/2.5.0/micronaut-security-session-2.5.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/security/micronaut-security-session/2.5.0/micronaut-security-session-2.5.0.jar", + "https://jcenter.bintray.com/io/micronaut/security/micronaut-security-session/2.5.0/micronaut-security-session-2.5.0.jar", + "https://maven.google.com/io/micronaut/security/micronaut-security-session/2.5.0/micronaut-security-session-2.5.0.jar" ], - "sha256": "094363f4df1d7cfd502961e07b315a817b74343e7fff1007a573bd52c1f00875", - "url": "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security-session/2.1.2/micronaut-security-session-2.1.2.jar" + "sha256": "e856e56a811ae82dffb8535b148e61df9b718ff34651457ad0f04a4d13b99128", + "url": "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security-session/2.5.0/micronaut-security-session-2.5.0.jar" }, { - "coord": "io.micronaut.security:micronaut-security-session:jar:sources:2.1.2", + "coord": "io.micronaut.security:micronaut-security-session:jar:sources:2.5.0", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.micronaut:micronaut-websocket:jar:sources:2.1.3", - "io.micronaut.security:micronaut-security-annotations:jar:sources:2.1.2", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "io.micronaut:micronaut-router:jar:sources:2.1.3", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "io.micronaut:micronaut-session:jar:sources:2.1.3", - "javax.inject:javax.inject:jar:sources:1", - "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-http-server:jar:sources:2.1.3", - "io.micronaut:micronaut-validation:jar:sources:2.1.3", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "io.micronaut.security:micronaut-security:jar:sources:2.1.2", - "io.micronaut:micronaut-management:jar:sources:2.0.1", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http-server:jar:sources:2.5.12", + "io.micronaut:micronaut-session:jar:sources:2.5.12", + "io.micronaut.security:micronaut-security:jar:sources:2.5.0", + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-session:jar:sources:2.1.3", - "io.micronaut:micronaut-http-server:jar:sources:2.1.3", - "io.micronaut.security:micronaut-security:jar:sources:2.1.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http-server:jar:sources:2.5.12", + "io.micronaut:micronaut-session:jar:sources:2.5.12", + "io.micronaut.security:micronaut-security:jar:sources:2.5.0", + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/security/micronaut-security-session/2.1.2/micronaut-security-session-2.1.2-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/security/micronaut-security-session/2.5.0/micronaut-security-session-2.5.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security-session/2.1.2/micronaut-security-session-2.1.2-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/security/micronaut-security-session/2.1.2/micronaut-security-session-2.1.2-sources.jar", - "https://jcenter.bintray.com/io/micronaut/security/micronaut-security-session/2.1.2/micronaut-security-session-2.1.2-sources.jar", - "https://maven.google.com/io/micronaut/security/micronaut-security-session/2.1.2/micronaut-security-session-2.1.2-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security-session/2.5.0/micronaut-security-session-2.5.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/security/micronaut-security-session/2.5.0/micronaut-security-session-2.5.0-sources.jar", + "https://jcenter.bintray.com/io/micronaut/security/micronaut-security-session/2.5.0/micronaut-security-session-2.5.0-sources.jar", + "https://maven.google.com/io/micronaut/security/micronaut-security-session/2.5.0/micronaut-security-session-2.5.0-sources.jar" ], - "sha256": "b5e20cf8b91701e5b3b730574b2487948b93c52c7eb5099151cc9e4e7f3efe53", - "url": "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security-session/2.1.2/micronaut-security-session-2.1.2-sources.jar" + "sha256": "ec7f6f7a408f30fcf1d098ef44286a8b22ee80ad2be79640c32b186da83d26a2", + "url": "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security-session/2.5.0/micronaut-security-session-2.5.0-sources.jar" }, { - "coord": "io.micronaut.security:micronaut-security:2.1.2", + "coord": "io.micronaut.security:micronaut-security:2.5.0", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", - "org.yaml:snakeyaml:1.26", - "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "io.micronaut:micronaut-router:2.1.3", - "io.micronaut:micronaut-websocket:2.1.3", - "io.micronaut:micronaut-management:2.0.1", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut.security:micronaut-security-annotations:2.1.2", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "io.micronaut:micronaut-http-server:2.1.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", - "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-validation:2.1.3", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut:micronaut-management:2.5.12", + "io.micronaut.security:micronaut-security-annotations:2.5.0", + "io.micronaut:micronaut-http-server:2.5.12", + "io.micronaut:micronaut-validation:2.5.12", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-inject:2.1.3", - "io.micronaut:micronaut-management:2.0.1", - "io.micronaut.security:micronaut-security-annotations:2.1.2", - "io.micronaut:micronaut-http:2.1.3", - "io.micronaut:micronaut-http-server:2.1.3", - "io.micronaut:micronaut-validation:2.1.3" + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut:micronaut-management:2.5.12", + "io.micronaut.security:micronaut-security-annotations:2.5.0", + "io.micronaut:micronaut-http-server:2.5.12", + "io.micronaut:micronaut-validation:2.5.12", + "io.micronaut:micronaut-inject:2.5.12" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/security/micronaut-security/2.1.2/micronaut-security-2.1.2.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/security/micronaut-security/2.5.0/micronaut-security-2.5.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security/2.1.2/micronaut-security-2.1.2.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/security/micronaut-security/2.1.2/micronaut-security-2.1.2.jar", - "https://jcenter.bintray.com/io/micronaut/security/micronaut-security/2.1.2/micronaut-security-2.1.2.jar", - "https://maven.google.com/io/micronaut/security/micronaut-security/2.1.2/micronaut-security-2.1.2.jar" + "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security/2.5.0/micronaut-security-2.5.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/security/micronaut-security/2.5.0/micronaut-security-2.5.0.jar", + "https://jcenter.bintray.com/io/micronaut/security/micronaut-security/2.5.0/micronaut-security-2.5.0.jar", + "https://maven.google.com/io/micronaut/security/micronaut-security/2.5.0/micronaut-security-2.5.0.jar" ], - "sha256": "d6cb6520753291ef1bad417a4428029bc6f0222eef2e5e793937e72e377341ae", - "url": "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security/2.1.2/micronaut-security-2.1.2.jar" + "sha256": "a766ebcd929485515dc052a2620268ced96783efa6485467347db40dfdc4af44", + "url": "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security/2.5.0/micronaut-security-2.5.0.jar" }, { - "coord": "io.micronaut.security:micronaut-security:jar:sources:2.1.2", + "coord": "io.micronaut.security:micronaut-security:jar:sources:2.5.0", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.micronaut:micronaut-websocket:jar:sources:2.1.3", - "io.micronaut.security:micronaut-security-annotations:jar:sources:2.1.2", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "io.micronaut:micronaut-router:jar:sources:2.1.3", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "javax.inject:javax.inject:jar:sources:1", - "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-http-server:jar:sources:2.1.3", - "io.micronaut:micronaut-validation:jar:sources:2.1.3", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "io.micronaut:micronaut-management:jar:sources:2.0.1", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http-server:jar:sources:2.5.12", + "io.micronaut:micronaut-management:jar:sources:2.5.12", + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.micronaut.security:micronaut-security-annotations:jar:sources:2.5.0", + "io.micronaut:micronaut-validation:jar:sources:2.5.12" ], "directDependencies": [ - "io.micronaut.security:micronaut-security-annotations:jar:sources:2.1.2", - "io.micronaut:micronaut-http-server:jar:sources:2.1.3", - "io.micronaut:micronaut-validation:jar:sources:2.1.3", - "io.micronaut:micronaut-management:jar:sources:2.0.1", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http-server:jar:sources:2.5.12", + "io.micronaut:micronaut-management:jar:sources:2.5.12", + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.micronaut.security:micronaut-security-annotations:jar:sources:2.5.0", + "io.micronaut:micronaut-validation:jar:sources:2.5.12" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/security/micronaut-security/2.1.2/micronaut-security-2.1.2-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/security/micronaut-security/2.5.0/micronaut-security-2.5.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security/2.1.2/micronaut-security-2.1.2-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/security/micronaut-security/2.1.2/micronaut-security-2.1.2-sources.jar", - "https://jcenter.bintray.com/io/micronaut/security/micronaut-security/2.1.2/micronaut-security-2.1.2-sources.jar", - "https://maven.google.com/io/micronaut/security/micronaut-security/2.1.2/micronaut-security-2.1.2-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security/2.5.0/micronaut-security-2.5.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/security/micronaut-security/2.5.0/micronaut-security-2.5.0-sources.jar", + "https://jcenter.bintray.com/io/micronaut/security/micronaut-security/2.5.0/micronaut-security-2.5.0-sources.jar", + "https://maven.google.com/io/micronaut/security/micronaut-security/2.5.0/micronaut-security-2.5.0-sources.jar" ], - "sha256": "bfcbc6bd6ae63a4d9cc83b27944d532efc66a290a2d5c893c82b17c0dacf1427", - "url": "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security/2.1.2/micronaut-security-2.1.2-sources.jar" + "sha256": "cb26fb21a51285725a91e3e02a7dc4f57276859401d06820d51e2c2a89a31560", + "url": "https://repo1.maven.org/maven2/io/micronaut/security/micronaut-security/2.5.0/micronaut-security-2.5.0-sources.jar" }, { - "coord": "io.micronaut.test:micronaut-test-core:2.2.1", + "coord": "io.micronaut.test:micronaut-test-core:2.3.7", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", - "org.yaml:snakeyaml:1.26", - "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", - "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "io.micronaut:micronaut-runtime:2.5.12", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-inject:2.1.3", - "io.micronaut:micronaut-runtime:2.1.3" + "io.micronaut:micronaut-inject:2.5.12", + "io.micronaut:micronaut-runtime:2.5.12" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/test/micronaut-test-core/2.2.1/micronaut-test-core-2.2.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/test/micronaut-test-core/2.3.7/micronaut-test-core-2.3.7.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-core/2.2.1/micronaut-test-core-2.2.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/test/micronaut-test-core/2.2.1/micronaut-test-core-2.2.1.jar", - "https://jcenter.bintray.com/io/micronaut/test/micronaut-test-core/2.2.1/micronaut-test-core-2.2.1.jar", - "https://maven.google.com/io/micronaut/test/micronaut-test-core/2.2.1/micronaut-test-core-2.2.1.jar" + "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-core/2.3.7/micronaut-test-core-2.3.7.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/test/micronaut-test-core/2.3.7/micronaut-test-core-2.3.7.jar", + "https://jcenter.bintray.com/io/micronaut/test/micronaut-test-core/2.3.7/micronaut-test-core-2.3.7.jar", + "https://maven.google.com/io/micronaut/test/micronaut-test-core/2.3.7/micronaut-test-core-2.3.7.jar" ], - "sha256": "6d83119d098775c75a0a9733d5fbd62964a76eee8e0ff4855b121d52a4ef9c18", - "url": "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-core/2.2.1/micronaut-test-core-2.2.1.jar" + "sha256": "c98a62eb08a135d3f5df748509b6e699032fe26bf700e3cdf2d28f3ffbbf8944", + "url": "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-core/2.3.7/micronaut-test-core-2.3.7.jar" }, { - "coord": "io.micronaut.test:micronaut-test-core:jar:sources:2.2.1", + "coord": "io.micronaut.test:micronaut-test-core:jar:sources:2.3.7", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "javax.inject:javax.inject:jar:sources:1", - "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-runtime:jar:sources:2.1.3" + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/test/micronaut-test-core/2.2.1/micronaut-test-core-2.2.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/test/micronaut-test-core/2.3.7/micronaut-test-core-2.3.7-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-core/2.2.1/micronaut-test-core-2.2.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/test/micronaut-test-core/2.2.1/micronaut-test-core-2.2.1-sources.jar", - "https://jcenter.bintray.com/io/micronaut/test/micronaut-test-core/2.2.1/micronaut-test-core-2.2.1-sources.jar", - "https://maven.google.com/io/micronaut/test/micronaut-test-core/2.2.1/micronaut-test-core-2.2.1-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-core/2.3.7/micronaut-test-core-2.3.7-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/test/micronaut-test-core/2.3.7/micronaut-test-core-2.3.7-sources.jar", + "https://jcenter.bintray.com/io/micronaut/test/micronaut-test-core/2.3.7/micronaut-test-core-2.3.7-sources.jar", + "https://maven.google.com/io/micronaut/test/micronaut-test-core/2.3.7/micronaut-test-core-2.3.7-sources.jar" ], - "sha256": "291c90b4b2c168e31abb4e15fa6983e647f80655f7f79c88031d1a6486e7e3d2", - "url": "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-core/2.2.1/micronaut-test-core-2.2.1-sources.jar" + "sha256": "1be64fab8c7afd308d48ce85360565e3128ccb3487c50ff9efb70e64f94590ea", + "url": "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-core/2.3.7/micronaut-test-core-2.3.7-sources.jar" }, { - "coord": "io.micronaut.test:micronaut-test-junit5:2.2.1", + "coord": "io.micronaut.test:micronaut-test-junit5:2.3.7", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", - "io.micronaut.test:micronaut-test-core:2.2.1", - "org.yaml:snakeyaml:1.26", - "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", + "org.apiguardian:apiguardian-api:1.1.1", "org.junit.jupiter:junit-jupiter-api:5.7.0", "org.opentest4j:opentest4j:1.2.0", - "io.reactivex.rxjava2:rxjava:2.2.20", - "org.apiguardian:apiguardian-api:1.1.0", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "javax.annotation:javax.annotation-api:1.3.2", - "org.junit.platform:junit-platform-commons:1.7.0", - "io.micronaut:micronaut-http:2.1.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", - "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "org.junit.platform:junit-platform-commons:1.8.0-M1", + "io.micronaut:micronaut-runtime:2.5.12", + "io.micronaut:micronaut-inject:2.5.12", + "io.micronaut.test:micronaut-test-core:2.3.7" ], "directDependencies": [ - "io.micronaut:micronaut-inject:2.1.3", - "io.micronaut:micronaut-runtime:2.1.3", - "io.micronaut.test:micronaut-test-core:2.2.1", + "io.micronaut:micronaut-inject:2.5.12", + "io.micronaut:micronaut-runtime:2.5.12", + "io.micronaut.test:micronaut-test-core:2.3.7", "org.junit.jupiter:junit-jupiter-api:5.7.0" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/test/micronaut-test-junit5/2.2.1/micronaut-test-junit5-2.2.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/test/micronaut-test-junit5/2.3.7/micronaut-test-junit5-2.3.7.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-junit5/2.2.1/micronaut-test-junit5-2.2.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/test/micronaut-test-junit5/2.2.1/micronaut-test-junit5-2.2.1.jar", - "https://jcenter.bintray.com/io/micronaut/test/micronaut-test-junit5/2.2.1/micronaut-test-junit5-2.2.1.jar", - "https://maven.google.com/io/micronaut/test/micronaut-test-junit5/2.2.1/micronaut-test-junit5-2.2.1.jar" + "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-junit5/2.3.7/micronaut-test-junit5-2.3.7.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/test/micronaut-test-junit5/2.3.7/micronaut-test-junit5-2.3.7.jar", + "https://jcenter.bintray.com/io/micronaut/test/micronaut-test-junit5/2.3.7/micronaut-test-junit5-2.3.7.jar", + "https://maven.google.com/io/micronaut/test/micronaut-test-junit5/2.3.7/micronaut-test-junit5-2.3.7.jar" ], - "sha256": "502cf238dc6ff159904932997c77ef2c600873cc533a3926f3b6545f67d688ff", - "url": "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-junit5/2.2.1/micronaut-test-junit5-2.2.1.jar" + "sha256": "a10e25e981eab9646b4f8c70cd361946b72d706d30d6bad4988403401209544e", + "url": "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-junit5/2.3.7/micronaut-test-junit5-2.3.7.jar" }, { - "coord": "io.micronaut.test:micronaut-test-junit5:jar:sources:2.2.1", + "coord": "io.micronaut.test:micronaut-test-junit5:jar:sources:2.3.7", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "org.junit.platform:junit-platform-commons:jar:sources:1.7.0", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "io.micronaut.test:micronaut-test-core:jar:sources:2.3.7", "org.opentest4j:opentest4j:jar:sources:1.2.0", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "javax.inject:javax.inject:jar:sources:1", - "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3", - "io.micronaut.test:micronaut-test-core:jar:sources:2.2.1" + "org.junit.platform:junit-platform-commons:jar:sources:1.8.0-M1", + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0" ], "directDependencies": [ - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "io.micronaut.test:micronaut-test-core:jar:sources:2.2.1", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "io.micronaut.test:micronaut-test-core:jar:sources:2.3.7", "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/test/micronaut-test-junit5/2.2.1/micronaut-test-junit5-2.2.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/test/micronaut-test-junit5/2.3.7/micronaut-test-junit5-2.3.7-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-junit5/2.2.1/micronaut-test-junit5-2.2.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/test/micronaut-test-junit5/2.2.1/micronaut-test-junit5-2.2.1-sources.jar", - "https://jcenter.bintray.com/io/micronaut/test/micronaut-test-junit5/2.2.1/micronaut-test-junit5-2.2.1-sources.jar", - "https://maven.google.com/io/micronaut/test/micronaut-test-junit5/2.2.1/micronaut-test-junit5-2.2.1-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-junit5/2.3.7/micronaut-test-junit5-2.3.7-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/test/micronaut-test-junit5/2.3.7/micronaut-test-junit5-2.3.7-sources.jar", + "https://jcenter.bintray.com/io/micronaut/test/micronaut-test-junit5/2.3.7/micronaut-test-junit5-2.3.7-sources.jar", + "https://maven.google.com/io/micronaut/test/micronaut-test-junit5/2.3.7/micronaut-test-junit5-2.3.7-sources.jar" ], - "sha256": "3e809f7676a5ea810ead16d587447ec99ed9f581f0c863316436a9f627df57c3", - "url": "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-junit5/2.2.1/micronaut-test-junit5-2.2.1-sources.jar" + "sha256": "71c29e4271d10bdb88ddbdf45c29686a3ce737f0323dd1c65c63fc8b911b9498", + "url": "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-junit5/2.3.7/micronaut-test-junit5-2.3.7-sources.jar" }, { - "coord": "io.micronaut.test:micronaut-test-kotlintest:2.2.1", + "coord": "io.micronaut.test:micronaut-test-kotlintest:2.3.7", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", - "io.micronaut.test:micronaut-test-core:2.2.1", - "io.micronaut.test:micronaut-test-junit5:2.2.1", + "org.junit.platform:junit-platform-suite-api:1.8.0-M1", "commons-io:commons-io:2.6", - "org.yaml:snakeyaml:1.26", - "com.google.code.findbugs:jsr305:3.0.2", - "org.junit.platform:junit-platform-engine:1.6.0", - "io.micronaut:micronaut-inject:2.1.3", - "org.junit.platform:junit-platform-launcher:1.6.0", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", "com.univocity:univocity-parsers:2.8.1", "io.mockk:mockk-agent-jvm:1.9.1", "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10", "io.kindedj:kindedj:1.1.0", "io.kotlintest:kotlintest-core:3.4.2", + "org.junit.platform:junit-platform-launcher:1.8.0-M1", "org.eclipse.jgit:org.eclipse.jgit:4.4.1.201607150455-r", "net.bytebuddy:byte-buddy:1.9.3", "org.junit.jupiter:junit-jupiter-api:5.7.0", "io.github.classgraph:classgraph:4.8.1", - "org.opentest4j:opentest4j:1.2.0", "io.mockk:mockk-agent-common:1.9.1", "io.mockk:mockk-agent-api:1.9.1", - "org.junit.platform:junit-platform-suite-api:1.6.0", "io.arrow-kt:arrow-core-data:0.9.0", "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10", - "io.reactivex.rxjava2:rxjava:2.2.20", "org.objenesis:objenesis:2.6", "io.mockk:mockk-common:1.9.1", "io.kotlintest:kotlintest-assertions:3.4.2", "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.1.1", - "org.apiguardian:apiguardian-api:1.1.0", + "io.micronaut:micronaut-runtime:2.5.12", "net.sourceforge.argparse4j:argparse4j:0.8.1", "io.mockk:mockk-dsl:1.9.1", "com.github.wumpz:diffutils:2.2", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", + "org.junit.platform:junit-platform-engine:1.8.0-M1", "org.jetbrains.kotlin:kotlin-reflect:1.4.10", "com.github.ajalt:mordant:1.2.1", - "javax.annotation:javax.annotation-api:1.3.2", "com.github.ajalt:colormath:1.2.0", - "org.junit.platform:junit-platform-commons:1.7.0", "io.arrow-kt:arrow-core-extensions:0.9.0", - "io.micronaut:micronaut-http:2.1.3", "io.kotlintest:kotlintest-extensions:3.4.2", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10", "io.mockk:mockk-dsl-jvm:1.9.1", "org.jetbrains.kotlin:kotlin-stdlib:1.4.10", "net.bytebuddy:byte-buddy-agent:1.9.3", "io.kotlintest:kotlintest-runner-console:3.4.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", - "javax.inject:javax.inject:1", "io.mockk:mockk:1.9.1", + "io.micronaut.test:micronaut-test-junit5:2.3.7", "io.arrow-kt:arrow-typeclasses:0.9.0", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", "io.arrow-kt:arrow-annotations:0.9.0", - "javax.validation:validation-api:2.0.1.Final", "io.kotlintest:kotlintest-runner-jvm:3.4.2", "io.kotlintest:kotlintest-runner-junit5:3.4.2", "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1", "org.jetbrains:annotations:13.0", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12", + "io.micronaut.test:micronaut-test-core:2.3.7" ], "directDependencies": [ - "io.micronaut.test:micronaut-test-core:2.2.1", - "io.micronaut.test:micronaut-test-junit5:2.2.1", - "io.micronaut:micronaut-inject:2.1.3", - "io.micronaut:micronaut-runtime:2.1.3", "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10", + "io.micronaut:micronaut-runtime:2.5.12", "org.jetbrains.kotlin:kotlin-reflect:1.4.10", - "io.kotlintest:kotlintest-runner-junit5:3.4.2" + "io.micronaut.test:micronaut-test-junit5:2.3.7", + "io.kotlintest:kotlintest-runner-junit5:3.4.2", + "io.micronaut:micronaut-inject:2.5.12", + "io.micronaut.test:micronaut-test-core:2.3.7" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/test/micronaut-test-kotlintest/2.2.1/micronaut-test-kotlintest-2.2.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/test/micronaut-test-kotlintest/2.3.7/micronaut-test-kotlintest-2.3.7.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-kotlintest/2.2.1/micronaut-test-kotlintest-2.2.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/test/micronaut-test-kotlintest/2.2.1/micronaut-test-kotlintest-2.2.1.jar", - "https://jcenter.bintray.com/io/micronaut/test/micronaut-test-kotlintest/2.2.1/micronaut-test-kotlintest-2.2.1.jar", - "https://maven.google.com/io/micronaut/test/micronaut-test-kotlintest/2.2.1/micronaut-test-kotlintest-2.2.1.jar" + "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-kotlintest/2.3.7/micronaut-test-kotlintest-2.3.7.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/test/micronaut-test-kotlintest/2.3.7/micronaut-test-kotlintest-2.3.7.jar", + "https://jcenter.bintray.com/io/micronaut/test/micronaut-test-kotlintest/2.3.7/micronaut-test-kotlintest-2.3.7.jar", + "https://maven.google.com/io/micronaut/test/micronaut-test-kotlintest/2.3.7/micronaut-test-kotlintest-2.3.7.jar" ], - "sha256": "637804ee815a368fd504271a2c12ce47e8e3b7064f2209cb77f330cadbdfb00e", - "url": "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-kotlintest/2.2.1/micronaut-test-kotlintest-2.2.1.jar" + "sha256": "b0c5a36e3a6efb44e79a20f77c7a18a0accf6d60e737fd7d75c417d483b5d91f", + "url": "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-kotlintest/2.3.7/micronaut-test-kotlintest-2.3.7.jar" }, { - "coord": "io.micronaut.test:micronaut-test-kotlintest:jar:sources:2.2.1", + "coord": "io.micronaut.test:micronaut-test-kotlintest:jar:sources:2.3.7", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", + "org.slf4j:slf4j-api:jar:sources:1.7.31", "com.github.wumpz:diffutils:jar:sources:2.2", - "javax.validation:validation-api:jar:sources:2.0.1.Final", - "org.junit.platform:junit-platform-suite-api:jar:sources:1.6.0", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", "net.sourceforge.argparse4j:argparse4j:jar:sources:0.8.1", + "org.junit.platform:junit-platform-suite-api:jar:sources:1.8.0-M1", "org.jetbrains.kotlin:kotlin-reflect:jar:sources:1.4.10", "org.jetbrains:annotations:jar:sources:13.0", "io.mockk:mockk-agent-api:jar:sources:1.9.1", "com.univocity:univocity-parsers:jar:sources:2.8.1", "io.kotlintest:kotlintest-runner-jvm:jar:sources:3.4.2", "io.kotlintest:kotlintest-runner-junit5:jar:sources:3.4.2", - "org.junit.platform:junit-platform-commons:jar:sources:1.7.0", "org.jetbrains.kotlin:kotlin-stdlib-common:jar:sources:1.4.10", "io.kotlintest:kotlintest-core:jar:sources:3.4.2", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", + "io.micronaut.test:micronaut-test-core:jar:sources:2.3.7", "io.mockk:mockk-common:jar:sources:1.9.1", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", "net.bytebuddy:byte-buddy:jar:sources:1.9.3", "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.4.10", "commons-io:commons-io:jar:sources:2.6", - "org.opentest4j:opentest4j:jar:sources:1.2.0", - "org.junit.platform:junit-platform-engine:jar:sources:1.6.0", "org.jetbrains.kotlin:kotlin-stdlib-jdk8:jar:sources:1.4.10", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", "io.arrow-kt:arrow-core-extensions:jar:sources:0.9.0", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:jar:sources:1.4.10", "net.bytebuddy:byte-buddy-agent:jar:sources:1.9.3", "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:jar:sources:1.1.1", - "javax.inject:javax.inject:jar:sources:1", + "org.junit.platform:junit-platform-launcher:jar:sources:1.8.0-M1", "io.mockk:mockk-dsl-jvm:jar:sources:1.9.1", - "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", + "io.micronaut.test:micronaut-test-junit5:jar:sources:2.3.7", "io.github.classgraph:classgraph:jar:sources:4.8.1", - "io.micronaut.test:micronaut-test-junit5:jar:sources:2.2.1", "io.mockk:mockk:jar:sources:1.9.1", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", "io.mockk:mockk-agent-jvm:jar:sources:1.9.1", "io.arrow-kt:arrow-typeclasses:jar:sources:0.9.0", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", "io.kotlintest:kotlintest-extensions:jar:sources:3.4.2", "org.eclipse.jgit:org.eclipse.jgit:jar:sources:4.4.1.201607150455-r", "io.kotlintest:kotlintest-assertions:jar:sources:3.4.2", "io.kotlintest:kotlintest-runner-console:jar:sources:3.4.2", "io.mockk:mockk-dsl:jar:sources:1.9.1", "io.arrow-kt:arrow-core-data:jar:sources:0.9.0", - "io.micronaut:micronaut-core:jar:sources:2.1.3", "io.arrow-kt:arrow-annotations:jar:sources:0.9.0", - "org.junit.platform:junit-platform-launcher:jar:sources:1.6.0", + "org.junit.platform:junit-platform-engine:jar:sources:1.8.0-M1", "com.github.ajalt:mordant:jar:sources:1.2.1", "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0", "org.jetbrains.kotlinx:kotlinx-coroutines-core:jar:sources:1.1.1", "com.github.ajalt:colormath:jar:sources:1.2.0", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", "io.mockk:mockk-agent-common:jar:sources:1.9.1", - "io.micronaut:micronaut-http:jar:sources:2.1.3", - "io.micronaut.test:micronaut-test-core:jar:sources:2.2.1", "org.objenesis:objenesis:jar:sources:2.6", "io.kindedj:kindedj:jar:sources:1.1.0" ], "directDependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", "org.jetbrains.kotlin:kotlin-reflect:jar:sources:1.4.10", "io.kotlintest:kotlintest-runner-junit5:jar:sources:3.4.2", + "io.micronaut.test:micronaut-test-core:jar:sources:2.3.7", "org.jetbrains.kotlin:kotlin-stdlib-jdk8:jar:sources:1.4.10", - "io.micronaut.test:micronaut-test-junit5:jar:sources:2.2.1", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut.test:micronaut-test-core:jar:sources:2.2.1" + "io.micronaut.test:micronaut-test-junit5:jar:sources:2.3.7", + "io.micronaut:micronaut-inject:jar:sources:2.5.12" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/test/micronaut-test-kotlintest/2.2.1/micronaut-test-kotlintest-2.2.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/test/micronaut-test-kotlintest/2.3.7/micronaut-test-kotlintest-2.3.7-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-kotlintest/2.2.1/micronaut-test-kotlintest-2.2.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/test/micronaut-test-kotlintest/2.2.1/micronaut-test-kotlintest-2.2.1-sources.jar", - "https://jcenter.bintray.com/io/micronaut/test/micronaut-test-kotlintest/2.2.1/micronaut-test-kotlintest-2.2.1-sources.jar", - "https://maven.google.com/io/micronaut/test/micronaut-test-kotlintest/2.2.1/micronaut-test-kotlintest-2.2.1-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-kotlintest/2.3.7/micronaut-test-kotlintest-2.3.7-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/test/micronaut-test-kotlintest/2.3.7/micronaut-test-kotlintest-2.3.7-sources.jar", + "https://jcenter.bintray.com/io/micronaut/test/micronaut-test-kotlintest/2.3.7/micronaut-test-kotlintest-2.3.7-sources.jar", + "https://maven.google.com/io/micronaut/test/micronaut-test-kotlintest/2.3.7/micronaut-test-kotlintest-2.3.7-sources.jar" ], - "sha256": "0b8e31245e45566d30ecadd989effb9ddb18672a190be9e3f2ed8ef9b323dba9", - "url": "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-kotlintest/2.2.1/micronaut-test-kotlintest-2.2.1-sources.jar" + "sha256": "e9d017c4e537547cf41b263e3303f7fa5193b8a513eb7529cff0c0ff12e6c5ae", + "url": "https://repo1.maven.org/maven2/io/micronaut/test/micronaut-test-kotlintest/2.3.7/micronaut-test-kotlintest-2.3.7-sources.jar" }, { - "coord": "io.micronaut:micronaut-aop:2.1.3", + "coord": "io.micronaut:micronaut-aop:2.5.12", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", "org.yaml:snakeyaml:1.26", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", + "com.github.spotbugs:spotbugs-annotations:4.0.3", "javax.annotation:javax.annotation-api:1.3.2", + "io.micronaut:micronaut-core:2.5.12", "javax.inject:javax.inject:1", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6" + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-core:2.1.3", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30" + "io.micronaut:micronaut-core:2.5.12", + "io.micronaut:micronaut-inject:2.5.12", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-aop/2.1.3/micronaut-aop-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-aop/2.5.12/micronaut-aop-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-aop/2.1.3/micronaut-aop-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-aop/2.1.3/micronaut-aop-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-aop/2.1.3/micronaut-aop-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-aop/2.1.3/micronaut-aop-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-aop/2.5.12/micronaut-aop-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-aop/2.5.12/micronaut-aop-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-aop/2.5.12/micronaut-aop-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-aop/2.5.12/micronaut-aop-2.5.12.jar" ], - "sha256": "dee1ab302c5c68e7a59ae542bea1c78909067b73ca39121da4ce02f009fefdf2", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-aop/2.1.3/micronaut-aop-2.1.3.jar" + "sha256": "f4d281efb5be471b28d805be47f56743552e9a5d294fb8854c81441975ae7b35", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-aop/2.5.12/micronaut-aop-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-aop:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-aop:jar:sources:2.5.12", "dependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", "javax.inject:javax.inject:jar:sources:1", "org.yaml:snakeyaml:jar:sources:1.26", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3" + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30" + "io.micronaut:micronaut-core:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "org.slf4j:slf4j-api:jar:sources:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-aop/2.1.3/micronaut-aop-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-aop/2.5.12/micronaut-aop-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-aop/2.1.3/micronaut-aop-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-aop/2.1.3/micronaut-aop-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-aop/2.1.3/micronaut-aop-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-aop/2.1.3/micronaut-aop-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-aop/2.5.12/micronaut-aop-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-aop/2.5.12/micronaut-aop-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-aop/2.5.12/micronaut-aop-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-aop/2.5.12/micronaut-aop-2.5.12-sources.jar" ], - "sha256": "cf1da10ed273a69ae609d66282c2c7d4f786fafb262bb63e0d0ae57f664dcac4", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-aop/2.1.3/micronaut-aop-2.1.3-sources.jar" + "sha256": "b7378856c20f16a86874a5ab7566488bf059e6e21fbaf2c3c1de9facd42e7cfe", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-aop/2.5.12/micronaut-aop-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-buffer-netty:2.1.3", + "coord": "io.micronaut:micronaut-buffer-netty:2.5.12", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", "org.yaml:snakeyaml:1.26", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-buffer:4.1.53.Final", + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final", "javax.annotation:javax.annotation-api:1.3.2", + "io.micronaut:micronaut-core:2.5.12", "javax.inject:javax.inject:1", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6" + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-core:2.1.3", - "io.micronaut:micronaut-inject:2.1.3", - "io.netty:netty-buffer:4.1.53.Final", - "org.slf4j:slf4j-api:1.7.30" + "io.micronaut:micronaut-core:2.5.12", + "io.micronaut:micronaut-inject:2.5.12", + "io.netty:netty-buffer:4.1.66.Final", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-buffer-netty/2.1.3/micronaut-buffer-netty-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-buffer-netty/2.5.12/micronaut-buffer-netty-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-buffer-netty/2.1.3/micronaut-buffer-netty-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-buffer-netty/2.1.3/micronaut-buffer-netty-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-buffer-netty/2.1.3/micronaut-buffer-netty-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-buffer-netty/2.1.3/micronaut-buffer-netty-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-buffer-netty/2.5.12/micronaut-buffer-netty-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-buffer-netty/2.5.12/micronaut-buffer-netty-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-buffer-netty/2.5.12/micronaut-buffer-netty-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-buffer-netty/2.5.12/micronaut-buffer-netty-2.5.12.jar" ], - "sha256": "6eb42904a83c2f1363ef5abc2b39b367abbef0cbbdb157e39533a190e64c1b99", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-buffer-netty/2.1.3/micronaut-buffer-netty-2.1.3.jar" + "sha256": "d53375385e43edd1f3c2b3d29dbc0fb6edd2eeef4ef3a5e3fc3b40e1696b3e95", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-buffer-netty/2.5.12/micronaut-buffer-netty-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-buffer-netty:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-buffer-netty:jar:sources:2.5.12", "dependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", + "io.netty:netty-buffer:jar:sources:4.1.66.Final", "javax.inject:javax.inject:jar:sources:1", "org.yaml:snakeyaml:jar:sources:1.26", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3" + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.netty:netty-common:jar:sources:4.1.66.Final", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" + ], + "directDependencies": [ + "io.micronaut:micronaut-core:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "org.slf4j:slf4j-api:jar:sources:1.7.31" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-buffer-netty/2.5.12/micronaut-buffer-netty-2.5.12-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/io/micronaut/micronaut-buffer-netty/2.5.12/micronaut-buffer-netty-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-buffer-netty/2.5.12/micronaut-buffer-netty-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-buffer-netty/2.5.12/micronaut-buffer-netty-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-buffer-netty/2.5.12/micronaut-buffer-netty-2.5.12-sources.jar" + ], + "sha256": "7e30b1edaafb48f0027cf8f6e65eef9128f6c86bcf8e6fa0140e7fd4f33f19c0", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-buffer-netty/2.5.12/micronaut-buffer-netty-2.5.12-sources.jar" + }, + { + "coord": "io.micronaut:micronaut-context:2.5.12", + "dependencies": [ + "org.yaml:snakeyaml:1.26", + "com.google.code.findbugs:jsr305:3.0.2", + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "javax.annotation:javax.annotation-api:1.3.2", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-core:2.5.12", + "javax.inject:javax.inject:1", + "javax.validation:validation-api:2.0.1.Final", + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "org.slf4j:slf4j-api:jar:sources:1.7.30" + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-inject:2.5.12", + "javax.validation:validation-api:2.0.1.Final", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-buffer-netty/2.1.3/micronaut-buffer-netty-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-context/2.5.12/micronaut-context-2.5.12.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/io/micronaut/micronaut-context/2.5.12/micronaut-context-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-context/2.5.12/micronaut-context-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-context/2.5.12/micronaut-context-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-context/2.5.12/micronaut-context-2.5.12.jar" + ], + "sha256": "a722d8b82d4f7e1d31cad27419d9ea46e118f8ace8a477b92faf44923cf670a9", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-context/2.5.12/micronaut-context-2.5.12.jar" + }, + { + "coord": "io.micronaut:micronaut-context:jar:sources:2.5.12", + "dependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", + "javax.validation:validation-api:jar:sources:2.0.1.Final", + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "javax.inject:javax.inject:jar:sources:1", + "org.yaml:snakeyaml:jar:sources:1.26", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" + ], + "directDependencies": [ + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "javax.validation:validation-api:jar:sources:2.0.1.Final", + "org.slf4j:slf4j-api:jar:sources:1.7.31" + ], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.template:soy", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" + ], + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-context/2.5.12/micronaut-context-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-buffer-netty/2.1.3/micronaut-buffer-netty-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-buffer-netty/2.1.3/micronaut-buffer-netty-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-buffer-netty/2.1.3/micronaut-buffer-netty-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-buffer-netty/2.1.3/micronaut-buffer-netty-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-context/2.5.12/micronaut-context-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-context/2.5.12/micronaut-context-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-context/2.5.12/micronaut-context-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-context/2.5.12/micronaut-context-2.5.12-sources.jar" ], - "sha256": "19c78d9881179545fd7d085f3f8fffc25ef998bc7eb002684a1f08dbefa8c08f", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-buffer-netty/2.1.3/micronaut-buffer-netty-2.1.3-sources.jar" + "sha256": "6e4b593a57170d4336d464b1a7b9c9ad70a7bd6083a99a26e25f115f59097561", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-context/2.5.12/micronaut-context-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-core:2.1.3", + "coord": "io.micronaut:micronaut-core-reactive:2.5.12", "dependencies": [ - "org.slf4j:slf4j-api:1.7.30", - "com.github.spotbugs:spotbugs-annotations:4.0.6", "org.reactivestreams:reactive-streams:1.0.3", - "com.google.code.findbugs:jsr305:3.0.2" + "com.google.code.findbugs:jsr305:3.0.2", + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "io.micronaut:micronaut-core:2.5.12", + "org.slf4j:slf4j-api:1.7.31" ], "directDependencies": [ - "com.github.spotbugs:spotbugs-annotations:4.0.6", + "io.micronaut:micronaut-core:2.5.12", "org.reactivestreams:reactive-streams:1.0.3", - "org.slf4j:slf4j-api:1.7.30" + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-core/2.1.3/micronaut-core-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-core-reactive/2.5.12/micronaut-core-reactive-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-core/2.1.3/micronaut-core-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-core/2.1.3/micronaut-core-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-core/2.1.3/micronaut-core-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-core/2.1.3/micronaut-core-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-core-reactive/2.5.12/micronaut-core-reactive-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-core-reactive/2.5.12/micronaut-core-reactive-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-core-reactive/2.5.12/micronaut-core-reactive-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-core-reactive/2.5.12/micronaut-core-reactive-2.5.12.jar" ], - "sha256": "6c5a3ea9409cd863441df7775fff0e16746399846fb503432793b0557119e3ac", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-core/2.1.3/micronaut-core-2.1.3.jar" + "sha256": "c6c7983007400894dd178fc75f98677971190c31e99c91917f0b1d9e50d0474f", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-core-reactive/2.5.12/micronaut-core-reactive-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-core:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", "dependencies": [ - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30" + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3" ], "directDependencies": [ - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30" + "org.slf4j:slf4j-api:jar:sources:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-core/2.1.3/micronaut-core-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-core-reactive/2.5.12/micronaut-core-reactive-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-core/2.1.3/micronaut-core-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-core/2.1.3/micronaut-core-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-core/2.1.3/micronaut-core-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-core/2.1.3/micronaut-core-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-core-reactive/2.5.12/micronaut-core-reactive-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-core-reactive/2.5.12/micronaut-core-reactive-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-core-reactive/2.5.12/micronaut-core-reactive-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-core-reactive/2.5.12/micronaut-core-reactive-2.5.12-sources.jar" ], - "sha256": "92a53b6af0d76500bae403637454d2a6e4be56f3a100112d599ff396f3297501", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-core/2.1.3/micronaut-core-2.1.3-sources.jar" + "sha256": "baccf4211fb1ba9da680c5a2126871d707859d5138c0de782cac87c831564f3d", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-core-reactive/2.5.12/micronaut-core-reactive-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-graal:2.1.3", + "coord": "io.micronaut:micronaut-core:2.5.12", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "org.slf4j:slf4j-api:1.7.31", + "com.google.code.findbugs:jsr305:3.0.2" + ], + "directDependencies": [ + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "org.slf4j:slf4j-api:1.7.31" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-core/2.5.12/micronaut-core-2.5.12.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/io/micronaut/micronaut-core/2.5.12/micronaut-core-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-core/2.5.12/micronaut-core-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-core/2.5.12/micronaut-core-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-core/2.5.12/micronaut-core-2.5.12.jar" + ], + "sha256": "f3a864da112a88e3c123342bc6af6d2e7eae920c79a3676300d7cce826d41786", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-core/2.5.12/micronaut-core-2.5.12.jar" + }, + { + "coord": "io.micronaut:micronaut-core:jar:sources:2.5.12", + "dependencies": [ + "com.google.code.findbugs:jsr305:jar:sources:3.0.2", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "org.slf4j:slf4j-api:jar:sources:1.7.31" + ], + "directDependencies": [ + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "org.slf4j:slf4j-api:jar:sources:1.7.31" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-core/2.5.12/micronaut-core-2.5.12-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/io/micronaut/micronaut-core/2.5.12/micronaut-core-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-core/2.5.12/micronaut-core-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-core/2.5.12/micronaut-core-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-core/2.5.12/micronaut-core-2.5.12-sources.jar" + ], + "sha256": "24b35474c1076cc9c757fd6b20ebbb0a18ffee06b79a6cbf77e88d9d90f29213", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-core/2.5.12/micronaut-core-2.5.12-sources.jar" + }, + { + "coord": "io.micronaut:micronaut-graal:2.5.12", + "dependencies": [ + "com.fasterxml.jackson.core:jackson-databind:2.12.2", + "com.fasterxml.jackson.core:jackson-core:2.12.3", "org.yaml:snakeyaml:1.26", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", + "com.github.spotbugs:spotbugs-annotations:4.0.3", "javax.annotation:javax.annotation-api:1.3.2", + "io.micronaut:micronaut-core:2.5.12", "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30" + "com.fasterxml.jackson.core:jackson-databind:2.12.2", + "io.micronaut:micronaut-inject:2.5.12", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-graal/2.1.3/micronaut-graal-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-graal/2.5.12/micronaut-graal-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-graal/2.1.3/micronaut-graal-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-graal/2.1.3/micronaut-graal-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-graal/2.1.3/micronaut-graal-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-graal/2.1.3/micronaut-graal-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-graal/2.5.12/micronaut-graal-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-graal/2.5.12/micronaut-graal-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-graal/2.5.12/micronaut-graal-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-graal/2.5.12/micronaut-graal-2.5.12.jar" ], - "sha256": "8958565e0cdc8499fe004312758b8a17199b116443478dcfcf660e82ec5bf16e", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-graal/2.1.3/micronaut-graal-2.1.3.jar" + "sha256": "ae90ce6e05f6126e106bce56ea881aacdc8766cc2a9f401c43b2b1242bf7143c", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-graal/2.5.12/micronaut-graal-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-graal:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-graal:jar:sources:2.5.12", "dependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3", "javax.inject:javax.inject:jar:sources:1", "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3" + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.2", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30" + "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.2", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "org.slf4j:slf4j-api:jar:sources:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-graal/2.1.3/micronaut-graal-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-graal/2.5.12/micronaut-graal-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-graal/2.1.3/micronaut-graal-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-graal/2.1.3/micronaut-graal-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-graal/2.1.3/micronaut-graal-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-graal/2.1.3/micronaut-graal-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-graal/2.5.12/micronaut-graal-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-graal/2.5.12/micronaut-graal-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-graal/2.5.12/micronaut-graal-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-graal/2.5.12/micronaut-graal-2.5.12-sources.jar" ], - "sha256": "dfcfadb843fe566d68c811d8e9cd47d195f9b58dd1a273d8dd5b000b33790bad", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-graal/2.1.3/micronaut-graal-2.1.3-sources.jar" + "sha256": "9d7e4edc491eb2b16050383cf1b7a3bdb036329ca7418fe22d8d45ebe2cf0434", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-graal/2.5.12/micronaut-graal-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-http-client-core:2.1.3", + "coord": "io.micronaut:micronaut-http-client-core:2.5.12", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3", + "com.fasterxml.jackson.core:jackson-databind:2.12.2", + "com.fasterxml.jackson.core:jackson-core:2.12.3", + "io.micronaut:micronaut-context:2.5.12", + "io.micronaut:micronaut-http:2.5.12", "org.yaml:snakeyaml:1.26", + "io.micronaut:micronaut-websocket:2.5.12", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "io.micronaut:micronaut-websocket:2.1.3", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", + "io.reactivex.rxjava2:rxjava:2.2.21", + "io.micronaut:micronaut-core-reactive:2.5.12", + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "io.micronaut:micronaut-runtime:2.5.12", "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-core:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.2", "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.2" ], "directDependencies": [ - "io.micronaut:micronaut-runtime:2.1.3", - "io.micronaut:micronaut-websocket:2.1.3", - "io.reactivex.rxjava2:rxjava:2.2.20", - "org.slf4j:slf4j-api:1.7.30" + "io.micronaut:micronaut-runtime:2.5.12", + "io.micronaut:micronaut-websocket:2.5.12", + "io.reactivex.rxjava2:rxjava:2.2.21", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-client-core/2.1.3/micronaut-http-client-core-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-client-core/2.5.12/micronaut-http-client-core-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-client-core/2.1.3/micronaut-http-client-core-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-client-core/2.1.3/micronaut-http-client-core-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-http-client-core/2.1.3/micronaut-http-client-core-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-http-client-core/2.1.3/micronaut-http-client-core-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-client-core/2.5.12/micronaut-http-client-core-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-client-core/2.5.12/micronaut-http-client-core-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-http-client-core/2.5.12/micronaut-http-client-core-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-http-client-core/2.5.12/micronaut-http-client-core-2.5.12.jar" ], - "sha256": "2146458add6bc8a4f0c9926cbf0f42439a80afe2d303e823ec07564269cc3e67", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-client-core/2.1.3/micronaut-http-client-core-2.1.3.jar" + "sha256": "920022412db4166e52a379ac205f7b89aed86edb48320a3d96ebd7cad1ec9e46", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-client-core/2.5.12/micronaut-http-client-core-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-http-client-core:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-http-client-core:jar:sources:2.5.12", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.micronaut:micronaut-websocket:jar:sources:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.12.2", + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.12.2", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", "javax.inject:javax.inject:jar:sources:1", + "io.micronaut:micronaut-context:jar:sources:2.5.12", "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "io.micronaut:micronaut-websocket:jar:sources:2.5.12", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", + "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.2", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "io.micronaut:micronaut-websocket:jar:sources:2.1.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "org.slf4j:slf4j-api:jar:sources:1.7.30" + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "io.micronaut:micronaut-websocket:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", + "org.slf4j:slf4j-api:jar:sources:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-client-core/2.1.3/micronaut-http-client-core-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-client-core/2.5.12/micronaut-http-client-core-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-client-core/2.1.3/micronaut-http-client-core-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-client-core/2.1.3/micronaut-http-client-core-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-http-client-core/2.1.3/micronaut-http-client-core-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-http-client-core/2.1.3/micronaut-http-client-core-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-client-core/2.5.12/micronaut-http-client-core-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-client-core/2.5.12/micronaut-http-client-core-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-http-client-core/2.5.12/micronaut-http-client-core-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-http-client-core/2.5.12/micronaut-http-client-core-2.5.12-sources.jar" ], - "sha256": "d2032ec03c795dbd3fc5513a5e3308fe841a60c3bb2cff9c21502637b7a5ad9e", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-client-core/2.1.3/micronaut-http-client-core-2.1.3-sources.jar" + "sha256": "5d4f252d84c1c143808c6a7b516fa24aefa8047aabea99da483924d9d610f97f", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-client-core/2.5.12/micronaut-http-client-core-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-http-client:2.1.3", + "coord": "io.micronaut:micronaut-http-client:2.5.12", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3", - "io.netty:netty-codec-http2:4.1.53.Final", - "io.netty:netty-codec:4.1.53.Final", - "io.netty:netty-handler:4.1.53.Final", + "com.fasterxml.jackson.core:jackson-databind:2.12.2", + "io.micronaut:micronaut-http-client-core:2.5.12", + "com.fasterxml.jackson.core:jackson-core:2.12.3", + "io.micronaut:micronaut-buffer-netty:2.5.12", + "io.micronaut:micronaut-context:2.5.12", + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut:micronaut-http-netty:2.5.12", "org.yaml:snakeyaml:1.26", - "io.netty:netty-codec-socks:4.1.53.Final", + "io.micronaut:micronaut-websocket:2.5.12", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-buffer:4.1.53.Final", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "io.netty:netty-resolver:4.1.53.Final", - "io.netty:netty-transport:4.1.53.Final", - "io.micronaut:micronaut-http-netty:2.1.3", - "io.micronaut:micronaut-websocket:2.1.3", - "io.micronaut:micronaut-buffer-netty:2.1.3", - "io.netty:netty-codec-http:4.1.53.Final", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-http-client-core:2.1.3", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", + "io.netty:netty-resolver:4.1.66.Final", + "io.netty:netty-handler-proxy:4.1.66.Final", + "io.reactivex.rxjava2:rxjava:2.2.21", + "io.netty:netty-codec-http:4.1.66.Final", + "io.netty:netty-handler:4.1.66.Final", + "io.micronaut:micronaut-core-reactive:2.5.12", + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "io.netty:netty-codec-socks:4.1.66.Final", + "io.netty:netty-codec:4.1.66.Final", + "io.netty:netty-transport:4.1.66.Final", + "io.micronaut:micronaut-runtime:2.5.12", + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-codec-http2:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final", "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "io.netty:netty-handler-proxy:4.1.53.Final", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-core:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.2", "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.2" ], "directDependencies": [ - "org.slf4j:slf4j-api:1.7.30", - "io.micronaut:micronaut-runtime:2.1.3", - "io.micronaut:micronaut-http-netty:2.1.3", - "io.micronaut:micronaut-websocket:2.1.3", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-http-client-core:2.1.3", - "io.netty:netty-handler-proxy:4.1.53.Final" + "io.micronaut:micronaut-http-client-core:2.5.12", + "io.micronaut:micronaut-http-netty:2.5.12", + "io.micronaut:micronaut-websocket:2.5.12", + "io.netty:netty-handler-proxy:4.1.66.Final", + "io.reactivex.rxjava2:rxjava:2.2.21", + "io.micronaut:micronaut-runtime:2.5.12", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-client/2.1.3/micronaut-http-client-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-client/2.5.12/micronaut-http-client-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-client/2.1.3/micronaut-http-client-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-client/2.1.3/micronaut-http-client-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-http-client/2.1.3/micronaut-http-client-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-http-client/2.1.3/micronaut-http-client-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-client/2.5.12/micronaut-http-client-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-client/2.5.12/micronaut-http-client-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-http-client/2.5.12/micronaut-http-client-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-http-client/2.5.12/micronaut-http-client-2.5.12.jar" ], - "sha256": "acaed5e55a8aae87158434c1d2ed58fdb95b74f3dea6b0a36e2fa1f88399ed46", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-client/2.1.3/micronaut-http-client-2.1.3.jar" + "sha256": "a6ab80500dfe3ae9e3a5cd9f970bbadbe85da1bae56f224c28b32c82f6639bad", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-client/2.5.12/micronaut-http-client-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-http-client:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-http-client:jar:sources:2.5.12", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "io.netty:netty-handler:jar:sources:4.1.53.Final", + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "io.netty:netty-handler-proxy:jar:sources:4.1.53.Final", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.micronaut:micronaut-websocket:jar:sources:2.1.3", - "io.micronaut:micronaut-http-netty:jar:sources:2.1.3", - "io.micronaut:micronaut-buffer-netty:jar:sources:2.1.3", - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "io.netty:netty-codec-http2:jar:sources:4.1.53.Final", - "io.netty:netty-codec-socks:jar:sources:4.1.53.Final", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "io.netty:netty-resolver:jar:sources:4.1.53.Final", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "io.netty:netty-codec-http:jar:sources:4.1.66.Final", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.12.2", + "io.netty:netty-codec:jar:sources:4.1.66.Final", + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3", + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.12.2", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-http-client-core:jar:sources:2.5.12", + "io.micronaut:micronaut-buffer-netty:jar:sources:2.5.12", + "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", "javax.inject:javax.inject:jar:sources:1", + "io.micronaut:micronaut-context:jar:sources:2.5.12", "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "io.netty:netty-codec-http:jar:sources:4.1.53.Final", - "io.netty:netty-codec:jar:sources:4.1.53.Final", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "io.micronaut:micronaut-http-client-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.netty:netty-codec-socks:jar:sources:4.1.66.Final", + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "io.netty:netty-codec-http2:jar:sources:4.1.66.Final", + "io.micronaut:micronaut-http-netty:jar:sources:2.5.12", + "io.micronaut:micronaut-websocket:jar:sources:2.5.12", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.netty:netty-handler-proxy:jar:sources:4.1.66.Final", + "io.netty:netty-transport:jar:sources:4.1.66.Final", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", + "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.2", + "io.netty:netty-resolver:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final", + "io.netty:netty-handler:jar:sources:4.1.66.Final", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "io.netty:netty-handler-proxy:jar:sources:4.1.53.Final", - "io.micronaut:micronaut-websocket:jar:sources:2.1.3", - "io.micronaut:micronaut-http-netty:jar:sources:2.1.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "io.micronaut:micronaut-http-client-core:jar:sources:2.1.3" + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "io.micronaut:micronaut-http-client-core:jar:sources:2.5.12", + "io.micronaut:micronaut-http-netty:jar:sources:2.5.12", + "io.micronaut:micronaut-websocket:jar:sources:2.5.12", + "io.netty:netty-handler-proxy:jar:sources:4.1.66.Final", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-client/2.1.3/micronaut-http-client-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-client/2.5.12/micronaut-http-client-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-client/2.1.3/micronaut-http-client-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-client/2.1.3/micronaut-http-client-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-http-client/2.1.3/micronaut-http-client-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-http-client/2.1.3/micronaut-http-client-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-client/2.5.12/micronaut-http-client-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-client/2.5.12/micronaut-http-client-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-http-client/2.5.12/micronaut-http-client-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-http-client/2.5.12/micronaut-http-client-2.5.12-sources.jar" ], - "sha256": "5f5c59460c5f2b6423806502f8e6ad6837274fffb32f59302a6e44167439d956", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-client/2.1.3/micronaut-http-client-2.1.3-sources.jar" + "sha256": "2b88375b491fcc919309285ad0cfb565b1f82c3a8aec78c8563cb542bb75906f", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-client/2.5.12/micronaut-http-client-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-http-netty:2.1.3", + "coord": "io.micronaut:micronaut-http-netty:2.5.12", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3", - "io.netty:netty-codec-http2:4.1.53.Final", - "io.netty:netty-codec:4.1.53.Final", - "io.netty:netty-handler:4.1.53.Final", + "io.micronaut:micronaut-buffer-netty:2.5.12", + "io.micronaut:micronaut-http:2.5.12", "org.yaml:snakeyaml:1.26", + "io.micronaut:micronaut-websocket:2.5.12", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-resolver:4.1.53.Final", - "io.netty:netty-transport:4.1.53.Final", - "io.micronaut:micronaut-websocket:2.1.3", - "io.micronaut:micronaut-buffer-netty:2.1.3", - "io.netty:netty-codec-http:4.1.53.Final", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", + "io.netty:netty-resolver:4.1.66.Final", + "io.reactivex.rxjava2:rxjava:2.2.21", + "io.netty:netty-codec-http:4.1.66.Final", + "io.netty:netty-handler:4.1.66.Final", + "io.micronaut:micronaut-core-reactive:2.5.12", + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "io.netty:netty-codec:4.1.66.Final", + "io.netty:netty-transport:4.1.66.Final", + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-codec-http2:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final", "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-core:2.5.12", "javax.inject:javax.inject:1", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6" + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.netty:netty-codec-http2:4.1.53.Final", - "io.netty:netty-handler:4.1.53.Final", - "org.slf4j:slf4j-api:1.7.30", - "io.micronaut:micronaut-websocket:2.1.3", - "io.micronaut:micronaut-buffer-netty:2.1.3", - "io.netty:netty-codec-http:4.1.53.Final", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-http:2.1.3" + "io.micronaut:micronaut-buffer-netty:2.5.12", + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut:micronaut-websocket:2.5.12", + "io.reactivex.rxjava2:rxjava:2.2.21", + "io.netty:netty-codec-http:4.1.66.Final", + "io.netty:netty-handler:4.1.66.Final", + "io.netty:netty-codec-http2:4.1.66.Final", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-netty/2.1.3/micronaut-http-netty-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-netty/2.5.12/micronaut-http-netty-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-netty/2.1.3/micronaut-http-netty-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-netty/2.1.3/micronaut-http-netty-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-http-netty/2.1.3/micronaut-http-netty-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-http-netty/2.1.3/micronaut-http-netty-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-netty/2.5.12/micronaut-http-netty-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-netty/2.5.12/micronaut-http-netty-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-http-netty/2.5.12/micronaut-http-netty-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-http-netty/2.5.12/micronaut-http-netty-2.5.12.jar" ], - "sha256": "961a417d25bd72d64bdf3d5879232a74dbc056a854d50e5fa0668d2b5dd64023", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-netty/2.1.3/micronaut-http-netty-2.1.3.jar" + "sha256": "94db03ecd885644d02db65c0f7669a23955ddc9497b599cfd72e4d20ec2ccbc4", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-netty/2.5.12/micronaut-http-netty-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-http-netty:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-http-netty:jar:sources:2.5.12", "dependencies": [ - "io.netty:netty-handler:jar:sources:4.1.53.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.micronaut:micronaut-websocket:jar:sources:2.1.3", - "io.micronaut:micronaut-buffer-netty:jar:sources:2.1.3", - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "io.netty:netty-codec-http2:jar:sources:4.1.53.Final", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "io.netty:netty-resolver:jar:sources:4.1.53.Final", + "io.netty:netty-codec-http:jar:sources:4.1.66.Final", + "io.netty:netty-codec:jar:sources:4.1.66.Final", + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-buffer-netty:jar:sources:2.5.12", + "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", "javax.inject:javax.inject:jar:sources:1", "org.yaml:snakeyaml:jar:sources:1.26", - "io.netty:netty-codec-http:jar:sources:4.1.53.Final", - "io.netty:netty-codec:jar:sources:4.1.53.Final", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.netty:netty-codec-http2:jar:sources:4.1.66.Final", + "io.micronaut:micronaut-websocket:jar:sources:2.5.12", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.netty:netty-transport:jar:sources:4.1.66.Final", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", + "io.netty:netty-resolver:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final", + "io.netty:netty-handler:jar:sources:4.1.66.Final", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "io.netty:netty-handler:jar:sources:4.1.53.Final", - "io.micronaut:micronaut-websocket:jar:sources:2.1.3", - "io.micronaut:micronaut-buffer-netty:jar:sources:2.1.3", - "io.netty:netty-codec-http2:jar:sources:4.1.53.Final", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "io.netty:netty-codec-http:jar:sources:4.1.53.Final", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.netty:netty-codec-http:jar:sources:4.1.66.Final", + "io.micronaut:micronaut-buffer-netty:jar:sources:2.5.12", + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.netty:netty-codec-http2:jar:sources:4.1.66.Final", + "io.micronaut:micronaut-websocket:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", + "io.netty:netty-handler:jar:sources:4.1.66.Final" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-netty/2.1.3/micronaut-http-netty-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-netty/2.5.12/micronaut-http-netty-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-netty/2.1.3/micronaut-http-netty-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-netty/2.1.3/micronaut-http-netty-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-http-netty/2.1.3/micronaut-http-netty-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-http-netty/2.1.3/micronaut-http-netty-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-netty/2.5.12/micronaut-http-netty-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-netty/2.5.12/micronaut-http-netty-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-http-netty/2.5.12/micronaut-http-netty-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-http-netty/2.5.12/micronaut-http-netty-2.5.12-sources.jar" ], - "sha256": "7d83956072de0b19cca1802319ad874ae69ca9c7726aabbdc12d64534e229050", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-netty/2.1.3/micronaut-http-netty-2.1.3-sources.jar" + "sha256": "3d64aaed16c2378ecd1fff0b79e3297bd5788256767ee448b0ea86e4fa60d79a", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-netty/2.5.12/micronaut-http-netty-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-http-server-netty:2.1.3", + "coord": "io.micronaut:micronaut-http-server-netty:2.5.12", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3", - "io.netty:netty-codec-http2:4.1.53.Final", - "io.netty:netty-codec:4.1.53.Final", - "io.netty:netty-handler:4.1.53.Final", + "com.fasterxml.jackson.core:jackson-databind:2.12.2", + "com.fasterxml.jackson.core:jackson-core:2.12.3", + "io.micronaut:micronaut-buffer-netty:2.5.12", + "io.micronaut:micronaut-context:2.5.12", + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut:micronaut-http-netty:2.5.12", "org.yaml:snakeyaml:1.26", + "io.micronaut:micronaut-websocket:2.5.12", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-buffer:4.1.53.Final", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "io.micronaut:micronaut-router:2.1.3", - "io.netty:netty-resolver:4.1.53.Final", - "io.netty:netty-transport:4.1.53.Final", - "io.micronaut:micronaut-http-netty:2.1.3", - "io.micronaut:micronaut-websocket:2.1.3", - "io.micronaut:micronaut-buffer-netty:2.1.3", - "io.netty:netty-codec-http:4.1.53.Final", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", + "io.netty:netty-resolver:4.1.66.Final", + "io.reactivex.rxjava2:rxjava:2.2.21", + "io.netty:netty-codec-http:4.1.66.Final", + "io.netty:netty-handler:4.1.66.Final", + "io.micronaut:micronaut-core-reactive:2.5.12", + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "io.micronaut:micronaut-router:2.5.12", + "io.netty:netty-codec:4.1.66.Final", + "io.netty:netty-transport:4.1.66.Final", + "io.micronaut:micronaut-runtime:2.5.12", + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-codec-http2:4.1.66.Final", + "io.micronaut:micronaut-http-server:2.5.12", + "io.netty:netty-common:4.1.66.Final", "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "io.micronaut:micronaut-http-server:2.1.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-core:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.2", "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.2" ], "directDependencies": [ - "org.slf4j:slf4j-api:1.7.30", - "io.micronaut:micronaut-http-netty:2.1.3", - "io.netty:netty-codec-http:4.1.53.Final", - "io.micronaut:micronaut-http-server:2.1.3", - "io.micronaut:micronaut-core:2.1.3" + "io.micronaut:micronaut-http-netty:2.5.12", + "io.netty:netty-codec-http:4.1.66.Final", + "io.micronaut:micronaut-http-server:2.5.12", + "io.micronaut:micronaut-core:2.5.12", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-server-netty/2.1.3/micronaut-http-server-netty-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-server-netty/2.5.12/micronaut-http-server-netty-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-server-netty/2.1.3/micronaut-http-server-netty-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-server-netty/2.1.3/micronaut-http-server-netty-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-http-server-netty/2.1.3/micronaut-http-server-netty-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-http-server-netty/2.1.3/micronaut-http-server-netty-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-server-netty/2.5.12/micronaut-http-server-netty-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-server-netty/2.5.12/micronaut-http-server-netty-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-http-server-netty/2.5.12/micronaut-http-server-netty-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-http-server-netty/2.5.12/micronaut-http-server-netty-2.5.12.jar" ], - "sha256": "7af9121f0ea8f4c7f40c239d823161e44b8d06acf4682696a8d63d0045cca912", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-server-netty/2.1.3/micronaut-http-server-netty-2.1.3.jar" + "sha256": "264a9b8e7e4729edaa3f19a2c882863cb48d8ecb8f9052b4df7658e8b369a178", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-server-netty/2.5.12/micronaut-http-server-netty-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-http-server-netty:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-http-server-netty:jar:sources:2.5.12", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "io.netty:netty-handler:jar:sources:4.1.53.Final", + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.micronaut:micronaut-websocket:jar:sources:2.1.3", - "io.micronaut:micronaut-http-netty:jar:sources:2.1.3", - "io.micronaut:micronaut-buffer-netty:jar:sources:2.1.3", - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "io.netty:netty-codec-http2:jar:sources:4.1.53.Final", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "io.micronaut:micronaut-router:jar:sources:2.1.3", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "io.netty:netty-resolver:jar:sources:4.1.53.Final", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "io.netty:netty-codec-http:jar:sources:4.1.66.Final", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.12.2", + "io.micronaut:micronaut-http-server:jar:sources:2.5.12", + "io.netty:netty-codec:jar:sources:4.1.66.Final", + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3", + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.12.2", + "io.micronaut:micronaut-router:jar:sources:2.5.12", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-buffer-netty:jar:sources:2.5.12", + "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", "javax.inject:javax.inject:jar:sources:1", + "io.micronaut:micronaut-context:jar:sources:2.5.12", "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "io.netty:netty-codec-http:jar:sources:4.1.53.Final", - "io.netty:netty-codec:jar:sources:4.1.53.Final", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-http-server:jar:sources:2.1.3", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "io.netty:netty-codec-http2:jar:sources:4.1.66.Final", + "io.micronaut:micronaut-http-netty:jar:sources:2.5.12", + "io.micronaut:micronaut-websocket:jar:sources:2.5.12", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.netty:netty-transport:jar:sources:4.1.66.Final", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", + "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.2", + "io.netty:netty-resolver:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final", + "io.netty:netty-handler:jar:sources:4.1.66.Final", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "io.micronaut:micronaut-http-netty:jar:sources:2.1.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "io.netty:netty-codec-http:jar:sources:4.1.53.Final", - "io.micronaut:micronaut-http-server:jar:sources:2.1.3", - "io.micronaut:micronaut-core:jar:sources:2.1.3" + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", + "io.netty:netty-codec-http:jar:sources:4.1.66.Final", + "io.micronaut:micronaut-http-server:jar:sources:2.5.12", + "io.micronaut:micronaut-http-netty:jar:sources:2.5.12" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-server-netty/2.1.3/micronaut-http-server-netty-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-server-netty/2.5.12/micronaut-http-server-netty-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-server-netty/2.1.3/micronaut-http-server-netty-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-server-netty/2.1.3/micronaut-http-server-netty-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-http-server-netty/2.1.3/micronaut-http-server-netty-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-http-server-netty/2.1.3/micronaut-http-server-netty-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-server-netty/2.5.12/micronaut-http-server-netty-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-server-netty/2.5.12/micronaut-http-server-netty-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-http-server-netty/2.5.12/micronaut-http-server-netty-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-http-server-netty/2.5.12/micronaut-http-server-netty-2.5.12-sources.jar" ], - "sha256": "634234a7e335207044e2f28003e547b674ceb4f9df2c20a7851fceee0c6cd32f", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-server-netty/2.1.3/micronaut-http-server-netty-2.1.3-sources.jar" + "sha256": "b0d446c48c2abdf7fc1f5db7abb21bf548610d0b1d6a5f785a6423aa94abe81b", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-server-netty/2.5.12/micronaut-http-server-netty-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-http-server:2.1.3", + "coord": "io.micronaut:micronaut-http-server:2.5.12", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3", + "com.fasterxml.jackson.core:jackson-databind:2.12.2", + "com.fasterxml.jackson.core:jackson-core:2.12.3", + "io.micronaut:micronaut-context:2.5.12", + "io.micronaut:micronaut-http:2.5.12", "org.yaml:snakeyaml:1.26", + "io.micronaut:micronaut-websocket:2.5.12", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "io.micronaut:micronaut-router:2.1.3", - "io.micronaut:micronaut-websocket:2.1.3", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", + "io.reactivex.rxjava2:rxjava:2.2.21", + "io.micronaut:micronaut-core-reactive:2.5.12", + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "io.micronaut:micronaut-router:2.5.12", + "io.micronaut:micronaut-runtime:2.5.12", "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-core:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.2", "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.2" ], "directDependencies": [ - "io.micronaut:micronaut-router:2.1.3", - "io.micronaut:micronaut-runtime:2.1.3", - "io.micronaut:micronaut-websocket:2.1.3", - "org.slf4j:slf4j-api:1.7.30" + "io.micronaut:micronaut-router:2.5.12", + "io.micronaut:micronaut-runtime:2.5.12", + "io.micronaut:micronaut-websocket:2.5.12", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-server/2.1.3/micronaut-http-server-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-server/2.5.12/micronaut-http-server-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-server/2.1.3/micronaut-http-server-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-server/2.1.3/micronaut-http-server-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-http-server/2.1.3/micronaut-http-server-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-http-server/2.1.3/micronaut-http-server-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-server/2.5.12/micronaut-http-server-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-server/2.5.12/micronaut-http-server-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-http-server/2.5.12/micronaut-http-server-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-http-server/2.5.12/micronaut-http-server-2.5.12.jar" ], - "sha256": "29e2ce77b1710c2e297b37335493f1e5db058a328b326f56c73496e9ffaffd79", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-server/2.1.3/micronaut-http-server-2.1.3.jar" + "sha256": "2bbb43d41d1a737b61a3f415ae9578ff7cb5c39cb5fe21bd09c78e9d562ac06e", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-server/2.5.12/micronaut-http-server-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-http-server:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-http-server:jar:sources:2.5.12", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.micronaut:micronaut-websocket:jar:sources:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "io.micronaut:micronaut-router:jar:sources:2.1.3", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.12.2", + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.12.2", + "io.micronaut:micronaut-router:jar:sources:2.5.12", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", "javax.inject:javax.inject:jar:sources:1", + "io.micronaut:micronaut-context:jar:sources:2.5.12", "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "io.micronaut:micronaut-websocket:jar:sources:2.5.12", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", + "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.2", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "io.micronaut:micronaut-router:jar:sources:2.1.3", - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "io.micronaut:micronaut-websocket:jar:sources:2.1.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30" + "io.micronaut:micronaut-router:jar:sources:2.5.12", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "io.micronaut:micronaut-websocket:jar:sources:2.5.12", + "org.slf4j:slf4j-api:jar:sources:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-server/2.1.3/micronaut-http-server-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http-server/2.5.12/micronaut-http-server-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-server/2.1.3/micronaut-http-server-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-server/2.1.3/micronaut-http-server-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-http-server/2.1.3/micronaut-http-server-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-http-server/2.1.3/micronaut-http-server-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-server/2.5.12/micronaut-http-server-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http-server/2.5.12/micronaut-http-server-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-http-server/2.5.12/micronaut-http-server-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-http-server/2.5.12/micronaut-http-server-2.5.12-sources.jar" ], - "sha256": "cb8bb5caf06a09c8dcae548651bb937e87f995786c87b6b5073bef6e36615516", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-server/2.1.3/micronaut-http-server-2.1.3-sources.jar" + "sha256": "e26f0563e3d4339f0c3b19fa43e3becc3a21ecc18faa100996655636430a81a5", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http-server/2.5.12/micronaut-http-server-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-http:2.1.3", + "coord": "io.micronaut:micronaut-http:2.5.12", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3", "org.yaml:snakeyaml:1.26", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", + "io.micronaut:micronaut-core-reactive:2.5.12", + "com.github.spotbugs:spotbugs-annotations:4.0.3", "javax.annotation:javax.annotation-api:1.3.2", + "io.micronaut:micronaut-core:2.5.12", "javax.inject:javax.inject:1", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6" + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30" + "io.micronaut:micronaut-core-reactive:2.5.12", + "io.micronaut:micronaut-inject:2.5.12", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http/2.1.3/micronaut-http-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http/2.5.12/micronaut-http-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-http/2.1.3/micronaut-http-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http/2.1.3/micronaut-http-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-http/2.1.3/micronaut-http-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-http/2.1.3/micronaut-http-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-http/2.5.12/micronaut-http-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http/2.5.12/micronaut-http-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-http/2.5.12/micronaut-http-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-http/2.5.12/micronaut-http-2.5.12.jar" ], - "sha256": "baaea5281b5e574ee13bec9753a78e39c7cb540ea4391bcffc517ded1e1d6aae", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http/2.1.3/micronaut-http-2.1.3.jar" + "sha256": "4b8c194a2ea9d275b4d62153a4fa573d7ddc00286739cdd8a80cd073e3bc20d8", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http/2.5.12/micronaut-http-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-http:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-http:jar:sources:2.5.12", "dependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", + "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", "javax.inject:javax.inject:jar:sources:1", "org.yaml:snakeyaml:jar:sources:1.26", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3" + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30" + "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "org.slf4j:slf4j-api:jar:sources:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http/2.1.3/micronaut-http-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-http/2.5.12/micronaut-http-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-http/2.1.3/micronaut-http-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http/2.1.3/micronaut-http-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-http/2.1.3/micronaut-http-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-http/2.1.3/micronaut-http-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-http/2.5.12/micronaut-http-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-http/2.5.12/micronaut-http-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-http/2.5.12/micronaut-http-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-http/2.5.12/micronaut-http-2.5.12-sources.jar" ], - "sha256": "e457cac59e8865990bec2e892c9f995b727380c8ed0d4bfad20fd3fbac85bffd", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http/2.1.3/micronaut-http-2.1.3-sources.jar" + "sha256": "195ffd09e6cfd7def4a6ce03af38e9d8cf6637324f7ed8c57f1aa6ff53cd5403", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-http/2.5.12/micronaut-http-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-inject-java:2.1.3", + "coord": "io.micronaut:micronaut-inject-java:2.5.12", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", "org.yaml:snakeyaml:1.26", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "io.micronaut:micronaut-aop:2.1.3", + "com.github.spotbugs:spotbugs-annotations:4.0.3", "javax.annotation:javax.annotation-api:1.3.2", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-core:2.5.12", "javax.inject:javax.inject:1", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6" + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-aop:2.1.3", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30" + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-inject:2.5.12", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-inject-java/2.1.3/micronaut-inject-java-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-inject-java/2.5.12/micronaut-inject-java-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-inject-java/2.1.3/micronaut-inject-java-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-inject-java/2.1.3/micronaut-inject-java-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-inject-java/2.1.3/micronaut-inject-java-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-inject-java/2.1.3/micronaut-inject-java-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-inject-java/2.5.12/micronaut-inject-java-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-inject-java/2.5.12/micronaut-inject-java-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-inject-java/2.5.12/micronaut-inject-java-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-inject-java/2.5.12/micronaut-inject-java-2.5.12.jar" ], - "sha256": "7e5c9e0e3448d6ab920bf0d3a304d24b65fa1e107188a802e87485c49a484f22", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-inject-java/2.1.3/micronaut-inject-java-2.1.3.jar" + "sha256": "c2bf773bcbef9aaa5804ad35501abd79cd49363070d7a7e605ec7c305d8f8bbf", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-inject-java/2.5.12/micronaut-inject-java-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-inject-java:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-inject-java:jar:sources:2.5.12", "dependencies": [ - "io.micronaut:micronaut-aop:jar:sources:2.1.3", + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", "javax.inject:javax.inject:jar:sources:1", "org.yaml:snakeyaml:jar:sources:1.26", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3" + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30" + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "org.slf4j:slf4j-api:jar:sources:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-inject-java/2.1.3/micronaut-inject-java-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-inject-java/2.5.12/micronaut-inject-java-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-inject-java/2.1.3/micronaut-inject-java-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-inject-java/2.1.3/micronaut-inject-java-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-inject-java/2.1.3/micronaut-inject-java-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-inject-java/2.1.3/micronaut-inject-java-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-inject-java/2.5.12/micronaut-inject-java-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-inject-java/2.5.12/micronaut-inject-java-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-inject-java/2.5.12/micronaut-inject-java-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-inject-java/2.5.12/micronaut-inject-java-2.5.12-sources.jar" ], - "sha256": "19af060bd5a448ca8841dd398491ea4e26b77e28173864226cd591cb72df4ee5", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-inject-java/2.1.3/micronaut-inject-java-2.1.3-sources.jar" + "sha256": "43cc70072b16f4e1af73e2f6c0c230f11bda41215756e5d511f524b7e0b43e20", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-inject-java/2.5.12/micronaut-inject-java-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-inject:2.1.3", + "coord": "io.micronaut:micronaut-inject:2.5.12", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", "org.yaml:snakeyaml:1.26", "com.google.code.findbugs:jsr305:3.0.2", - "org.slf4j:slf4j-api:1.7.30", + "com.github.spotbugs:spotbugs-annotations:4.0.3", "javax.annotation:javax.annotation-api:1.3.2", + "io.micronaut:micronaut-core:2.5.12", "javax.inject:javax.inject:1", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6" + "org.slf4j:slf4j-api:1.7.31" ], "directDependencies": [ "org.yaml:snakeyaml:1.26", - "org.slf4j:slf4j-api:1.7.30", "javax.annotation:javax.annotation-api:1.3.2", + "io.micronaut:micronaut-core:2.5.12", "javax.inject:javax.inject:1", - "io.micronaut:micronaut-core:2.1.3" + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-inject/2.1.3/micronaut-inject-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-inject/2.5.12/micronaut-inject-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-inject/2.1.3/micronaut-inject-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-inject/2.1.3/micronaut-inject-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-inject/2.1.3/micronaut-inject-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-inject/2.1.3/micronaut-inject-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-inject/2.5.12/micronaut-inject-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-inject/2.5.12/micronaut-inject-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-inject/2.5.12/micronaut-inject-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-inject/2.5.12/micronaut-inject-2.5.12.jar" ], - "sha256": "273b199793d23ab7d30e9e878e227a323eb8c42eb6d02577a6460d1a1685416a", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-inject/2.1.3/micronaut-inject-2.1.3.jar" + "sha256": "124cb83ea24d422c2cac012650cd89f9073b199c1f1951e1c1fbb87c4ab32ef2", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-inject/2.5.12/micronaut-inject-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-inject:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-inject:jar:sources:2.5.12", "dependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", "javax.inject:javax.inject:jar:sources:1", "org.yaml:snakeyaml:jar:sources:1.26", - "io.micronaut:micronaut-core:jar:sources:2.1.3", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "org.slf4j:slf4j-api:jar:sources:1.7.30", + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "javax.inject:javax.inject:jar:sources:1", "org.yaml:snakeyaml:jar:sources:1.26", - "io.micronaut:micronaut-core:jar:sources:2.1.3", "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-inject/2.1.3/micronaut-inject-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-inject/2.5.12/micronaut-inject-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-inject/2.1.3/micronaut-inject-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-inject/2.1.3/micronaut-inject-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-inject/2.1.3/micronaut-inject-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-inject/2.1.3/micronaut-inject-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-inject/2.5.12/micronaut-inject-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-inject/2.5.12/micronaut-inject-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-inject/2.5.12/micronaut-inject-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-inject/2.5.12/micronaut-inject-2.5.12-sources.jar" ], - "sha256": "92705d7e221c5c3e0d22571a57bde0443f69737f00ebdf02544047e784c3dd24", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-inject/2.1.3/micronaut-inject-2.1.3-sources.jar" + "sha256": "927b8547d553bcb9c06b06d2fdff41871e053daf28105b29fa111281661ad585", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-inject/2.5.12/micronaut-inject-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-management:2.0.1", + "coord": "io.micronaut:micronaut-management:2.5.12", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3", + "com.fasterxml.jackson.core:jackson-databind:2.12.2", + "com.fasterxml.jackson.core:jackson-core:2.12.3", + "io.micronaut:micronaut-context:2.5.12", + "io.micronaut:micronaut-http:2.5.12", "org.yaml:snakeyaml:1.26", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "io.micronaut:micronaut-router:2.1.3", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", + "io.reactivex.rxjava2:rxjava:2.2.21", + "io.micronaut:micronaut-core-reactive:2.5.12", + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "io.micronaut:micronaut-router:2.5.12", + "io.micronaut:micronaut-runtime:2.5.12", "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-core:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.2", "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.2" ], "directDependencies": [ - "io.micronaut:micronaut-router:2.1.3", - "io.micronaut:micronaut-runtime:2.1.3", - "org.slf4j:slf4j-api:1.7.30" + "io.micronaut:micronaut-router:2.5.12", + "io.micronaut:micronaut-runtime:2.5.12", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-management/2.0.1/micronaut-management-2.0.1.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-management/2.5.12/micronaut-management-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-management/2.0.1/micronaut-management-2.0.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-management/2.0.1/micronaut-management-2.0.1.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-management/2.0.1/micronaut-management-2.0.1.jar", - "https://maven.google.com/io/micronaut/micronaut-management/2.0.1/micronaut-management-2.0.1.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-management/2.5.12/micronaut-management-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-management/2.5.12/micronaut-management-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-management/2.5.12/micronaut-management-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-management/2.5.12/micronaut-management-2.5.12.jar" ], - "sha256": "cf89dd67703229797cea3376551c1a8723876136fcbb4aabdcbc7479b77923e9", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-management/2.0.1/micronaut-management-2.0.1.jar" + "sha256": "4924aa45d304c43ca8c484175f41c54ec124a369129613d3a5692e8a321813e4", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-management/2.5.12/micronaut-management-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-management:jar:sources:2.0.1", + "coord": "io.micronaut:micronaut-management:jar:sources:2.5.12", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "io.micronaut:micronaut-router:jar:sources:2.1.3", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.12.2", + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.12.2", + "io.micronaut:micronaut-router:jar:sources:2.5.12", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", "javax.inject:javax.inject:jar:sources:1", + "io.micronaut:micronaut-context:jar:sources:2.5.12", "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", + "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.2", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "io.micronaut:micronaut-router:jar:sources:2.1.3", - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30" + "io.micronaut:micronaut-router:jar:sources:2.5.12", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "org.slf4j:slf4j-api:jar:sources:1.7.31" ], "exclusions": [ "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-management/2.0.1/micronaut-management-2.0.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-management/2.5.12/micronaut-management-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-management/2.0.1/micronaut-management-2.0.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-management/2.0.1/micronaut-management-2.0.1-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-management/2.0.1/micronaut-management-2.0.1-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-management/2.0.1/micronaut-management-2.0.1-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-management/2.5.12/micronaut-management-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-management/2.5.12/micronaut-management-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-management/2.5.12/micronaut-management-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-management/2.5.12/micronaut-management-2.5.12-sources.jar" ], - "sha256": "cebb4bb12f5daac14d9910c57efcfa2f33e24471123943068300851667510e97", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-management/2.0.1/micronaut-management-2.0.1-sources.jar" + "sha256": "cd22d639836ace093e6cf2b1bb3ca01be8ef546bbf00ee39daad15cece41b86d", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-management/2.5.12/micronaut-management-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-messaging:2.1.3", + "coord": "io.micronaut:micronaut-messaging:2.5.12", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3", + "com.fasterxml.jackson.core:jackson-databind:2.12.2", + "com.fasterxml.jackson.core:jackson-core:2.12.3", + "io.micronaut:micronaut-context:2.5.12", + "io.micronaut:micronaut-http:2.5.12", "org.yaml:snakeyaml:1.26", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", + "io.reactivex.rxjava2:rxjava:2.2.21", + "io.micronaut:micronaut-core-reactive:2.5.12", + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "io.micronaut:micronaut-runtime:2.5.12", "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-core:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.2", "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.2" ], "directDependencies": [ - "io.micronaut:micronaut-inject:2.1.3", - "io.micronaut:micronaut-runtime:2.1.3", - "org.slf4j:slf4j-api:1.7.30" + "io.micronaut:micronaut-inject:2.5.12", + "io.micronaut:micronaut-runtime:2.5.12", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-messaging/2.1.3/micronaut-messaging-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-messaging/2.5.12/micronaut-messaging-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-messaging/2.1.3/micronaut-messaging-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-messaging/2.1.3/micronaut-messaging-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-messaging/2.1.3/micronaut-messaging-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-messaging/2.1.3/micronaut-messaging-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-messaging/2.5.12/micronaut-messaging-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-messaging/2.5.12/micronaut-messaging-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-messaging/2.5.12/micronaut-messaging-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-messaging/2.5.12/micronaut-messaging-2.5.12.jar" ], - "sha256": "5c7a048f1cfd53eb050414334c5debca69927557722547b17eb03f270e2281ec", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-messaging/2.1.3/micronaut-messaging-2.1.3.jar" + "sha256": "686ff233c5e74849faf17da3ebc7f006bbe0ec1d29452dd2a117b82dee943e8a", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-messaging/2.5.12/micronaut-messaging-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-messaging:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-messaging:jar:sources:2.5.12", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.12.2", + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.12.2", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", "javax.inject:javax.inject:jar:sources:1", + "io.micronaut:micronaut-context:jar:sources:2.5.12", "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", + "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.2", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30" + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "org.slf4j:slf4j-api:jar:sources:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-messaging/2.1.3/micronaut-messaging-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-messaging/2.5.12/micronaut-messaging-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-messaging/2.1.3/micronaut-messaging-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-messaging/2.1.3/micronaut-messaging-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-messaging/2.1.3/micronaut-messaging-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-messaging/2.1.3/micronaut-messaging-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-messaging/2.5.12/micronaut-messaging-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-messaging/2.5.12/micronaut-messaging-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-messaging/2.5.12/micronaut-messaging-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-messaging/2.5.12/micronaut-messaging-2.5.12-sources.jar" ], - "sha256": "1cf3452ee39623fc61cf263be53a6615c9ba1bc959f7f77e5262d5df9ffa6aa1", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-messaging/2.1.3/micronaut-messaging-2.1.3-sources.jar" + "sha256": "d6e3fe5ca3c84b59bd0de9bbe5f2378fab3be8d98642805142c2f5dd5ac01b5a", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-messaging/2.5.12/micronaut-messaging-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-multitenancy:2.1.3", + "coord": "io.micronaut:micronaut-multitenancy:2.2.3", "dependencies": [ - "org.reactivestreams:reactive-streams:1.0.3", - "org.yaml:snakeyaml:1.26", - "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "io.micronaut:micronaut-router:2.1.3", - "io.micronaut:micronaut-websocket:2.1.3", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", - "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "io.micronaut:micronaut-http-server:2.1.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", - "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut:micronaut-http-server:2.5.12", + "io.micronaut:micronaut-runtime:2.5.12", + "org.slf4j:slf4j-api:1.7.31" ], "directDependencies": [ - "io.micronaut:micronaut-http:2.1.3", - "io.micronaut:micronaut-http-server:2.1.3", - "io.micronaut:micronaut-runtime:2.1.3", - "org.slf4j:slf4j-api:1.7.30" + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut:micronaut-http-server:2.5.12", + "io.micronaut:micronaut-runtime:2.5.12", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", - "com.google.common.html.types:types" + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-multitenancy/2.1.3/micronaut-multitenancy-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-multitenancy/2.2.3/micronaut-multitenancy-2.2.3.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-multitenancy/2.1.3/micronaut-multitenancy-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-multitenancy/2.1.3/micronaut-multitenancy-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-multitenancy/2.1.3/micronaut-multitenancy-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-multitenancy/2.1.3/micronaut-multitenancy-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-multitenancy/2.2.3/micronaut-multitenancy-2.2.3.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-multitenancy/2.2.3/micronaut-multitenancy-2.2.3.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-multitenancy/2.2.3/micronaut-multitenancy-2.2.3.jar", + "https://maven.google.com/io/micronaut/micronaut-multitenancy/2.2.3/micronaut-multitenancy-2.2.3.jar" ], - "sha256": "95906a90cd641960dd84349a1b3f6710c87deaef1c254c55976601aed87499a6", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-multitenancy/2.1.3/micronaut-multitenancy-2.1.3.jar" + "sha256": "8d8d1ba0554eb1575ccfee1e71339c95a8e79d456dc16545f35c8206c1c7ff7d", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-multitenancy/2.2.3/micronaut-multitenancy-2.2.3.jar" }, { - "coord": "io.micronaut:micronaut-multitenancy:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-multitenancy:jar:sources:2.2.3", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.micronaut:micronaut-websocket:jar:sources:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "io.micronaut:micronaut-router:jar:sources:2.1.3", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "javax.inject:javax.inject:jar:sources:1", - "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-http-server:jar:sources:2.1.3", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-http-server:jar:sources:2.5.12", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-http:jar:sources:2.1.3", - "io.micronaut:micronaut-http-server:jar:sources:2.1.3", - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30" + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.micronaut:micronaut-http-server:jar:sources:2.5.12", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "org.slf4j:slf4j-api:jar:sources:1.7.31" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", - "com.google.common.html.types:types" + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-multitenancy/2.1.3/micronaut-multitenancy-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-multitenancy/2.2.3/micronaut-multitenancy-2.2.3-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-multitenancy/2.1.3/micronaut-multitenancy-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-multitenancy/2.1.3/micronaut-multitenancy-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-multitenancy/2.1.3/micronaut-multitenancy-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-multitenancy/2.1.3/micronaut-multitenancy-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-multitenancy/2.2.3/micronaut-multitenancy-2.2.3-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-multitenancy/2.2.3/micronaut-multitenancy-2.2.3-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-multitenancy/2.2.3/micronaut-multitenancy-2.2.3-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-multitenancy/2.2.3/micronaut-multitenancy-2.2.3-sources.jar" ], - "sha256": "d964b585709bc97c45b04c0ab37c5e94a2465271b49203a3451b11d38249687a", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-multitenancy/2.1.3/micronaut-multitenancy-2.1.3-sources.jar" + "sha256": "80bb6bce3821897ddf6bd9f68e2340b002b777c514b4d2781199a4575170f463", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-multitenancy/2.2.3/micronaut-multitenancy-2.2.3-sources.jar" }, { - "coord": "io.micronaut:micronaut-router:2.1.3", + "coord": "io.micronaut:micronaut-router:2.5.12", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3", + "io.micronaut:micronaut-http:2.5.12", "org.yaml:snakeyaml:1.26", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", + "io.micronaut:micronaut-core-reactive:2.5.12", + "com.github.spotbugs:spotbugs-annotations:4.0.3", "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", + "io.micronaut:micronaut-core:2.5.12", "javax.inject:javax.inject:1", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6" + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-http:2.1.3", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30" + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut:micronaut-inject:2.5.12", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-router/2.1.3/micronaut-router-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-router/2.5.12/micronaut-router-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-router/2.1.3/micronaut-router-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-router/2.1.3/micronaut-router-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-router/2.1.3/micronaut-router-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-router/2.1.3/micronaut-router-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-router/2.5.12/micronaut-router-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-router/2.5.12/micronaut-router-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-router/2.5.12/micronaut-router-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-router/2.5.12/micronaut-router-2.5.12.jar" ], - "sha256": "b032802dd028c82c7ac0030abda099e43c2af73c8aeb4f3811d130b40c9c0b8b", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-router/2.1.3/micronaut-router-2.1.3.jar" + "sha256": "b164bbf3e7a2a6278bea2711f6efc7d5e13a77124677a69acc669e3faad8614f", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-router/2.5.12/micronaut-router-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-router:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-router:jar:sources:2.5.12", "dependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", + "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", "javax.inject:javax.inject:jar:sources:1", "org.yaml:snakeyaml:jar:sources:1.26", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "io.micronaut:micronaut-http:jar:sources:2.1.3", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30" + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "org.slf4j:slf4j-api:jar:sources:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-router/2.1.3/micronaut-router-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-router/2.5.12/micronaut-router-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-router/2.1.3/micronaut-router-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-router/2.1.3/micronaut-router-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-router/2.1.3/micronaut-router-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-router/2.1.3/micronaut-router-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-router/2.5.12/micronaut-router-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-router/2.5.12/micronaut-router-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-router/2.5.12/micronaut-router-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-router/2.5.12/micronaut-router-2.5.12-sources.jar" ], - "sha256": "3ed6d21f83e54bd0c9eb331f3500c38a8bed1af425a963fc35c958943d0a3f49", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-router/2.1.3/micronaut-router-2.1.3-sources.jar" + "sha256": "99d292b791859bca03b614730c8801098d8ce608358cbc8ec18dd200af84288c", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-router/2.5.12/micronaut-router-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-runtime:2.1.3", + "coord": "io.micronaut:micronaut-runtime:2.5.12", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3", + "com.fasterxml.jackson.core:jackson-databind:2.12.2", + "com.fasterxml.jackson.core:jackson-core:2.12.3", + "io.micronaut:micronaut-context:2.5.12", + "io.micronaut:micronaut-http:2.5.12", "org.yaml:snakeyaml:1.26", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", + "io.reactivex.rxjava2:rxjava:2.2.21", + "io.micronaut:micronaut-core-reactive:2.5.12", + "com.github.spotbugs:spotbugs-annotations:4.0.3", "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-core:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.2", "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" - ], - "directDependencies": [ - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", - "io.micronaut:micronaut-http:2.1.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", - "javax.validation:validation-api:2.0.1.Final" + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.2" + ], + "directDependencies": [ + "com.fasterxml.jackson.core:jackson-databind:2.12.2", + "io.micronaut:micronaut-context:2.5.12", + "io.micronaut:micronaut-http:2.5.12", + "io.reactivex.rxjava2:rxjava:2.2.21", + "io.micronaut:micronaut-core-reactive:2.5.12", + "io.micronaut:micronaut-aop:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.2", + "javax.validation:validation-api:2.0.1.Final", + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.2" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-runtime/2.1.3/micronaut-runtime-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-runtime/2.5.12/micronaut-runtime-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-runtime/2.1.3/micronaut-runtime-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-runtime/2.1.3/micronaut-runtime-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-runtime/2.1.3/micronaut-runtime-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-runtime/2.1.3/micronaut-runtime-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-runtime/2.5.12/micronaut-runtime-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-runtime/2.5.12/micronaut-runtime-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-runtime/2.5.12/micronaut-runtime-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-runtime/2.5.12/micronaut-runtime-2.5.12.jar" ], - "sha256": "8cbbb9466660494a50abe4026bb99e5e8f968b8f1f8941e6b9183f21006a426c", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-runtime/2.1.3/micronaut-runtime-2.1.3.jar" + "sha256": "cd6e29a180f0142209850b6ba63b5e14297f397d02f0530ac6fee62c63e97fe4", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-runtime/2.5.12/micronaut-runtime-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-runtime:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-runtime:jar:sources:2.5.12", "dependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.12.2", + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.12.2", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", "javax.inject:javax.inject:jar:sources:1", + "io.micronaut:micronaut-context:jar:sources:2.5.12", "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", + "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.2", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.12.2", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.12.2", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", + "io.micronaut:micronaut-context:jar:sources:2.5.12", + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", + "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.2" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-runtime/2.1.3/micronaut-runtime-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-runtime/2.5.12/micronaut-runtime-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-runtime/2.1.3/micronaut-runtime-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-runtime/2.1.3/micronaut-runtime-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-runtime/2.1.3/micronaut-runtime-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-runtime/2.1.3/micronaut-runtime-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-runtime/2.5.12/micronaut-runtime-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-runtime/2.5.12/micronaut-runtime-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-runtime/2.5.12/micronaut-runtime-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-runtime/2.5.12/micronaut-runtime-2.5.12-sources.jar" ], - "sha256": "107f9604add246b8e2178680e4b26db942373a4370fb46b53b112d9074cb4a71", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-runtime/2.1.3/micronaut-runtime-2.1.3-sources.jar" + "sha256": "34785ef1109ccb13eac5c69f0583eadccea77c3e03a6c8928c77f1b451ab2a45", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-runtime/2.5.12/micronaut-runtime-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-session:2.1.3", + "coord": "io.micronaut:micronaut-session:2.5.12", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3", + "com.fasterxml.jackson.core:jackson-databind:2.12.2", + "com.fasterxml.jackson.core:jackson-core:2.12.3", + "io.micronaut:micronaut-context:2.5.12", + "io.micronaut:micronaut-http:2.5.12", "org.yaml:snakeyaml:1.26", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", + "io.reactivex.rxjava2:rxjava:2.2.21", + "io.micronaut:micronaut-core-reactive:2.5.12", + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "io.micronaut:micronaut-runtime:2.5.12", "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-core:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.2", "javax.inject:javax.inject:1", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.2" ], "directDependencies": [ - "io.micronaut:micronaut-http:2.1.3", - "io.micronaut:micronaut-runtime:2.1.3", - "org.slf4j:slf4j-api:1.7.30" + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut:micronaut-runtime:2.5.12", + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-session/2.1.3/micronaut-session-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-session/2.5.12/micronaut-session-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-session/2.1.3/micronaut-session-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-session/2.1.3/micronaut-session-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-session/2.1.3/micronaut-session-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-session/2.1.3/micronaut-session-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-session/2.5.12/micronaut-session-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-session/2.5.12/micronaut-session-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-session/2.5.12/micronaut-session-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-session/2.5.12/micronaut-session-2.5.12.jar" ], - "sha256": "397eb5ed4ddffedb378c08392057b05f63d4c8c3d45eb50b442567f2b790fab0", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-session/2.1.3/micronaut-session-2.1.3.jar" + "sha256": "6e75fb9447a614eddb44bf7d822c8cd9870bf0a26173cae3975988d76c3e158c", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-session/2.5.12/micronaut-session-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-session:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-session:jar:sources:2.5.12", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.12.2", + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.12.2", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", "javax.inject:javax.inject:jar:sources:1", + "io.micronaut:micronaut-context:jar:sources:2.5.12", "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", + "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.2", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "io.micronaut:micronaut-http:jar:sources:2.1.3", - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30" + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "org.slf4j:slf4j-api:jar:sources:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-session/2.1.3/micronaut-session-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-session/2.5.12/micronaut-session-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-session/2.1.3/micronaut-session-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-session/2.1.3/micronaut-session-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-session/2.1.3/micronaut-session-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-session/2.1.3/micronaut-session-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-session/2.5.12/micronaut-session-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-session/2.5.12/micronaut-session-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-session/2.5.12/micronaut-session-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-session/2.5.12/micronaut-session-2.5.12-sources.jar" ], - "sha256": "cc3b86a81fe1a52e2334e180d9bc62dbad6af16504366b11479f10da4d53dcc8", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-session/2.1.3/micronaut-session-2.1.3-sources.jar" + "sha256": "ce6bddc0c98724958fb96e5a612f99a2bd3cbfd1496b9e54b84ba9420d046317", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-session/2.5.12/micronaut-session-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-tracing:2.1.3", + "coord": "io.micronaut:micronaut-tracing:2.5.12", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3", - "io.netty:netty-codec-http2:4.1.53.Final", - "io.netty:netty-codec:4.1.53.Final", - "io.netty:netty-handler:4.1.53.Final", + "com.fasterxml.jackson.core:jackson-databind:2.12.2", + "io.micronaut:micronaut-http-client-core:2.5.12", + "com.fasterxml.jackson.core:jackson-core:2.12.3", + "io.micronaut:micronaut-buffer-netty:2.5.12", + "io.micronaut:micronaut-context:2.5.12", + "io.micronaut:micronaut-http:2.5.12", + "io.micronaut:micronaut-http-netty:2.5.12", "org.yaml:snakeyaml:1.26", - "io.netty:netty-codec-socks:4.1.53.Final", + "io.micronaut:micronaut-websocket:2.5.12", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-buffer:4.1.53.Final", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.2", - "io.micronaut:micronaut-runtime:2.1.3", - "io.netty:netty-resolver:4.1.53.Final", - "io.netty:netty-transport:4.1.53.Final", - "io.micronaut:micronaut-http-netty:2.1.3", - "io.micronaut:micronaut-websocket:2.1.3", - "io.micronaut:micronaut-buffer-netty:2.1.3", - "io.netty:netty-codec-http:4.1.53.Final", - "io.micronaut:micronaut-http-client:2.1.3", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-http-client-core:2.1.3", - "io.micronaut:micronaut-aop:2.1.3", - "com.fasterxml.jackson.core:jackson-annotations:2.11.2", + "io.netty:netty-resolver:4.1.66.Final", + "io.netty:netty-handler-proxy:4.1.66.Final", + "io.micronaut:micronaut-http-client:2.5.12", + "io.reactivex.rxjava2:rxjava:2.2.21", + "io.netty:netty-codec-http:4.1.66.Final", + "io.netty:netty-handler:4.1.66.Final", + "io.micronaut:micronaut-core-reactive:2.5.12", + "com.github.spotbugs:spotbugs-annotations:4.0.3", + "io.netty:netty-codec-socks:4.1.66.Final", + "io.netty:netty-codec:4.1.66.Final", + "io.netty:netty-transport:4.1.66.Final", + "io.micronaut:micronaut-runtime:2.5.12", + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-codec-http2:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final", "javax.annotation:javax.annotation-api:1.3.2", "io.opentracing:opentracing-api:0.33.0", - "io.micronaut:micronaut-http:2.1.3", - "io.netty:netty-handler-proxy:4.1.53.Final", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.2", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-core:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.2", "javax.inject:javax.inject:1", "io.opentracing:opentracing-util:0.33.0", - "com.fasterxml.jackson.core:jackson-databind:2.11.2", "io.opentracing:opentracing-noop:0.33.0", "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6", - "com.fasterxml.jackson.core:jackson-core:2.11.3" + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.2" ], "directDependencies": [ - "org.slf4j:slf4j-api:1.7.30", - "io.micronaut:micronaut-runtime:2.1.3", - "io.micronaut:micronaut-http-client:2.1.3", + "io.micronaut:micronaut-http-client:2.5.12", + "io.micronaut:micronaut-runtime:2.5.12", "io.opentracing:opentracing-api:0.33.0", + "io.micronaut:micronaut-core:2.5.12", "io.opentracing:opentracing-util:0.33.0", - "io.micronaut:micronaut-core:2.1.3" + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-tracing/2.1.3/micronaut-tracing-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-tracing/2.5.12/micronaut-tracing-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-tracing/2.1.3/micronaut-tracing-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-tracing/2.1.3/micronaut-tracing-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-tracing/2.1.3/micronaut-tracing-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-tracing/2.1.3/micronaut-tracing-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-tracing/2.5.12/micronaut-tracing-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-tracing/2.5.12/micronaut-tracing-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-tracing/2.5.12/micronaut-tracing-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-tracing/2.5.12/micronaut-tracing-2.5.12.jar" ], - "sha256": "54c3c6d16aa33ac9bf4d8985e0c03afa5078537a3fc5e7e74bb7aa12cdef33b3", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-tracing/2.1.3/micronaut-tracing-2.1.3.jar" + "sha256": "411e782f09409fd23a8dd8bbd378a23e4aa6099518497b1232cd3f2427a976b2", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-tracing/2.5.12/micronaut-tracing-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-tracing:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-tracing:jar:sources:2.5.12", "dependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "io.netty:netty-handler:jar:sources:4.1.53.Final", + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "javax.validation:validation-api:jar:sources:2.0.1.Final", - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "io.netty:netty-handler-proxy:jar:sources:4.1.53.Final", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "io.micronaut:micronaut-websocket:jar:sources:2.1.3", - "io.micronaut:micronaut-http-netty:jar:sources:2.1.3", - "io.micronaut:micronaut-buffer-netty:jar:sources:2.1.3", - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "io.netty:netty-codec-http2:jar:sources:4.1.53.Final", - "io.netty:netty-codec-socks:jar:sources:4.1.53.Final", - "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.11.2", - "io.netty:netty-common:jar:sources:4.1.53.Final", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", + "io.netty:netty-codec-http:jar:sources:4.1.66.Final", + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.12.2", + "io.netty:netty-codec:jar:sources:4.1.66.Final", + "com.fasterxml.jackson.core:jackson-core:jar:sources:2.12.3", + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.12.2", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", "io.opentracing:opentracing-noop:jar:sources:0.33.0", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", - "io.netty:netty-resolver:jar:sources:4.1.53.Final", + "io.micronaut:micronaut-http-client-core:jar:sources:2.5.12", + "io.micronaut:micronaut-buffer-netty:jar:sources:2.5.12", + "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", "javax.inject:javax.inject:jar:sources:1", + "io.micronaut:micronaut-context:jar:sources:2.5.12", "org.yaml:snakeyaml:jar:sources:1.26", - "com.fasterxml.jackson.core:jackson-core:jar:sources:2.11.3", - "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:sources:2.11.2", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:sources:2.11.2", - "io.netty:netty-codec-http:jar:sources:4.1.53.Final", - "io.netty:netty-codec:jar:sources:4.1.53.Final", - "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.11.2", + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.netty:netty-codec-socks:jar:sources:4.1.66.Final", + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "io.netty:netty-codec-http2:jar:sources:4.1.66.Final", + "io.micronaut:micronaut-http-netty:jar:sources:2.5.12", + "io.micronaut:micronaut-websocket:jar:sources:2.5.12", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", "io.opentracing:opentracing-api:jar:sources:0.33.0", + "io.netty:netty-handler-proxy:jar:sources:4.1.66.Final", + "io.netty:netty-transport:jar:sources:4.1.66.Final", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", + "com.fasterxml.jackson.core:jackson-databind:jar:sources:2.12.2", "io.opentracing:opentracing-util:jar:sources:0.33.0", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "io.micronaut:micronaut-http-client-core:jar:sources:2.1.3", - "io.micronaut:micronaut-http-client:jar:sources:2.1.3", + "io.netty:netty-resolver:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final", + "io.netty:netty-handler:jar:sources:4.1.66.Final", "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http-client:jar:sources:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-runtime:jar:sources:2.1.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30", + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", + "io.micronaut:micronaut-runtime:jar:sources:2.5.12", "io.opentracing:opentracing-api:jar:sources:0.33.0", "io.opentracing:opentracing-util:jar:sources:0.33.0", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "io.micronaut:micronaut-http-client:jar:sources:2.1.3" + "io.micronaut:micronaut-http-client:jar:sources:2.5.12" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-tracing/2.1.3/micronaut-tracing-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-tracing/2.5.12/micronaut-tracing-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-tracing/2.1.3/micronaut-tracing-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-tracing/2.1.3/micronaut-tracing-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-tracing/2.1.3/micronaut-tracing-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-tracing/2.1.3/micronaut-tracing-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-tracing/2.5.12/micronaut-tracing-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-tracing/2.5.12/micronaut-tracing-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-tracing/2.5.12/micronaut-tracing-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-tracing/2.5.12/micronaut-tracing-2.5.12-sources.jar" ], - "sha256": "31f013f27ee8cee88a485a589c394ba36c3ed44e0cb7778ce54557c3dede5e84", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-tracing/2.1.3/micronaut-tracing-2.1.3-sources.jar" + "sha256": "148a58386dc3e8d39c3ba47db206a940aafc904298178bae1d4f89795034bbc5", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-tracing/2.5.12/micronaut-tracing-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-validation:2.1.3", + "coord": "io.micronaut:micronaut-validation:2.5.12", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3", "org.yaml:snakeyaml:1.26", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", + "io.micronaut:micronaut-core-reactive:2.5.12", + "com.github.spotbugs:spotbugs-annotations:4.0.3", "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", + "io.micronaut:micronaut-core:2.5.12", "javax.inject:javax.inject:1", "javax.validation:validation-api:2.0.1.Final", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6" + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-http:2.1.3", - "io.micronaut:micronaut-inject:2.1.3", + "io.micronaut:micronaut-core-reactive:2.5.12", + "io.micronaut:micronaut-inject:2.5.12", "javax.validation:validation-api:2.0.1.Final", - "org.slf4j:slf4j-api:1.7.30" + "org.slf4j:slf4j-api:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-validation/2.1.3/micronaut-validation-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-validation/2.5.12/micronaut-validation-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-validation/2.1.3/micronaut-validation-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-validation/2.1.3/micronaut-validation-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-validation/2.1.3/micronaut-validation-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-validation/2.1.3/micronaut-validation-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-validation/2.5.12/micronaut-validation-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-validation/2.5.12/micronaut-validation-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-validation/2.5.12/micronaut-validation-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-validation/2.5.12/micronaut-validation-2.5.12.jar" ], - "sha256": "6fffafa2f9a7cabd098466063183455c307e443b3f2625628f6c770b8624b158", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-validation/2.1.3/micronaut-validation-2.1.3.jar" + "sha256": "fe422d17eab28ce45debd44eac7a37bd806b47db1c87f8df52bb41f051a248bd", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-validation/2.5.12/micronaut-validation-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-validation:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-validation:jar:sources:2.5.12", "dependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "javax.validation:validation-api:jar:sources:2.0.1.Final", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", + "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", "javax.inject:javax.inject:jar:sources:1", "org.yaml:snakeyaml:jar:sources:1.26", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "io.micronaut:micronaut-http:jar:sources:2.1.3", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", + "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", "javax.validation:validation-api:jar:sources:2.0.1.Final", - "org.slf4j:slf4j-api:jar:sources:1.7.30" + "org.slf4j:slf4j-api:jar:sources:1.7.31" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-validation/2.1.3/micronaut-validation-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-validation/2.5.12/micronaut-validation-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-validation/2.1.3/micronaut-validation-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-validation/2.1.3/micronaut-validation-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-validation/2.1.3/micronaut-validation-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-validation/2.1.3/micronaut-validation-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-validation/2.5.12/micronaut-validation-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-validation/2.5.12/micronaut-validation-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-validation/2.5.12/micronaut-validation-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-validation/2.5.12/micronaut-validation-2.5.12-sources.jar" ], - "sha256": "30db57192e308eb22b3ede617d9b0e833336f5bdafaf9b7d8bfb97522b0b026e", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-validation/2.1.3/micronaut-validation-2.1.3-sources.jar" + "sha256": "87fd363e02bd1b1d9f132ab53ff13826186f5fd8f9b3bb9f7764d1b389b2701b", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-validation/2.5.12/micronaut-validation-2.5.12-sources.jar" }, { - "coord": "io.micronaut:micronaut-websocket:2.1.3", + "coord": "io.micronaut:micronaut-websocket:2.5.12", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3", + "io.micronaut:micronaut-http:2.5.12", "org.yaml:snakeyaml:1.26", "com.google.code.findbugs:jsr305:3.0.2", - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", + "io.reactivex.rxjava2:rxjava:2.2.21", + "io.micronaut:micronaut-core-reactive:2.5.12", + "com.github.spotbugs:spotbugs-annotations:4.0.3", "javax.annotation:javax.annotation-api:1.3.2", - "io.micronaut:micronaut-http:2.1.3", + "io.micronaut:micronaut-aop:2.5.12", + "io.micronaut:micronaut-core:2.5.12", "javax.inject:javax.inject:1", - "io.micronaut:micronaut-core:2.1.3", - "com.github.spotbugs:spotbugs-annotations:4.0.6" + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12" ], "directDependencies": [ - "io.micronaut:micronaut-inject:2.1.3", - "org.slf4j:slf4j-api:1.7.30", - "io.reactivex.rxjava2:rxjava:2.2.20", - "io.micronaut:micronaut-aop:2.1.3", - "io.micronaut:micronaut-http:2.1.3" + "io.micronaut:micronaut-http:2.5.12", + "io.reactivex.rxjava2:rxjava:2.2.21", + "io.micronaut:micronaut-aop:2.5.12", + "org.slf4j:slf4j-api:1.7.31", + "io.micronaut:micronaut-inject:2.5.12" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-websocket/2.1.3/micronaut-websocket-2.1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-websocket/2.5.12/micronaut-websocket-2.5.12.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-websocket/2.1.3/micronaut-websocket-2.1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-websocket/2.1.3/micronaut-websocket-2.1.3.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-websocket/2.1.3/micronaut-websocket-2.1.3.jar", - "https://maven.google.com/io/micronaut/micronaut-websocket/2.1.3/micronaut-websocket-2.1.3.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-websocket/2.5.12/micronaut-websocket-2.5.12.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-websocket/2.5.12/micronaut-websocket-2.5.12.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-websocket/2.5.12/micronaut-websocket-2.5.12.jar", + "https://maven.google.com/io/micronaut/micronaut-websocket/2.5.12/micronaut-websocket-2.5.12.jar" ], - "sha256": "cbe470a2a1f1044de162b73e48c1c3b8a31878fa10fa50ab1d6d9f81d71ee487", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-websocket/2.1.3/micronaut-websocket-2.1.3.jar" + "sha256": "455928da0c5efb2b8b8bf6fa57d90c2a8a32d1f5265578a09c8384f300fbf24e", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-websocket/2.5.12/micronaut-websocket-2.5.12.jar" }, { - "coord": "io.micronaut:micronaut-websocket:jar:sources:2.1.3", + "coord": "io.micronaut:micronaut-websocket:jar:sources:2.5.12", "dependencies": [ - "io.micronaut:micronaut-aop:jar:sources:2.1.3", + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-core:jar:sources:2.5.12", "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.6", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-core-reactive:jar:sources:2.5.12", "org.reactivestreams:reactive-streams:jar:sources:1.0.3", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", "javax.inject:javax.inject:jar:sources:1", "org.yaml:snakeyaml:jar:sources:1.26", - "io.micronaut:micronaut-core:jar:sources:2.1.3", - "javax.annotation:javax.annotation-api:jar:sources:1.3.2", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "com.github.spotbugs:spotbugs-annotations:jar:sources:4.0.3", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", + "javax.annotation:javax.annotation-api:jar:sources:1.3.2" ], "directDependencies": [ - "io.micronaut:micronaut-aop:jar:sources:2.1.3", - "org.slf4j:slf4j-api:jar:sources:1.7.30", - "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", - "io.micronaut:micronaut-inject:jar:sources:2.1.3", - "io.micronaut:micronaut-http:jar:sources:2.1.3" + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "io.micronaut:micronaut-aop:jar:sources:2.5.12", + "io.micronaut:micronaut-http:jar:sources:2.5.12", + "io.micronaut:micronaut-inject:jar:sources:2.5.12", + "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-websocket/2.1.3/micronaut-websocket-2.1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/micronaut/micronaut-websocket/2.5.12/micronaut-websocket-2.5.12-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/micronaut/micronaut-websocket/2.1.3/micronaut-websocket-2.1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-websocket/2.1.3/micronaut-websocket-2.1.3-sources.jar", - "https://jcenter.bintray.com/io/micronaut/micronaut-websocket/2.1.3/micronaut-websocket-2.1.3-sources.jar", - "https://maven.google.com/io/micronaut/micronaut-websocket/2.1.3/micronaut-websocket-2.1.3-sources.jar" + "https://repo1.maven.org/maven2/io/micronaut/micronaut-websocket/2.5.12/micronaut-websocket-2.5.12-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/micronaut/micronaut-websocket/2.5.12/micronaut-websocket-2.5.12-sources.jar", + "https://jcenter.bintray.com/io/micronaut/micronaut-websocket/2.5.12/micronaut-websocket-2.5.12-sources.jar", + "https://maven.google.com/io/micronaut/micronaut-websocket/2.5.12/micronaut-websocket-2.5.12-sources.jar" ], - "sha256": "3be543bc00df34722aae6be01dca09a2abb41fd774e84c3f0fdb91086e2fcd21", - "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-websocket/2.1.3/micronaut-websocket-2.1.3-sources.jar" + "sha256": "f646d2e112966938d94df8a80173c9fba477c985b5d768aa4a72c5a9005a669d", + "url": "https://repo1.maven.org/maven2/io/micronaut/micronaut-websocket/2.5.12/micronaut-websocket-2.5.12-sources.jar" }, { "coord": "io.mockk:mockk-agent-api:1.9.1", @@ -10391,11 +11761,8 @@ { "coord": "io.mockk:mockk-dsl-jvm:1.9.1", "dependencies": [ - "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10", "io.mockk:mockk-dsl:1.9.1", - "org.jetbrains.kotlin:kotlin-reflect:1.4.10", - "org.jetbrains.kotlin:kotlin-stdlib:1.4.10", - "org.jetbrains:annotations:13.0" + "org.jetbrains.kotlin:kotlin-reflect:1.4.10" ], "directDependencies": [ "io.mockk:mockk-dsl:1.9.1", @@ -10419,9 +11786,6 @@ "coord": "io.mockk:mockk-dsl-jvm:jar:sources:1.9.1", "dependencies": [ "org.jetbrains.kotlin:kotlin-reflect:jar:sources:1.4.10", - "org.jetbrains:annotations:jar:sources:13.0", - "org.jetbrains.kotlin:kotlin-stdlib-common:jar:sources:1.4.10", - "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.4.10", "io.mockk:mockk-dsl:jar:sources:1.9.1" ], "directDependencies": [ @@ -10482,7 +11846,6 @@ "coord": "io.mockk:mockk:1.9.1", "dependencies": [ "io.mockk:mockk-agent-jvm:1.9.1", - "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10", "net.bytebuddy:byte-buddy:1.9.3", "io.mockk:mockk-agent-common:1.9.1", "io.mockk:mockk-agent-api:1.9.1", @@ -10491,9 +11854,7 @@ "io.mockk:mockk-dsl:1.9.1", "org.jetbrains.kotlin:kotlin-reflect:1.4.10", "io.mockk:mockk-dsl-jvm:1.9.1", - "org.jetbrains.kotlin:kotlin-stdlib:1.4.10", - "net.bytebuddy:byte-buddy-agent:1.9.3", - "org.jetbrains:annotations:13.0" + "net.bytebuddy:byte-buddy-agent:1.9.3" ], "directDependencies": [ "io.mockk:mockk-agent-jvm:1.9.1", @@ -10519,12 +11880,9 @@ "coord": "io.mockk:mockk:jar:sources:1.9.1", "dependencies": [ "org.jetbrains.kotlin:kotlin-reflect:jar:sources:1.4.10", - "org.jetbrains:annotations:jar:sources:13.0", "io.mockk:mockk-agent-api:jar:sources:1.9.1", - "org.jetbrains.kotlin:kotlin-stdlib-common:jar:sources:1.4.10", "io.mockk:mockk-common:jar:sources:1.9.1", "net.bytebuddy:byte-buddy:jar:sources:1.9.3", - "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.4.10", "net.bytebuddy:byte-buddy-agent:jar:sources:1.9.3", "io.mockk:mockk-dsl-jvm:jar:sources:1.9.1", "io.mockk:mockk-agent-jvm:jar:sources:1.9.1", @@ -10553,776 +11911,991 @@ "url": "https://repo1.maven.org/maven2/io/mockk/mockk/1.9.1/mockk-1.9.1-sources.jar" }, { - "coord": "io.netty:netty-buffer:4.1.53.Final", + "coord": "io.netty:netty-buffer:4.1.66.Final", "dependencies": [ - "io.netty:netty-common:4.1.53.Final" + "io.netty:netty-common:4.1.66.Final" ], "directDependencies": [ - "io.netty:netty-common:4.1.53.Final" + "io.netty:netty-common:4.1.66.Final" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-buffer/4.1.53.Final/netty-buffer-4.1.53.Final.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-buffer/4.1.66.Final/netty-buffer-4.1.66.Final.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-buffer/4.1.53.Final/netty-buffer-4.1.53.Final.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-buffer/4.1.53.Final/netty-buffer-4.1.53.Final.jar", - "https://jcenter.bintray.com/io/netty/netty-buffer/4.1.53.Final/netty-buffer-4.1.53.Final.jar", - "https://maven.google.com/io/netty/netty-buffer/4.1.53.Final/netty-buffer-4.1.53.Final.jar" + "https://repo1.maven.org/maven2/io/netty/netty-buffer/4.1.66.Final/netty-buffer-4.1.66.Final.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-buffer/4.1.66.Final/netty-buffer-4.1.66.Final.jar", + "https://jcenter.bintray.com/io/netty/netty-buffer/4.1.66.Final/netty-buffer-4.1.66.Final.jar", + "https://maven.google.com/io/netty/netty-buffer/4.1.66.Final/netty-buffer-4.1.66.Final.jar" ], - "sha256": "63b1346bf9c97ac3c78db84ce4ffe32a69782724d337db7849cefe4e53330f21", - "url": "https://repo1.maven.org/maven2/io/netty/netty-buffer/4.1.53.Final/netty-buffer-4.1.53.Final.jar" + "sha256": "99af46a08546da9e03cb5cd6e3daac624771bb08663f304d60a988e27da59cef", + "url": "https://repo1.maven.org/maven2/io/netty/netty-buffer/4.1.66.Final/netty-buffer-4.1.66.Final.jar" }, { - "coord": "io.netty:netty-buffer:jar:sources:4.1.53.Final", + "coord": "io.netty:netty-buffer:jar:sources:4.1.66.Final", "dependencies": [ - "io.netty:netty-common:jar:sources:4.1.53.Final" + "io.netty:netty-common:jar:sources:4.1.66.Final" ], "directDependencies": [ - "io.netty:netty-common:jar:sources:4.1.53.Final" + "io.netty:netty-common:jar:sources:4.1.66.Final" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", - "com.google.common.html.types:types" + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-buffer/4.1.53.Final/netty-buffer-4.1.53.Final-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-buffer/4.1.66.Final/netty-buffer-4.1.66.Final-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-buffer/4.1.53.Final/netty-buffer-4.1.53.Final-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-buffer/4.1.53.Final/netty-buffer-4.1.53.Final-sources.jar", - "https://jcenter.bintray.com/io/netty/netty-buffer/4.1.53.Final/netty-buffer-4.1.53.Final-sources.jar", - "https://maven.google.com/io/netty/netty-buffer/4.1.53.Final/netty-buffer-4.1.53.Final-sources.jar" + "https://repo1.maven.org/maven2/io/netty/netty-buffer/4.1.66.Final/netty-buffer-4.1.66.Final-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-buffer/4.1.66.Final/netty-buffer-4.1.66.Final-sources.jar", + "https://jcenter.bintray.com/io/netty/netty-buffer/4.1.66.Final/netty-buffer-4.1.66.Final-sources.jar", + "https://maven.google.com/io/netty/netty-buffer/4.1.66.Final/netty-buffer-4.1.66.Final-sources.jar" ], - "sha256": "c466884345d4a3a03de6b74b20c9e6fec78d304a1b6121a11d107c9b8992cbc2", - "url": "https://repo1.maven.org/maven2/io/netty/netty-buffer/4.1.53.Final/netty-buffer-4.1.53.Final-sources.jar" + "sha256": "585cf50a06afdf610bd16cfc3f5676507b36ab33026d4f39e73ccca7b27bcb12", + "url": "https://repo1.maven.org/maven2/io/netty/netty-buffer/4.1.66.Final/netty-buffer-4.1.66.Final-sources.jar" }, { - "coord": "io.netty:netty-codec-http2:4.1.53.Final", + "coord": "io.netty:netty-codec-http2:4.1.66.Final", "dependencies": [ - "io.netty:netty-codec:4.1.53.Final", - "io.netty:netty-handler:4.1.53.Final", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-resolver:4.1.53.Final", - "io.netty:netty-transport:4.1.53.Final", - "io.netty:netty-codec-http:4.1.53.Final" + "io.netty:netty-resolver:4.1.66.Final", + "io.netty:netty-codec-http:4.1.66.Final", + "io.netty:netty-handler:4.1.66.Final", + "io.netty:netty-codec:4.1.66.Final", + "io.netty:netty-transport:4.1.66.Final", + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final" ], "directDependencies": [ - "io.netty:netty-codec:4.1.53.Final", - "io.netty:netty-handler:4.1.53.Final", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-transport:4.1.53.Final", - "io.netty:netty-codec-http:4.1.53.Final" + "io.netty:netty-codec-http:4.1.66.Final", + "io.netty:netty-handler:4.1.66.Final", + "io.netty:netty-codec:4.1.66.Final", + "io.netty:netty-transport:4.1.66.Final", + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", - "com.google.common.html.types:types" + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-codec-http2/4.1.53.Final/netty-codec-http2-4.1.53.Final.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-codec-http2/4.1.66.Final/netty-codec-http2-4.1.66.Final.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-codec-http2/4.1.53.Final/netty-codec-http2-4.1.53.Final.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-codec-http2/4.1.53.Final/netty-codec-http2-4.1.53.Final.jar", - "https://jcenter.bintray.com/io/netty/netty-codec-http2/4.1.53.Final/netty-codec-http2-4.1.53.Final.jar", - "https://maven.google.com/io/netty/netty-codec-http2/4.1.53.Final/netty-codec-http2-4.1.53.Final.jar" + "https://repo1.maven.org/maven2/io/netty/netty-codec-http2/4.1.66.Final/netty-codec-http2-4.1.66.Final.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-codec-http2/4.1.66.Final/netty-codec-http2-4.1.66.Final.jar", + "https://jcenter.bintray.com/io/netty/netty-codec-http2/4.1.66.Final/netty-codec-http2-4.1.66.Final.jar", + "https://maven.google.com/io/netty/netty-codec-http2/4.1.66.Final/netty-codec-http2-4.1.66.Final.jar" ], - "sha256": "6c777f308be5bb53f33925e695dc97f0b88688e2f1937c2c7a3975e977362df4", - "url": "https://repo1.maven.org/maven2/io/netty/netty-codec-http2/4.1.53.Final/netty-codec-http2-4.1.53.Final.jar" + "sha256": "206e8685fc0e75860b348b2925e6a416d1bace4cea82adbb87abcbc8cb91ad2d", + "url": "https://repo1.maven.org/maven2/io/netty/netty-codec-http2/4.1.66.Final/netty-codec-http2-4.1.66.Final.jar" }, { - "coord": "io.netty:netty-codec-http2:jar:sources:4.1.53.Final", + "coord": "io.netty:netty-codec-http2:jar:sources:4.1.66.Final", "dependencies": [ - "io.netty:netty-handler:jar:sources:4.1.53.Final", - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "io.netty:netty-resolver:jar:sources:4.1.53.Final", - "io.netty:netty-codec-http:jar:sources:4.1.53.Final", - "io.netty:netty-codec:jar:sources:4.1.53.Final" + "io.netty:netty-codec-http:jar:sources:4.1.66.Final", + "io.netty:netty-codec:jar:sources:4.1.66.Final", + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "io.netty:netty-transport:jar:sources:4.1.66.Final", + "io.netty:netty-resolver:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final", + "io.netty:netty-handler:jar:sources:4.1.66.Final" ], "directDependencies": [ - "io.netty:netty-handler:jar:sources:4.1.53.Final", - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "io.netty:netty-codec-http:jar:sources:4.1.53.Final", - "io.netty:netty-codec:jar:sources:4.1.53.Final" + "io.netty:netty-codec-http:jar:sources:4.1.66.Final", + "io.netty:netty-codec:jar:sources:4.1.66.Final", + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "io.netty:netty-transport:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final", + "io.netty:netty-handler:jar:sources:4.1.66.Final" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-codec-http2/4.1.53.Final/netty-codec-http2-4.1.53.Final-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-codec-http2/4.1.66.Final/netty-codec-http2-4.1.66.Final-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-codec-http2/4.1.53.Final/netty-codec-http2-4.1.53.Final-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-codec-http2/4.1.53.Final/netty-codec-http2-4.1.53.Final-sources.jar", - "https://jcenter.bintray.com/io/netty/netty-codec-http2/4.1.53.Final/netty-codec-http2-4.1.53.Final-sources.jar", - "https://maven.google.com/io/netty/netty-codec-http2/4.1.53.Final/netty-codec-http2-4.1.53.Final-sources.jar" + "https://repo1.maven.org/maven2/io/netty/netty-codec-http2/4.1.66.Final/netty-codec-http2-4.1.66.Final-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-codec-http2/4.1.66.Final/netty-codec-http2-4.1.66.Final-sources.jar", + "https://jcenter.bintray.com/io/netty/netty-codec-http2/4.1.66.Final/netty-codec-http2-4.1.66.Final-sources.jar", + "https://maven.google.com/io/netty/netty-codec-http2/4.1.66.Final/netty-codec-http2-4.1.66.Final-sources.jar" ], - "sha256": "1094e7939f7f317c8be58f81e1611a41cd8d47a8fadbd83d86ad0898e346ae4e", - "url": "https://repo1.maven.org/maven2/io/netty/netty-codec-http2/4.1.53.Final/netty-codec-http2-4.1.53.Final-sources.jar" + "sha256": "689f9e1c075e65153838e4969966459ebd833f02549ead98cbbcf9201b2f6d7d", + "url": "https://repo1.maven.org/maven2/io/netty/netty-codec-http2/4.1.66.Final/netty-codec-http2-4.1.66.Final-sources.jar" }, { - "coord": "io.netty:netty-codec-http:4.1.53.Final", + "coord": "io.netty:netty-codec-http:4.1.66.Final", "dependencies": [ - "io.netty:netty-codec:4.1.53.Final", - "io.netty:netty-handler:4.1.53.Final", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-resolver:4.1.53.Final", - "io.netty:netty-transport:4.1.53.Final" + "io.netty:netty-resolver:4.1.66.Final", + "io.netty:netty-handler:4.1.66.Final", + "io.netty:netty-codec:4.1.66.Final", + "io.netty:netty-transport:4.1.66.Final", + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final" ], "directDependencies": [ - "io.netty:netty-codec:4.1.53.Final", - "io.netty:netty-handler:4.1.53.Final", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-transport:4.1.53.Final" + "io.netty:netty-handler:4.1.66.Final", + "io.netty:netty-codec:4.1.66.Final", + "io.netty:netty-transport:4.1.66.Final", + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", - "com.google.common.html.types:types" + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-codec-http/4.1.53.Final/netty-codec-http-4.1.53.Final.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-codec-http/4.1.66.Final/netty-codec-http-4.1.66.Final.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-codec-http/4.1.53.Final/netty-codec-http-4.1.53.Final.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-codec-http/4.1.53.Final/netty-codec-http-4.1.53.Final.jar", - "https://jcenter.bintray.com/io/netty/netty-codec-http/4.1.53.Final/netty-codec-http-4.1.53.Final.jar", - "https://maven.google.com/io/netty/netty-codec-http/4.1.53.Final/netty-codec-http-4.1.53.Final.jar" + "https://repo1.maven.org/maven2/io/netty/netty-codec-http/4.1.66.Final/netty-codec-http-4.1.66.Final.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-codec-http/4.1.66.Final/netty-codec-http-4.1.66.Final.jar", + "https://jcenter.bintray.com/io/netty/netty-codec-http/4.1.66.Final/netty-codec-http-4.1.66.Final.jar", + "https://maven.google.com/io/netty/netty-codec-http/4.1.66.Final/netty-codec-http-4.1.66.Final.jar" ], - "sha256": "48a81eec058871c4eaf66670f824a76f403579c53ac7a6840dd6ace9dfc4ad4e", - "url": "https://repo1.maven.org/maven2/io/netty/netty-codec-http/4.1.53.Final/netty-codec-http-4.1.53.Final.jar" + "sha256": "b30b35c69bfe39497aeaf2c7a99bcaea68a9d3eaeee403ecb597cd14f5df4205", + "url": "https://repo1.maven.org/maven2/io/netty/netty-codec-http/4.1.66.Final/netty-codec-http-4.1.66.Final.jar" }, { - "coord": "io.netty:netty-codec-http:jar:sources:4.1.53.Final", + "coord": "io.netty:netty-codec-http:jar:sources:4.1.66.Final", "dependencies": [ - "io.netty:netty-handler:jar:sources:4.1.53.Final", - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "io.netty:netty-resolver:jar:sources:4.1.53.Final", - "io.netty:netty-codec:jar:sources:4.1.53.Final" + "io.netty:netty-codec:jar:sources:4.1.66.Final", + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "io.netty:netty-transport:jar:sources:4.1.66.Final", + "io.netty:netty-resolver:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final", + "io.netty:netty-handler:jar:sources:4.1.66.Final" ], "directDependencies": [ - "io.netty:netty-handler:jar:sources:4.1.53.Final", - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "io.netty:netty-codec:jar:sources:4.1.53.Final" + "io.netty:netty-codec:jar:sources:4.1.66.Final", + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "io.netty:netty-transport:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final", + "io.netty:netty-handler:jar:sources:4.1.66.Final" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-codec-http/4.1.53.Final/netty-codec-http-4.1.53.Final-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-codec-http/4.1.66.Final/netty-codec-http-4.1.66.Final-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-codec-http/4.1.53.Final/netty-codec-http-4.1.53.Final-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-codec-http/4.1.53.Final/netty-codec-http-4.1.53.Final-sources.jar", - "https://jcenter.bintray.com/io/netty/netty-codec-http/4.1.53.Final/netty-codec-http-4.1.53.Final-sources.jar", - "https://maven.google.com/io/netty/netty-codec-http/4.1.53.Final/netty-codec-http-4.1.53.Final-sources.jar" + "https://repo1.maven.org/maven2/io/netty/netty-codec-http/4.1.66.Final/netty-codec-http-4.1.66.Final-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-codec-http/4.1.66.Final/netty-codec-http-4.1.66.Final-sources.jar", + "https://jcenter.bintray.com/io/netty/netty-codec-http/4.1.66.Final/netty-codec-http-4.1.66.Final-sources.jar", + "https://maven.google.com/io/netty/netty-codec-http/4.1.66.Final/netty-codec-http-4.1.66.Final-sources.jar" ], - "sha256": "4e1ab4c470f395be82040c867b8353229468bade85c993d65d7a1bf64f1b2aa6", - "url": "https://repo1.maven.org/maven2/io/netty/netty-codec-http/4.1.53.Final/netty-codec-http-4.1.53.Final-sources.jar" + "sha256": "3b8e009d3841eb9a3c79d7b617edaa7b67dc916d3e122f9bfe9ca772eec97dac", + "url": "https://repo1.maven.org/maven2/io/netty/netty-codec-http/4.1.66.Final/netty-codec-http-4.1.66.Final-sources.jar" }, { - "coord": "io.netty:netty-codec-socks:4.1.53.Final", + "coord": "io.netty:netty-codec-socks:4.1.66.Final", "dependencies": [ - "io.netty:netty-codec:4.1.53.Final", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-resolver:4.1.53.Final", - "io.netty:netty-transport:4.1.53.Final" + "io.netty:netty-resolver:4.1.66.Final", + "io.netty:netty-codec:4.1.66.Final", + "io.netty:netty-transport:4.1.66.Final", + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final" ], "directDependencies": [ - "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-codec:4.1.53.Final", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-transport:4.1.53.Final" + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-codec:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final", + "io.netty:netty-transport:4.1.66.Final" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-codec-socks/4.1.53.Final/netty-codec-socks-4.1.53.Final.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-codec-socks/4.1.66.Final/netty-codec-socks-4.1.66.Final.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-codec-socks/4.1.53.Final/netty-codec-socks-4.1.53.Final.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-codec-socks/4.1.53.Final/netty-codec-socks-4.1.53.Final.jar", - "https://jcenter.bintray.com/io/netty/netty-codec-socks/4.1.53.Final/netty-codec-socks-4.1.53.Final.jar", - "https://maven.google.com/io/netty/netty-codec-socks/4.1.53.Final/netty-codec-socks-4.1.53.Final.jar" + "https://repo1.maven.org/maven2/io/netty/netty-codec-socks/4.1.66.Final/netty-codec-socks-4.1.66.Final.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-codec-socks/4.1.66.Final/netty-codec-socks-4.1.66.Final.jar", + "https://jcenter.bintray.com/io/netty/netty-codec-socks/4.1.66.Final/netty-codec-socks-4.1.66.Final.jar", + "https://maven.google.com/io/netty/netty-codec-socks/4.1.66.Final/netty-codec-socks-4.1.66.Final.jar" ], - "sha256": "8de51d13f48da8eece7df43aa00d6d2923a215668526d8888879fa1534e9aafe", - "url": "https://repo1.maven.org/maven2/io/netty/netty-codec-socks/4.1.53.Final/netty-codec-socks-4.1.53.Final.jar" + "sha256": "f81e9c0c9879769fe5fea1fef54405a9efb0562b4b2f144ab8641819ea38d44b", + "url": "https://repo1.maven.org/maven2/io/netty/netty-codec-socks/4.1.66.Final/netty-codec-socks-4.1.66.Final.jar" }, { - "coord": "io.netty:netty-codec-socks:jar:sources:4.1.53.Final", + "coord": "io.netty:netty-codec-socks:jar:sources:4.1.66.Final", "dependencies": [ - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "io.netty:netty-resolver:jar:sources:4.1.53.Final", - "io.netty:netty-codec:jar:sources:4.1.53.Final" + "io.netty:netty-codec:jar:sources:4.1.66.Final", + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "io.netty:netty-transport:jar:sources:4.1.66.Final", + "io.netty:netty-resolver:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final" ], "directDependencies": [ - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "io.netty:netty-codec:jar:sources:4.1.53.Final", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "io.netty:netty-transport:jar:sources:4.1.53.Final" + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "io.netty:netty-codec:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final", + "io.netty:netty-transport:jar:sources:4.1.66.Final" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-codec-socks/4.1.53.Final/netty-codec-socks-4.1.53.Final-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-codec-socks/4.1.66.Final/netty-codec-socks-4.1.66.Final-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-codec-socks/4.1.53.Final/netty-codec-socks-4.1.53.Final-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-codec-socks/4.1.53.Final/netty-codec-socks-4.1.53.Final-sources.jar", - "https://jcenter.bintray.com/io/netty/netty-codec-socks/4.1.53.Final/netty-codec-socks-4.1.53.Final-sources.jar", - "https://maven.google.com/io/netty/netty-codec-socks/4.1.53.Final/netty-codec-socks-4.1.53.Final-sources.jar" + "https://repo1.maven.org/maven2/io/netty/netty-codec-socks/4.1.66.Final/netty-codec-socks-4.1.66.Final-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-codec-socks/4.1.66.Final/netty-codec-socks-4.1.66.Final-sources.jar", + "https://jcenter.bintray.com/io/netty/netty-codec-socks/4.1.66.Final/netty-codec-socks-4.1.66.Final-sources.jar", + "https://maven.google.com/io/netty/netty-codec-socks/4.1.66.Final/netty-codec-socks-4.1.66.Final-sources.jar" ], - "sha256": "fb70bd164015e318bab02ce035025698afe532e7d3bef24a12277c4605456999", - "url": "https://repo1.maven.org/maven2/io/netty/netty-codec-socks/4.1.53.Final/netty-codec-socks-4.1.53.Final-sources.jar" + "sha256": "6bd023f81ee969c2b25cf679b325aed4602da7b0d19adae07c34c2acfa5838a6", + "url": "https://repo1.maven.org/maven2/io/netty/netty-codec-socks/4.1.66.Final/netty-codec-socks-4.1.66.Final-sources.jar" }, { - "coord": "io.netty:netty-codec:4.1.53.Final", + "coord": "io.netty:netty-codec:4.1.66.Final", "dependencies": [ - "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-transport:4.1.53.Final", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-resolver:4.1.53.Final" + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-transport:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final", + "io.netty:netty-resolver:4.1.66.Final" ], "directDependencies": [ - "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-transport:4.1.53.Final" + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final", + "io.netty:netty-transport:4.1.66.Final" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", - "com.google.common.html.types:types" + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-codec/4.1.53.Final/netty-codec-4.1.53.Final.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-codec/4.1.66.Final/netty-codec-4.1.66.Final.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-codec/4.1.53.Final/netty-codec-4.1.53.Final.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-codec/4.1.53.Final/netty-codec-4.1.53.Final.jar", - "https://jcenter.bintray.com/io/netty/netty-codec/4.1.53.Final/netty-codec-4.1.53.Final.jar", - "https://maven.google.com/io/netty/netty-codec/4.1.53.Final/netty-codec-4.1.53.Final.jar" + "https://repo1.maven.org/maven2/io/netty/netty-codec/4.1.66.Final/netty-codec-4.1.66.Final.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-codec/4.1.66.Final/netty-codec-4.1.66.Final.jar", + "https://jcenter.bintray.com/io/netty/netty-codec/4.1.66.Final/netty-codec-4.1.66.Final.jar", + "https://maven.google.com/io/netty/netty-codec/4.1.66.Final/netty-codec-4.1.66.Final.jar" ], - "sha256": "de3239f3a8d5bdbc8c5049e00d0ab5415d1ccede30cb4e322694e8ffe3e354b5", - "url": "https://repo1.maven.org/maven2/io/netty/netty-codec/4.1.53.Final/netty-codec-4.1.53.Final.jar" + "sha256": "d852eab012b0f06c94a625f96404e32e8fe829708a84e10ae71b2d8dfab47f9e", + "url": "https://repo1.maven.org/maven2/io/netty/netty-codec/4.1.66.Final/netty-codec-4.1.66.Final.jar" }, { - "coord": "io.netty:netty-codec:jar:sources:4.1.53.Final", + "coord": "io.netty:netty-codec:jar:sources:4.1.66.Final", "dependencies": [ - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "io.netty:netty-resolver:jar:sources:4.1.53.Final", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "io.netty:netty-transport:jar:sources:4.1.53.Final" + "io.netty:netty-transport:jar:sources:4.1.66.Final", + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "io.netty:netty-resolver:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final" ], "directDependencies": [ - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "io.netty:netty-transport:jar:sources:4.1.53.Final" + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final", + "io.netty:netty-transport:jar:sources:4.1.66.Final" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-codec/4.1.66.Final/netty-codec-4.1.66.Final-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/io/netty/netty-codec/4.1.66.Final/netty-codec-4.1.66.Final-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-codec/4.1.66.Final/netty-codec-4.1.66.Final-sources.jar", + "https://jcenter.bintray.com/io/netty/netty-codec/4.1.66.Final/netty-codec-4.1.66.Final-sources.jar", + "https://maven.google.com/io/netty/netty-codec/4.1.66.Final/netty-codec-4.1.66.Final-sources.jar" ], + "sha256": "ebd415713e814c6a76c2830298e1c9b3a0f8e3ee57b4f920876abd49d1d5748b", + "url": "https://repo1.maven.org/maven2/io/netty/netty-codec/4.1.66.Final/netty-codec-4.1.66.Final-sources.jar" + }, + { + "coord": "io.netty:netty-common:4.1.66.Final", + "dependencies": [], + "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-codec/4.1.53.Final/netty-codec-4.1.53.Final-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-common/4.1.66.Final/netty-common-4.1.66.Final.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-codec/4.1.53.Final/netty-codec-4.1.53.Final-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-codec/4.1.53.Final/netty-codec-4.1.53.Final-sources.jar", - "https://jcenter.bintray.com/io/netty/netty-codec/4.1.53.Final/netty-codec-4.1.53.Final-sources.jar", - "https://maven.google.com/io/netty/netty-codec/4.1.53.Final/netty-codec-4.1.53.Final-sources.jar" + "https://repo1.maven.org/maven2/io/netty/netty-common/4.1.66.Final/netty-common-4.1.66.Final.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-common/4.1.66.Final/netty-common-4.1.66.Final.jar", + "https://jcenter.bintray.com/io/netty/netty-common/4.1.66.Final/netty-common-4.1.66.Final.jar", + "https://maven.google.com/io/netty/netty-common/4.1.66.Final/netty-common-4.1.66.Final.jar" ], - "sha256": "ed01ae0ffdea809ef2e54e03479c5b6c6a05503f8eca95485496e1a449c33154", - "url": "https://repo1.maven.org/maven2/io/netty/netty-codec/4.1.53.Final/netty-codec-4.1.53.Final-sources.jar" + "sha256": "bf7e66d832e62dd2cbe7802a3d45ece7f8c6de2958e69d85fae0149cb0820459", + "url": "https://repo1.maven.org/maven2/io/netty/netty-common/4.1.66.Final/netty-common-4.1.66.Final.jar" }, { - "coord": "io.netty:netty-common:4.1.53.Final", + "coord": "io.netty:netty-common:jar:sources:4.1.66.Final", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-common/4.1.53.Final/netty-common-4.1.53.Final.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-common/4.1.66.Final/netty-common-4.1.66.Final-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-common/4.1.53.Final/netty-common-4.1.53.Final.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-common/4.1.53.Final/netty-common-4.1.53.Final.jar", - "https://jcenter.bintray.com/io/netty/netty-common/4.1.53.Final/netty-common-4.1.53.Final.jar", - "https://maven.google.com/io/netty/netty-common/4.1.53.Final/netty-common-4.1.53.Final.jar" + "https://repo1.maven.org/maven2/io/netty/netty-common/4.1.66.Final/netty-common-4.1.66.Final-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-common/4.1.66.Final/netty-common-4.1.66.Final-sources.jar", + "https://jcenter.bintray.com/io/netty/netty-common/4.1.66.Final/netty-common-4.1.66.Final-sources.jar", + "https://maven.google.com/io/netty/netty-common/4.1.66.Final/netty-common-4.1.66.Final-sources.jar" ], - "sha256": "0fc9d8ac53dcafa7823498cf668839d3fa164ea2e02eee5946c5f5061f61a5d9", - "url": "https://repo1.maven.org/maven2/io/netty/netty-common/4.1.53.Final/netty-common-4.1.53.Final.jar" + "sha256": "4d12f46d99d239bc6b3175fb04ab8d2d7b083e14447bdf72dfb5770513b29638", + "url": "https://repo1.maven.org/maven2/io/netty/netty-common/4.1.66.Final/netty-common-4.1.66.Final-sources.jar" }, { - "coord": "io.netty:netty-common:jar:sources:4.1.53.Final", - "dependencies": [], - "directDependencies": [], + "coord": "io.netty:netty-handler-proxy:4.1.66.Final", + "dependencies": [ + "io.netty:netty-resolver:4.1.66.Final", + "io.netty:netty-codec-http:4.1.66.Final", + "io.netty:netty-handler:4.1.66.Final", + "io.netty:netty-codec-socks:4.1.66.Final", + "io.netty:netty-codec:4.1.66.Final", + "io.netty:netty-transport:4.1.66.Final", + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final" + ], + "directDependencies": [ + "io.netty:netty-codec-http:4.1.66.Final", + "io.netty:netty-codec-socks:4.1.66.Final", + "io.netty:netty-codec:4.1.66.Final", + "io.netty:netty-transport:4.1.66.Final", + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final" + ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-common/4.1.53.Final/netty-common-4.1.53.Final-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-handler-proxy/4.1.66.Final/netty-handler-proxy-4.1.66.Final.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-common/4.1.53.Final/netty-common-4.1.53.Final-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-common/4.1.53.Final/netty-common-4.1.53.Final-sources.jar", - "https://jcenter.bintray.com/io/netty/netty-common/4.1.53.Final/netty-common-4.1.53.Final-sources.jar", - "https://maven.google.com/io/netty/netty-common/4.1.53.Final/netty-common-4.1.53.Final-sources.jar" + "https://repo1.maven.org/maven2/io/netty/netty-handler-proxy/4.1.66.Final/netty-handler-proxy-4.1.66.Final.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-handler-proxy/4.1.66.Final/netty-handler-proxy-4.1.66.Final.jar", + "https://jcenter.bintray.com/io/netty/netty-handler-proxy/4.1.66.Final/netty-handler-proxy-4.1.66.Final.jar", + "https://maven.google.com/io/netty/netty-handler-proxy/4.1.66.Final/netty-handler-proxy-4.1.66.Final.jar" ], - "sha256": "ad1f8461c0f818af44b0e1297c752cbe169b58aa80721d394e69941900518961", - "url": "https://repo1.maven.org/maven2/io/netty/netty-common/4.1.53.Final/netty-common-4.1.53.Final-sources.jar" + "sha256": "b300d6276cd7f3c35b16ed5f55ec9d6752ccdebac78473e02d607e1c76792247", + "url": "https://repo1.maven.org/maven2/io/netty/netty-handler-proxy/4.1.66.Final/netty-handler-proxy-4.1.66.Final.jar" }, { - "coord": "io.netty:netty-handler-proxy:4.1.53.Final", + "coord": "io.netty:netty-handler-proxy:jar:sources:4.1.66.Final", "dependencies": [ - "io.netty:netty-codec:4.1.53.Final", - "io.netty:netty-handler:4.1.53.Final", - "io.netty:netty-codec-socks:4.1.53.Final", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-resolver:4.1.53.Final", - "io.netty:netty-transport:4.1.53.Final", - "io.netty:netty-codec-http:4.1.53.Final" + "io.netty:netty-codec-http:jar:sources:4.1.66.Final", + "io.netty:netty-codec:jar:sources:4.1.66.Final", + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "io.netty:netty-codec-socks:jar:sources:4.1.66.Final", + "io.netty:netty-transport:jar:sources:4.1.66.Final", + "io.netty:netty-resolver:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final", + "io.netty:netty-handler:jar:sources:4.1.66.Final" ], "directDependencies": [ - "io.netty:netty-codec:4.1.53.Final", - "io.netty:netty-codec-socks:4.1.53.Final", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-transport:4.1.53.Final", - "io.netty:netty-codec-http:4.1.53.Final" + "io.netty:netty-codec-http:jar:sources:4.1.66.Final", + "io.netty:netty-codec:jar:sources:4.1.66.Final", + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "io.netty:netty-codec-socks:jar:sources:4.1.66.Final", + "io.netty:netty-transport:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-handler-proxy/4.1.53.Final/netty-handler-proxy-4.1.53.Final.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-handler-proxy/4.1.66.Final/netty-handler-proxy-4.1.66.Final-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-handler-proxy/4.1.53.Final/netty-handler-proxy-4.1.53.Final.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-handler-proxy/4.1.53.Final/netty-handler-proxy-4.1.53.Final.jar", - "https://jcenter.bintray.com/io/netty/netty-handler-proxy/4.1.53.Final/netty-handler-proxy-4.1.53.Final.jar", - "https://maven.google.com/io/netty/netty-handler-proxy/4.1.53.Final/netty-handler-proxy-4.1.53.Final.jar" + "https://repo1.maven.org/maven2/io/netty/netty-handler-proxy/4.1.66.Final/netty-handler-proxy-4.1.66.Final-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-handler-proxy/4.1.66.Final/netty-handler-proxy-4.1.66.Final-sources.jar", + "https://jcenter.bintray.com/io/netty/netty-handler-proxy/4.1.66.Final/netty-handler-proxy-4.1.66.Final-sources.jar", + "https://maven.google.com/io/netty/netty-handler-proxy/4.1.66.Final/netty-handler-proxy-4.1.66.Final-sources.jar" ], - "sha256": "1f4b5a540f596e45bcde7c53f4c02780aa5e56eea4103eee78b7d104f90c97b1", - "url": "https://repo1.maven.org/maven2/io/netty/netty-handler-proxy/4.1.53.Final/netty-handler-proxy-4.1.53.Final.jar" + "sha256": "891af0fcec320616a50e4aa53e9d243e88425488f5f49488774c22610bf90d85", + "url": "https://repo1.maven.org/maven2/io/netty/netty-handler-proxy/4.1.66.Final/netty-handler-proxy-4.1.66.Final-sources.jar" }, { - "coord": "io.netty:netty-handler-proxy:jar:sources:4.1.53.Final", + "coord": "io.netty:netty-handler:4.1.66.Final", "dependencies": [ - "io.netty:netty-handler:jar:sources:4.1.53.Final", - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "io.netty:netty-codec-socks:jar:sources:4.1.53.Final", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "io.netty:netty-resolver:jar:sources:4.1.53.Final", - "io.netty:netty-codec-http:jar:sources:4.1.53.Final", - "io.netty:netty-codec:jar:sources:4.1.53.Final" + "io.netty:netty-resolver:4.1.66.Final", + "io.netty:netty-codec:4.1.66.Final", + "io.netty:netty-transport:4.1.66.Final", + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final" ], "directDependencies": [ - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "io.netty:netty-codec-socks:jar:sources:4.1.53.Final", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "io.netty:netty-codec-http:jar:sources:4.1.53.Final", - "io.netty:netty-codec:jar:sources:4.1.53.Final" + "io.netty:netty-resolver:4.1.66.Final", + "io.netty:netty-codec:4.1.66.Final", + "io.netty:netty-transport:4.1.66.Final", + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", - "com.google.common.html.types:types" + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-handler-proxy/4.1.53.Final/netty-handler-proxy-4.1.53.Final-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-handler/4.1.66.Final/netty-handler-4.1.66.Final.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-handler-proxy/4.1.53.Final/netty-handler-proxy-4.1.53.Final-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-handler-proxy/4.1.53.Final/netty-handler-proxy-4.1.53.Final-sources.jar", - "https://jcenter.bintray.com/io/netty/netty-handler-proxy/4.1.53.Final/netty-handler-proxy-4.1.53.Final-sources.jar", - "https://maven.google.com/io/netty/netty-handler-proxy/4.1.53.Final/netty-handler-proxy-4.1.53.Final-sources.jar" + "https://repo1.maven.org/maven2/io/netty/netty-handler/4.1.66.Final/netty-handler-4.1.66.Final.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-handler/4.1.66.Final/netty-handler-4.1.66.Final.jar", + "https://jcenter.bintray.com/io/netty/netty-handler/4.1.66.Final/netty-handler-4.1.66.Final.jar", + "https://maven.google.com/io/netty/netty-handler/4.1.66.Final/netty-handler-4.1.66.Final.jar" ], - "sha256": "81ba772b2fa07c71edff2502b9c550780fbde39e50726a60c071bb5058d95f4b", - "url": "https://repo1.maven.org/maven2/io/netty/netty-handler-proxy/4.1.53.Final/netty-handler-proxy-4.1.53.Final-sources.jar" + "sha256": "b67da9271458afb434478d86be9f2736a5be2981e49eda2429a9962b10684f7f", + "url": "https://repo1.maven.org/maven2/io/netty/netty-handler/4.1.66.Final/netty-handler-4.1.66.Final.jar" }, { - "coord": "io.netty:netty-handler:4.1.53.Final", + "coord": "io.netty:netty-handler:jar:sources:4.1.66.Final", "dependencies": [ - "io.netty:netty-codec:4.1.53.Final", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-resolver:4.1.53.Final", - "io.netty:netty-transport:4.1.53.Final" + "io.netty:netty-codec:jar:sources:4.1.66.Final", + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "io.netty:netty-transport:jar:sources:4.1.66.Final", + "io.netty:netty-resolver:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final" ], "directDependencies": [ - "io.netty:netty-codec:4.1.53.Final", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-resolver:4.1.53.Final", - "io.netty:netty-transport:4.1.53.Final" + "io.netty:netty-codec:jar:sources:4.1.66.Final", + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "io.netty:netty-transport:jar:sources:4.1.66.Final", + "io.netty:netty-resolver:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-handler/4.1.53.Final/netty-handler-4.1.53.Final.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-handler/4.1.66.Final/netty-handler-4.1.66.Final-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-handler/4.1.53.Final/netty-handler-4.1.53.Final.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-handler/4.1.53.Final/netty-handler-4.1.53.Final.jar", - "https://jcenter.bintray.com/io/netty/netty-handler/4.1.53.Final/netty-handler-4.1.53.Final.jar", - "https://maven.google.com/io/netty/netty-handler/4.1.53.Final/netty-handler-4.1.53.Final.jar" + "https://repo1.maven.org/maven2/io/netty/netty-handler/4.1.66.Final/netty-handler-4.1.66.Final-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-handler/4.1.66.Final/netty-handler-4.1.66.Final-sources.jar", + "https://jcenter.bintray.com/io/netty/netty-handler/4.1.66.Final/netty-handler-4.1.66.Final-sources.jar", + "https://maven.google.com/io/netty/netty-handler/4.1.66.Final/netty-handler-4.1.66.Final-sources.jar" ], - "sha256": "6b5bc48c787af3d14be2387d2b180bc0f50e7dce7a7eb3afa7e3dd00c68f37e3", - "url": "https://repo1.maven.org/maven2/io/netty/netty-handler/4.1.53.Final/netty-handler-4.1.53.Final.jar" + "sha256": "00097132b743f5fdeafb2670720450883e01efc24b4de33739e10e749ef6f636", + "url": "https://repo1.maven.org/maven2/io/netty/netty-handler/4.1.66.Final/netty-handler-4.1.66.Final-sources.jar" }, { - "coord": "io.netty:netty-handler:jar:sources:4.1.53.Final", + "coord": "io.netty:netty-resolver:4.1.66.Final", "dependencies": [ - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "io.netty:netty-resolver:jar:sources:4.1.53.Final", - "io.netty:netty-codec:jar:sources:4.1.53.Final" + "io.netty:netty-common:4.1.66.Final" ], "directDependencies": [ - "io.netty:netty-transport:jar:sources:4.1.53.Final", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "io.netty:netty-resolver:jar:sources:4.1.53.Final", - "io.netty:netty-codec:jar:sources:4.1.53.Final" + "io.netty:netty-common:4.1.66.Final" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", - "com.google.common.html.types:types" + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-handler/4.1.53.Final/netty-handler-4.1.53.Final-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-resolver/4.1.66.Final/netty-resolver-4.1.66.Final.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-handler/4.1.53.Final/netty-handler-4.1.53.Final-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-handler/4.1.53.Final/netty-handler-4.1.53.Final-sources.jar", - "https://jcenter.bintray.com/io/netty/netty-handler/4.1.53.Final/netty-handler-4.1.53.Final-sources.jar", - "https://maven.google.com/io/netty/netty-handler/4.1.53.Final/netty-handler-4.1.53.Final-sources.jar" + "https://repo1.maven.org/maven2/io/netty/netty-resolver/4.1.66.Final/netty-resolver-4.1.66.Final.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-resolver/4.1.66.Final/netty-resolver-4.1.66.Final.jar", + "https://jcenter.bintray.com/io/netty/netty-resolver/4.1.66.Final/netty-resolver-4.1.66.Final.jar", + "https://maven.google.com/io/netty/netty-resolver/4.1.66.Final/netty-resolver-4.1.66.Final.jar" ], - "sha256": "ed446c541a9159545482837aafe5b6630073086ee7c610f7e2e9266d01e34cbf", - "url": "https://repo1.maven.org/maven2/io/netty/netty-handler/4.1.53.Final/netty-handler-4.1.53.Final-sources.jar" + "sha256": "e89040e9a760f13ec6c05175d633254dd4ea603b204f7a442744485b3c46d1eb", + "url": "https://repo1.maven.org/maven2/io/netty/netty-resolver/4.1.66.Final/netty-resolver-4.1.66.Final.jar" }, { - "coord": "io.netty:netty-resolver:4.1.53.Final", + "coord": "io.netty:netty-resolver:jar:sources:4.1.66.Final", "dependencies": [ - "io.netty:netty-common:4.1.53.Final" + "io.netty:netty-common:jar:sources:4.1.66.Final" ], "directDependencies": [ - "io.netty:netty-common:4.1.53.Final" + "io.netty:netty-common:jar:sources:4.1.66.Final" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-resolver/4.1.53.Final/netty-resolver-4.1.53.Final.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-resolver/4.1.66.Final/netty-resolver-4.1.66.Final-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-resolver/4.1.53.Final/netty-resolver-4.1.53.Final.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-resolver/4.1.53.Final/netty-resolver-4.1.53.Final.jar", - "https://jcenter.bintray.com/io/netty/netty-resolver/4.1.53.Final/netty-resolver-4.1.53.Final.jar", - "https://maven.google.com/io/netty/netty-resolver/4.1.53.Final/netty-resolver-4.1.53.Final.jar" + "https://repo1.maven.org/maven2/io/netty/netty-resolver/4.1.66.Final/netty-resolver-4.1.66.Final-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-resolver/4.1.66.Final/netty-resolver-4.1.66.Final-sources.jar", + "https://jcenter.bintray.com/io/netty/netty-resolver/4.1.66.Final/netty-resolver-4.1.66.Final-sources.jar", + "https://maven.google.com/io/netty/netty-resolver/4.1.66.Final/netty-resolver-4.1.66.Final-sources.jar" ], - "sha256": "93a531bbaa6d3a0652cbc9f13c21ca6cab9d6160c7a6f47c9cf29a5f2c5d6489", - "url": "https://repo1.maven.org/maven2/io/netty/netty-resolver/4.1.53.Final/netty-resolver-4.1.53.Final.jar" + "sha256": "7c08f6575bcb22b71ecf2d0a34177ead2967afb77903c810e621bab9d9450372", + "url": "https://repo1.maven.org/maven2/io/netty/netty-resolver/4.1.66.Final/netty-resolver-4.1.66.Final-sources.jar" }, { - "coord": "io.netty:netty-resolver:jar:sources:4.1.53.Final", - "dependencies": [ - "io.netty:netty-common:jar:sources:4.1.53.Final" - ], - "directDependencies": [ - "io.netty:netty-common:jar:sources:4.1.53.Final" - ], + "coord": "io.netty:netty-tcnative-boringssl-static:2.0.40.Final", + "dependencies": [], + "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", - "com.google.common.html.types:types" + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-resolver/4.1.53.Final/netty-resolver-4.1.53.Final-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-tcnative-boringssl-static/2.0.40.Final/netty-tcnative-boringssl-static-2.0.40.Final.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-resolver/4.1.53.Final/netty-resolver-4.1.53.Final-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-resolver/4.1.53.Final/netty-resolver-4.1.53.Final-sources.jar", - "https://jcenter.bintray.com/io/netty/netty-resolver/4.1.53.Final/netty-resolver-4.1.53.Final-sources.jar", - "https://maven.google.com/io/netty/netty-resolver/4.1.53.Final/netty-resolver-4.1.53.Final-sources.jar" + "https://repo1.maven.org/maven2/io/netty/netty-tcnative-boringssl-static/2.0.40.Final/netty-tcnative-boringssl-static-2.0.40.Final.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-tcnative-boringssl-static/2.0.40.Final/netty-tcnative-boringssl-static-2.0.40.Final.jar", + "https://jcenter.bintray.com/io/netty/netty-tcnative-boringssl-static/2.0.40.Final/netty-tcnative-boringssl-static-2.0.40.Final.jar", + "https://maven.google.com/io/netty/netty-tcnative-boringssl-static/2.0.40.Final/netty-tcnative-boringssl-static-2.0.40.Final.jar" ], - "sha256": "eae9a47f085c8ffba1425f7e3005fb69aa2c28ac545843b6d70a78a4a4190e0e", - "url": "https://repo1.maven.org/maven2/io/netty/netty-resolver/4.1.53.Final/netty-resolver-4.1.53.Final-sources.jar" + "sha256": "343b6072fb223da8614e51e326164abe9bc61e724599e46216ef4b65266e1ab2", + "url": "https://repo1.maven.org/maven2/io/netty/netty-tcnative-boringssl-static/2.0.40.Final/netty-tcnative-boringssl-static-2.0.40.Final.jar" }, { - "coord": "io.netty:netty-transport:4.1.53.Final", - "dependencies": [ - "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-resolver:4.1.53.Final" - ], - "directDependencies": [ - "io.netty:netty-buffer:4.1.53.Final", - "io.netty:netty-common:4.1.53.Final", - "io.netty:netty-resolver:4.1.53.Final" - ], + "coord": "io.netty:netty-tcnative-boringssl-static:jar:sources:2.0.40.Final", + "dependencies": [], + "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-transport/4.1.53.Final/netty-transport-4.1.53.Final.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-tcnative-boringssl-static/2.0.40.Final/netty-tcnative-boringssl-static-2.0.40.Final-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-transport/4.1.53.Final/netty-transport-4.1.53.Final.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-transport/4.1.53.Final/netty-transport-4.1.53.Final.jar", - "https://jcenter.bintray.com/io/netty/netty-transport/4.1.53.Final/netty-transport-4.1.53.Final.jar", - "https://maven.google.com/io/netty/netty-transport/4.1.53.Final/netty-transport-4.1.53.Final.jar" + "https://repo1.maven.org/maven2/io/netty/netty-tcnative-boringssl-static/2.0.40.Final/netty-tcnative-boringssl-static-2.0.40.Final-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-tcnative-boringssl-static/2.0.40.Final/netty-tcnative-boringssl-static-2.0.40.Final-sources.jar", + "https://jcenter.bintray.com/io/netty/netty-tcnative-boringssl-static/2.0.40.Final/netty-tcnative-boringssl-static-2.0.40.Final-sources.jar", + "https://maven.google.com/io/netty/netty-tcnative-boringssl-static/2.0.40.Final/netty-tcnative-boringssl-static-2.0.40.Final-sources.jar" ], - "sha256": "140ff7266eb56762b7bbb9220e82ee27ad1f2f1dc262abcec6d7137cfec5bd00", - "url": "https://repo1.maven.org/maven2/io/netty/netty-transport/4.1.53.Final/netty-transport-4.1.53.Final.jar" + "sha256": "83f357517c0115b59cd89d821f6d683bbb0dcd4fe3ee7df4cd6bee6c902e6e85", + "url": "https://repo1.maven.org/maven2/io/netty/netty-tcnative-boringssl-static/2.0.40.Final/netty-tcnative-boringssl-static-2.0.40.Final-sources.jar" }, { - "coord": "io.netty:netty-transport:jar:sources:4.1.53.Final", - "dependencies": [ - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "io.netty:netty-resolver:jar:sources:4.1.53.Final", - "io.netty:netty-common:jar:sources:4.1.53.Final" + "coord": "io.netty:netty-tcnative:2.0.40.Final", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.template:soy", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "directDependencies": [ - "io.netty:netty-buffer:jar:sources:4.1.53.Final", - "io.netty:netty-common:jar:sources:4.1.53.Final", - "io.netty:netty-resolver:jar:sources:4.1.53.Final" + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-tcnative/2.0.40.Final/netty-tcnative-2.0.40.Final.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/io/netty/netty-tcnative/2.0.40.Final/netty-tcnative-2.0.40.Final.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-tcnative/2.0.40.Final/netty-tcnative-2.0.40.Final.jar", + "https://jcenter.bintray.com/io/netty/netty-tcnative/2.0.40.Final/netty-tcnative-2.0.40.Final.jar", + "https://maven.google.com/io/netty/netty-tcnative/2.0.40.Final/netty-tcnative-2.0.40.Final.jar" ], + "sha256": "ee616f681ae1717453f9f7c6d472448239d01a388f6b15b00127ef56ce73b9fe", + "url": "https://repo1.maven.org/maven2/io/netty/netty-tcnative/2.0.40.Final/netty-tcnative-2.0.40.Final.jar" + }, + { + "coord": "io.netty:netty-tcnative:jar:sources:2.0.40.Final", + "dependencies": [], + "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" + ], + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-tcnative/2.0.40.Final/netty-tcnative-2.0.40.Final-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/io/netty/netty-tcnative/2.0.40.Final/netty-tcnative-2.0.40.Final-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-tcnative/2.0.40.Final/netty-tcnative-2.0.40.Final-sources.jar", + "https://jcenter.bintray.com/io/netty/netty-tcnative/2.0.40.Final/netty-tcnative-2.0.40.Final-sources.jar", + "https://maven.google.com/io/netty/netty-tcnative/2.0.40.Final/netty-tcnative-2.0.40.Final-sources.jar" + ], + "sha256": "cd20ebf5b4af3766ae8c44c605d733f45cb82bb925ccbf37411b27f035a515c1", + "url": "https://repo1.maven.org/maven2/io/netty/netty-tcnative/2.0.40.Final/netty-tcnative-2.0.40.Final-sources.jar" + }, + { + "coord": "io.netty:netty-transport:4.1.66.Final", + "dependencies": [ + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final", + "io.netty:netty-resolver:4.1.66.Final" + ], + "directDependencies": [ + "io.netty:netty-buffer:4.1.66.Final", + "io.netty:netty-common:4.1.66.Final", + "io.netty:netty-resolver:4.1.66.Final" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-transport/4.1.66.Final/netty-transport-4.1.66.Final.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/io/netty/netty-transport/4.1.66.Final/netty-transport-4.1.66.Final.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-transport/4.1.66.Final/netty-transport-4.1.66.Final.jar", + "https://jcenter.bintray.com/io/netty/netty-transport/4.1.66.Final/netty-transport-4.1.66.Final.jar", + "https://maven.google.com/io/netty/netty-transport/4.1.66.Final/netty-transport-4.1.66.Final.jar" + ], + "sha256": "59e0c2f8e55e0c1d5df254226395a865af87a9e11f8b96d2dff92660b1dbcfc1", + "url": "https://repo1.maven.org/maven2/io/netty/netty-transport/4.1.66.Final/netty-transport-4.1.66.Final.jar" + }, + { + "coord": "io.netty:netty-transport:jar:sources:4.1.66.Final", + "dependencies": [ + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "io.netty:netty-resolver:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final" + ], + "directDependencies": [ + "io.netty:netty-buffer:jar:sources:4.1.66.Final", + "io.netty:netty-common:jar:sources:4.1.66.Final", + "io.netty:netty-resolver:jar:sources:4.1.66.Final" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-transport/4.1.53.Final/netty-transport-4.1.53.Final-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/netty/netty-transport/4.1.66.Final/netty-transport-4.1.66.Final-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/netty/netty-transport/4.1.53.Final/netty-transport-4.1.53.Final-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-transport/4.1.53.Final/netty-transport-4.1.53.Final-sources.jar", - "https://jcenter.bintray.com/io/netty/netty-transport/4.1.53.Final/netty-transport-4.1.53.Final-sources.jar", - "https://maven.google.com/io/netty/netty-transport/4.1.53.Final/netty-transport-4.1.53.Final-sources.jar" + "https://repo1.maven.org/maven2/io/netty/netty-transport/4.1.66.Final/netty-transport-4.1.66.Final-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/netty/netty-transport/4.1.66.Final/netty-transport-4.1.66.Final-sources.jar", + "https://jcenter.bintray.com/io/netty/netty-transport/4.1.66.Final/netty-transport-4.1.66.Final-sources.jar", + "https://maven.google.com/io/netty/netty-transport/4.1.66.Final/netty-transport-4.1.66.Final-sources.jar" ], - "sha256": "6937709a206c13dba06373e12f4db348036db611b7524dec6b50144e6c5c910a", - "url": "https://repo1.maven.org/maven2/io/netty/netty-transport/4.1.53.Final/netty-transport-4.1.53.Final-sources.jar" + "sha256": "f01f7aafc144478fafc5c25eeeb665edab6b5dc9d9359fc4a2e1683e80778ccd", + "url": "https://repo1.maven.org/maven2/io/netty/netty-transport/4.1.66.Final/netty-transport-4.1.66.Final-sources.jar" }, { - "coord": "io.opencensus:opencensus-api:0.24.0", + "coord": "io.opencensus:opencensus-api:0.28.0", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.code.findbugs:jsr305", + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/opencensus/opencensus-api/0.24.0/opencensus-api-0.24.0.jar", + "file": "v1/https/repo1.maven.org/maven2/io/opencensus/opencensus-api/0.28.0/opencensus-api-0.28.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/opencensus/opencensus-api/0.24.0/opencensus-api-0.24.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/opencensus/opencensus-api/0.24.0/opencensus-api-0.24.0.jar", - "https://jcenter.bintray.com/io/opencensus/opencensus-api/0.24.0/opencensus-api-0.24.0.jar", - "https://maven.google.com/io/opencensus/opencensus-api/0.24.0/opencensus-api-0.24.0.jar" + "https://repo1.maven.org/maven2/io/opencensus/opencensus-api/0.28.0/opencensus-api-0.28.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/opencensus/opencensus-api/0.28.0/opencensus-api-0.28.0.jar", + "https://jcenter.bintray.com/io/opencensus/opencensus-api/0.28.0/opencensus-api-0.28.0.jar", + "https://maven.google.com/io/opencensus/opencensus-api/0.28.0/opencensus-api-0.28.0.jar" ], - "sha256": "f561b1cc2673844288e596ddf5bb6596868a8472fd2cb8993953fc5c034b2352", - "url": "https://repo1.maven.org/maven2/io/opencensus/opencensus-api/0.24.0/opencensus-api-0.24.0.jar" + "sha256": "0c1723f3f6d3061323845ce8b88b35fdda500812e0a75b8eb5fcc4ad8c871a95", + "url": "https://repo1.maven.org/maven2/io/opencensus/opencensus-api/0.28.0/opencensus-api-0.28.0.jar" }, { - "coord": "io.opencensus:opencensus-api:jar:sources:0.24.0", - "dependencies": [], - "directDependencies": [], + "coord": "io.opencensus:opencensus-api:jar:sources:0.28.0", + "dependencies": [ + "io.grpc:grpc-context:jar:sources:1.38.1" + ], + "directDependencies": [ + "io.grpc:grpc-context:jar:sources:1.38.1" + ], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/opencensus/opencensus-api/0.24.0/opencensus-api-0.24.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/opencensus/opencensus-api/0.28.0/opencensus-api-0.28.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/opencensus/opencensus-api/0.24.0/opencensus-api-0.24.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/opencensus/opencensus-api/0.24.0/opencensus-api-0.24.0-sources.jar", - "https://jcenter.bintray.com/io/opencensus/opencensus-api/0.24.0/opencensus-api-0.24.0-sources.jar", - "https://maven.google.com/io/opencensus/opencensus-api/0.24.0/opencensus-api-0.24.0-sources.jar" + "https://repo1.maven.org/maven2/io/opencensus/opencensus-api/0.28.0/opencensus-api-0.28.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/opencensus/opencensus-api/0.28.0/opencensus-api-0.28.0-sources.jar", + "https://jcenter.bintray.com/io/opencensus/opencensus-api/0.28.0/opencensus-api-0.28.0-sources.jar", + "https://maven.google.com/io/opencensus/opencensus-api/0.28.0/opencensus-api-0.28.0-sources.jar" ], - "sha256": "01693c455b3748a494813ae612e1766c9e804d56561b759d8399270865427bf6", - "url": "https://repo1.maven.org/maven2/io/opencensus/opencensus-api/0.24.0/opencensus-api-0.24.0-sources.jar" + "sha256": "0c6aedc3a87be3b8110eeeb8d7df84d68c3b79831247ddf422d14a2c5faa5fd1", + "url": "https://repo1.maven.org/maven2/io/opencensus/opencensus-api/0.28.0/opencensus-api-0.28.0-sources.jar" }, { - "coord": "io.opencensus:opencensus-contrib-grpc-util:0.24.0", + "coord": "io.opencensus:opencensus-contrib-grpc-util:0.28.0", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.opencensus:opencensus-api", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/opencensus/opencensus-contrib-grpc-util/0.24.0/opencensus-contrib-grpc-util-0.24.0.jar", + "file": "v1/https/repo1.maven.org/maven2/io/opencensus/opencensus-contrib-grpc-util/0.28.0/opencensus-contrib-grpc-util-0.28.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/opencensus/opencensus-contrib-grpc-util/0.24.0/opencensus-contrib-grpc-util-0.24.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/opencensus/opencensus-contrib-grpc-util/0.24.0/opencensus-contrib-grpc-util-0.24.0.jar", - "https://jcenter.bintray.com/io/opencensus/opencensus-contrib-grpc-util/0.24.0/opencensus-contrib-grpc-util-0.24.0.jar", - "https://maven.google.com/io/opencensus/opencensus-contrib-grpc-util/0.24.0/opencensus-contrib-grpc-util-0.24.0.jar" + "https://repo1.maven.org/maven2/io/opencensus/opencensus-contrib-grpc-util/0.28.0/opencensus-contrib-grpc-util-0.28.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/opencensus/opencensus-contrib-grpc-util/0.28.0/opencensus-contrib-grpc-util-0.28.0.jar", + "https://jcenter.bintray.com/io/opencensus/opencensus-contrib-grpc-util/0.28.0/opencensus-contrib-grpc-util-0.28.0.jar", + "https://maven.google.com/io/opencensus/opencensus-contrib-grpc-util/0.28.0/opencensus-contrib-grpc-util-0.28.0.jar" ], - "sha256": "6d3e561866c651d9a7d47f11eef2b35e555a6269924c741a76a057b9c1201c76", - "url": "https://repo1.maven.org/maven2/io/opencensus/opencensus-contrib-grpc-util/0.24.0/opencensus-contrib-grpc-util-0.24.0.jar" + "sha256": "b9168346e6af6593300a1bc27ef74254aa1f24019885938dd8fb852b877d55f0", + "url": "https://repo1.maven.org/maven2/io/opencensus/opencensus-contrib-grpc-util/0.28.0/opencensus-contrib-grpc-util-0.28.0.jar" }, { - "coord": "io.opencensus:opencensus-contrib-grpc-util:jar:sources:0.24.0", + "coord": "io.opencensus:opencensus-contrib-grpc-util:jar:sources:0.28.0", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.opencensus:opencensus-api", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/opencensus/opencensus-contrib-grpc-util/0.24.0/opencensus-contrib-grpc-util-0.24.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/opencensus/opencensus-contrib-grpc-util/0.28.0/opencensus-contrib-grpc-util-0.28.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/opencensus/opencensus-contrib-grpc-util/0.24.0/opencensus-contrib-grpc-util-0.24.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/opencensus/opencensus-contrib-grpc-util/0.24.0/opencensus-contrib-grpc-util-0.24.0-sources.jar", - "https://jcenter.bintray.com/io/opencensus/opencensus-contrib-grpc-util/0.24.0/opencensus-contrib-grpc-util-0.24.0-sources.jar", - "https://maven.google.com/io/opencensus/opencensus-contrib-grpc-util/0.24.0/opencensus-contrib-grpc-util-0.24.0-sources.jar" + "https://repo1.maven.org/maven2/io/opencensus/opencensus-contrib-grpc-util/0.28.0/opencensus-contrib-grpc-util-0.28.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/opencensus/opencensus-contrib-grpc-util/0.28.0/opencensus-contrib-grpc-util-0.28.0-sources.jar", + "https://jcenter.bintray.com/io/opencensus/opencensus-contrib-grpc-util/0.28.0/opencensus-contrib-grpc-util-0.28.0-sources.jar", + "https://maven.google.com/io/opencensus/opencensus-contrib-grpc-util/0.28.0/opencensus-contrib-grpc-util-0.28.0-sources.jar" ], - "sha256": "3a266f155c7d9dc684652919655fd7e27af71152ae8f439231c295927feb370a", - "url": "https://repo1.maven.org/maven2/io/opencensus/opencensus-contrib-grpc-util/0.24.0/opencensus-contrib-grpc-util-0.24.0-sources.jar" + "sha256": "a3c01421b44b6bd27122a46bb49112c0ea045626d03b1f37f79ea219907d18b6", + "url": "https://repo1.maven.org/maven2/io/opencensus/opencensus-contrib-grpc-util/0.28.0/opencensus-contrib-grpc-util-0.28.0-sources.jar" }, { - "coord": "io.opencensus:opencensus-contrib-http-util:0.24.0", - "dependencies": [], - "directDependencies": [], + "coord": "io.opencensus:opencensus-contrib-http-util:0.28.0", + "dependencies": [ + "com.google.guava:guava:30.1.1-android", + "io.grpc:grpc-context:1.38.1", + "io.opencensus:opencensus-api:0.28.0" + ], + "directDependencies": [ + "com.google.guava:guava:30.1.1-android", + "io.opencensus:opencensus-api:0.28.0" + ], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.opencensus:opencensus-api", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/opencensus/opencensus-contrib-http-util/0.24.0/opencensus-contrib-http-util-0.24.0.jar", + "file": "v1/https/repo1.maven.org/maven2/io/opencensus/opencensus-contrib-http-util/0.28.0/opencensus-contrib-http-util-0.28.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/opencensus/opencensus-contrib-http-util/0.24.0/opencensus-contrib-http-util-0.24.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/opencensus/opencensus-contrib-http-util/0.24.0/opencensus-contrib-http-util-0.24.0.jar", - "https://jcenter.bintray.com/io/opencensus/opencensus-contrib-http-util/0.24.0/opencensus-contrib-http-util-0.24.0.jar", - "https://maven.google.com/io/opencensus/opencensus-contrib-http-util/0.24.0/opencensus-contrib-http-util-0.24.0.jar" + "https://repo1.maven.org/maven2/io/opencensus/opencensus-contrib-http-util/0.28.0/opencensus-contrib-http-util-0.28.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/opencensus/opencensus-contrib-http-util/0.28.0/opencensus-contrib-http-util-0.28.0.jar", + "https://jcenter.bintray.com/io/opencensus/opencensus-contrib-http-util/0.28.0/opencensus-contrib-http-util-0.28.0.jar", + "https://maven.google.com/io/opencensus/opencensus-contrib-http-util/0.28.0/opencensus-contrib-http-util-0.28.0.jar" ], - "sha256": "7155273bbb1ed3d477ea33cf19d7bbc0b285ff395f43b29ae576722cf247000f", - "url": "https://repo1.maven.org/maven2/io/opencensus/opencensus-contrib-http-util/0.24.0/opencensus-contrib-http-util-0.24.0.jar" + "sha256": "49c3db2a29f1fdb2f73928cbea969bd1d40fab7cc5bb6273022babd96f7a789b", + "url": "https://repo1.maven.org/maven2/io/opencensus/opencensus-contrib-http-util/0.28.0/opencensus-contrib-http-util-0.28.0.jar" }, { - "coord": "io.opencensus:opencensus-contrib-http-util:jar:sources:0.24.0", - "dependencies": [], - "directDependencies": [], + "coord": "io.opencensus:opencensus-contrib-http-util:jar:sources:0.28.0", + "dependencies": [ + "io.opencensus:opencensus-api:jar:sources:0.28.0" + ], + "directDependencies": [ + "io.opencensus:opencensus-api:jar:sources:0.28.0" + ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", - "io.opencensus:opencensus-api", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/opencensus/opencensus-contrib-http-util/0.24.0/opencensus-contrib-http-util-0.24.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/opencensus/opencensus-contrib-http-util/0.28.0/opencensus-contrib-http-util-0.28.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/opencensus/opencensus-contrib-http-util/0.24.0/opencensus-contrib-http-util-0.24.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/opencensus/opencensus-contrib-http-util/0.24.0/opencensus-contrib-http-util-0.24.0-sources.jar", - "https://jcenter.bintray.com/io/opencensus/opencensus-contrib-http-util/0.24.0/opencensus-contrib-http-util-0.24.0-sources.jar", - "https://maven.google.com/io/opencensus/opencensus-contrib-http-util/0.24.0/opencensus-contrib-http-util-0.24.0-sources.jar" + "https://repo1.maven.org/maven2/io/opencensus/opencensus-contrib-http-util/0.28.0/opencensus-contrib-http-util-0.28.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/opencensus/opencensus-contrib-http-util/0.28.0/opencensus-contrib-http-util-0.28.0-sources.jar", + "https://jcenter.bintray.com/io/opencensus/opencensus-contrib-http-util/0.28.0/opencensus-contrib-http-util-0.28.0-sources.jar", + "https://maven.google.com/io/opencensus/opencensus-contrib-http-util/0.28.0/opencensus-contrib-http-util-0.28.0-sources.jar" ], - "sha256": "bbf2eb991fb641ad9e4e07f4fabfa3222edcded61440663d2886c8be13ca2cae", - "url": "https://repo1.maven.org/maven2/io/opencensus/opencensus-contrib-http-util/0.24.0/opencensus-contrib-http-util-0.24.0-sources.jar" + "sha256": "57991d9ef81499585431a5f9fdbff8b1acabd7826428a514f45d24100887eeaf", + "url": "https://repo1.maven.org/maven2/io/opencensus/opencensus-contrib-http-util/0.28.0/opencensus-contrib-http-util-0.28.0-sources.jar" }, { "coord": "io.opentracing.contrib:opentracing-grpc:0.2.3", @@ -11629,7 +13202,7 @@ "url": "https://repo1.maven.org/maven2/io/perfmark/perfmark-java9/0.23.0/perfmark-java9-0.23.0-sources.jar" }, { - "coord": "io.projectreactor:reactor-core:3.3.6.RELEASE", + "coord": "io.projectreactor:reactor-core:3.3.13.RELEASE", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3" ], @@ -11637,31 +13210,36 @@ "org.reactivestreams:reactive-streams:1.0.3" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/projectreactor/reactor-core/3.3.6.RELEASE/reactor-core-3.3.6.RELEASE.jar", + "file": "v1/https/repo1.maven.org/maven2/io/projectreactor/reactor-core/3.3.13.RELEASE/reactor-core-3.3.13.RELEASE.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/projectreactor/reactor-core/3.3.6.RELEASE/reactor-core-3.3.6.RELEASE.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/projectreactor/reactor-core/3.3.6.RELEASE/reactor-core-3.3.6.RELEASE.jar", - "https://jcenter.bintray.com/io/projectreactor/reactor-core/3.3.6.RELEASE/reactor-core-3.3.6.RELEASE.jar", - "https://maven.google.com/io/projectreactor/reactor-core/3.3.6.RELEASE/reactor-core-3.3.6.RELEASE.jar" + "https://repo1.maven.org/maven2/io/projectreactor/reactor-core/3.3.13.RELEASE/reactor-core-3.3.13.RELEASE.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/projectreactor/reactor-core/3.3.13.RELEASE/reactor-core-3.3.13.RELEASE.jar", + "https://jcenter.bintray.com/io/projectreactor/reactor-core/3.3.13.RELEASE/reactor-core-3.3.13.RELEASE.jar", + "https://maven.google.com/io/projectreactor/reactor-core/3.3.13.RELEASE/reactor-core-3.3.13.RELEASE.jar" ], - "sha256": "1c2c315896e0f213e83b5a7723db8f708c72c4de299b7364721a6a95e8800d42", - "url": "https://repo1.maven.org/maven2/io/projectreactor/reactor-core/3.3.6.RELEASE/reactor-core-3.3.6.RELEASE.jar" + "sha256": "f25a10e4a1c3254d38c199ce7efcfaa9c4d5952e1eecb2be4db528fb955d4535", + "url": "https://repo1.maven.org/maven2/io/projectreactor/reactor-core/3.3.13.RELEASE/reactor-core-3.3.13.RELEASE.jar" }, { - "coord": "io.projectreactor:reactor-core:jar:sources:3.3.6.RELEASE", + "coord": "io.projectreactor:reactor-core:jar:sources:3.3.13.RELEASE", "dependencies": [ "org.reactivestreams:reactive-streams:jar:sources:1.0.3" ], @@ -11669,31 +13247,36 @@ "org.reactivestreams:reactive-streams:jar:sources:1.0.3" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/projectreactor/reactor-core/3.3.6.RELEASE/reactor-core-3.3.6.RELEASE-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/projectreactor/reactor-core/3.3.13.RELEASE/reactor-core-3.3.13.RELEASE-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/projectreactor/reactor-core/3.3.6.RELEASE/reactor-core-3.3.6.RELEASE-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/projectreactor/reactor-core/3.3.6.RELEASE/reactor-core-3.3.6.RELEASE-sources.jar", - "https://jcenter.bintray.com/io/projectreactor/reactor-core/3.3.6.RELEASE/reactor-core-3.3.6.RELEASE-sources.jar", - "https://maven.google.com/io/projectreactor/reactor-core/3.3.6.RELEASE/reactor-core-3.3.6.RELEASE-sources.jar" + "https://repo1.maven.org/maven2/io/projectreactor/reactor-core/3.3.13.RELEASE/reactor-core-3.3.13.RELEASE-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/projectreactor/reactor-core/3.3.13.RELEASE/reactor-core-3.3.13.RELEASE-sources.jar", + "https://jcenter.bintray.com/io/projectreactor/reactor-core/3.3.13.RELEASE/reactor-core-3.3.13.RELEASE-sources.jar", + "https://maven.google.com/io/projectreactor/reactor-core/3.3.13.RELEASE/reactor-core-3.3.13.RELEASE-sources.jar" ], - "sha256": "a64f05b6db878ca95bd07e62b348ee2adf48927f52585e43eb54fa988ccf05a4", - "url": "https://repo1.maven.org/maven2/io/projectreactor/reactor-core/3.3.6.RELEASE/reactor-core-3.3.6.RELEASE-sources.jar" + "sha256": "6d45d689f484b4016f03f12779fbc56ed7e878a6557bdb0b7db0e7aa16f78ce6", + "url": "https://repo1.maven.org/maven2/io/projectreactor/reactor-core/3.3.13.RELEASE/reactor-core-3.3.13.RELEASE-sources.jar" }, { - "coord": "io.reactivex.rxjava2:rxjava:2.2.20", + "coord": "io.reactivex.rxjava2:rxjava:2.2.21", "dependencies": [ "org.reactivestreams:reactive-streams:1.0.3" ], @@ -11701,31 +13284,36 @@ "org.reactivestreams:reactive-streams:1.0.3" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/io/reactivex/rxjava2/rxjava/2.2.20/rxjava-2.2.20.jar", + "file": "v1/https/repo1.maven.org/maven2/io/reactivex/rxjava2/rxjava/2.2.21/rxjava-2.2.21.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/reactivex/rxjava2/rxjava/2.2.20/rxjava-2.2.20.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/reactivex/rxjava2/rxjava/2.2.20/rxjava-2.2.20.jar", - "https://jcenter.bintray.com/io/reactivex/rxjava2/rxjava/2.2.20/rxjava-2.2.20.jar", - "https://maven.google.com/io/reactivex/rxjava2/rxjava/2.2.20/rxjava-2.2.20.jar" + "https://repo1.maven.org/maven2/io/reactivex/rxjava2/rxjava/2.2.21/rxjava-2.2.21.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/reactivex/rxjava2/rxjava/2.2.21/rxjava-2.2.21.jar", + "https://jcenter.bintray.com/io/reactivex/rxjava2/rxjava/2.2.21/rxjava-2.2.21.jar", + "https://maven.google.com/io/reactivex/rxjava2/rxjava/2.2.21/rxjava-2.2.21.jar" ], - "sha256": "62c53b2f62e091eaeacc1af70bd58ef15ee7672bf895e51a4959a5d45abce163", - "url": "https://repo1.maven.org/maven2/io/reactivex/rxjava2/rxjava/2.2.20/rxjava-2.2.20.jar" + "sha256": "59df6541a840018f0f4c899aae4f4c1f4383f4c16feb5268615fbe384d28501c", + "url": "https://repo1.maven.org/maven2/io/reactivex/rxjava2/rxjava/2.2.21/rxjava-2.2.21.jar" }, { - "coord": "io.reactivex.rxjava2:rxjava:jar:sources:2.2.20", + "coord": "io.reactivex.rxjava2:rxjava:jar:sources:2.2.21", "dependencies": [ "org.reactivestreams:reactive-streams:jar:sources:1.0.3" ], @@ -11736,33 +13324,104 @@ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/io/reactivex/rxjava2/rxjava/2.2.20/rxjava-2.2.20-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/io/reactivex/rxjava2/rxjava/2.2.21/rxjava-2.2.21-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/io/reactivex/rxjava2/rxjava/2.2.21/rxjava-2.2.21-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/io/reactivex/rxjava2/rxjava/2.2.21/rxjava-2.2.21-sources.jar", + "https://jcenter.bintray.com/io/reactivex/rxjava2/rxjava/2.2.21/rxjava-2.2.21-sources.jar", + "https://maven.google.com/io/reactivex/rxjava2/rxjava/2.2.21/rxjava-2.2.21-sources.jar" + ], + "sha256": "b39c62f4b1860a962a55fb29eeccd332f3434cc3a8224ec51b6f6bf0fd17ac6f", + "url": "https://repo1.maven.org/maven2/io/reactivex/rxjava2/rxjava/2.2.21/rxjava-2.2.21-sources.jar" + }, + { + "coord": "jakarta.inject:jakarta.inject-api:2.0.0", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.template:soy", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" + ], + "file": "v1/https/repo1.maven.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/jakarta.inject-api-2.0.0.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/jakarta.inject-api-2.0.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/jakarta/inject/jakarta.inject-api/2.0.0/jakarta.inject-api-2.0.0.jar", + "https://jcenter.bintray.com/jakarta/inject/jakarta.inject-api/2.0.0/jakarta.inject-api-2.0.0.jar", + "https://maven.google.com/jakarta/inject/jakarta.inject-api/2.0.0/jakarta.inject-api-2.0.0.jar" + ], + "sha256": "842ccf3b892aca3fbd384c99d1516a8b7c448c55cee560ab2724488016198706", + "url": "https://repo1.maven.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/jakarta.inject-api-2.0.0.jar" + }, + { + "coord": "jakarta.inject:jakarta.inject-api:jar:sources:2.0.0", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "io.grpc:grpc-grpclb", + "com.google.template:soy", + "io.grpc:grpc-context", + "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", + "io.grpc:grpc-okhttp", + "io.grpc:grpc-protobuf-lite", + "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", + "io.grpc:grpc-auth", + "io.grpc:grpc-protobuf", + "io.grpc:grpc-stub", + "com.google.common.html.types:types", + "io.grpc:grpc-netty", + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" + ], + "file": "v1/https/repo1.maven.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/jakarta.inject-api-2.0.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/io/reactivex/rxjava2/rxjava/2.2.20/rxjava-2.2.20-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/io/reactivex/rxjava2/rxjava/2.2.20/rxjava-2.2.20-sources.jar", - "https://jcenter.bintray.com/io/reactivex/rxjava2/rxjava/2.2.20/rxjava-2.2.20-sources.jar", - "https://maven.google.com/io/reactivex/rxjava2/rxjava/2.2.20/rxjava-2.2.20-sources.jar" + "https://repo1.maven.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/jakarta.inject-api-2.0.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/jakarta/inject/jakarta.inject-api/2.0.0/jakarta.inject-api-2.0.0-sources.jar", + "https://jcenter.bintray.com/jakarta/inject/jakarta.inject-api/2.0.0/jakarta.inject-api-2.0.0-sources.jar", + "https://maven.google.com/jakarta/inject/jakarta.inject-api/2.0.0/jakarta.inject-api-2.0.0-sources.jar" ], - "sha256": "02fa9fab3c96eb46e14e6eafd43a5b82ba804f7bd89e0b5d473aad8f47f17e5e", - "url": "https://repo1.maven.org/maven2/io/reactivex/rxjava2/rxjava/2.2.20/rxjava-2.2.20-sources.jar" + "sha256": "98e36f5a1df78bab8b4bddf5152c687b0b5ba08ef00dece26eef66f08ab2906e", + "url": "https://repo1.maven.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/jakarta.inject-api-2.0.0-sources.jar" }, { "coord": "jakarta.transaction:jakarta.transaction-api:1.3.3", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], "file": "v1/https/repo1.maven.org/maven2/jakarta/transaction/jakarta.transaction-api/1.3.3/jakarta.transaction-api-1.3.3.jar", "mirror_urls": [ @@ -11779,18 +13438,23 @@ "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], "file": "v1/https/repo1.maven.org/maven2/jakarta/transaction/jakarta.transaction-api/1.3.3/jakarta.transaction-api-1.3.3-sources.jar", "mirror_urls": [ @@ -11826,17 +13490,7 @@ "directDependencies": [], "exclusions": [ "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], "file": "v1/https/repo1.maven.org/maven2/javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2-sources.jar", "mirror_urls": [ @@ -11920,6 +13574,50 @@ "sha256": "78fc8207d394c91e329be90fc051e98180bd2a35c14e0df73f66a653c7aea19f", "url": "https://repo1.maven.org/maven2/javax/validation/validation-api/2.0.1.Final/validation-api-2.0.1.Final-sources.jar" }, + { + "coord": "junit:junit:4.13.2", + "dependencies": [ + "org.hamcrest:hamcrest-core:1.3" + ], + "directDependencies": [ + "org.hamcrest:hamcrest-core:1.3" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/junit/junit/4.13.2/junit-4.13.2.jar", + "https://jcenter.bintray.com/junit/junit/4.13.2/junit-4.13.2.jar", + "https://maven.google.com/junit/junit/4.13.2/junit-4.13.2.jar" + ], + "sha256": "8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3", + "url": "https://repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2.jar" + }, + { + "coord": "junit:junit:jar:sources:4.13.2", + "dependencies": [ + "org.hamcrest:hamcrest-core:jar:sources:1.3" + ], + "directDependencies": [ + "org.hamcrest:hamcrest-core:jar:sources:1.3" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/junit/junit/4.13.2/junit-4.13.2-sources.jar", + "https://jcenter.bintray.com/junit/junit/4.13.2/junit-4.13.2-sources.jar", + "https://maven.google.com/junit/junit/4.13.2/junit-4.13.2-sources.jar" + ], + "sha256": "34181df6482d40ea4c046b063cb53c7ffae94bdf1b1d62695bdf3adf9dea7e3a", + "url": "https://repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2-sources.jar" + }, { "coord": "net.bytebuddy:byte-buddy-agent:1.9.3", "dependencies": [], @@ -11992,6 +13690,42 @@ "sha256": "f72e7e29cc321424af33aef71c5776e76a83b2c6b80414ad3602083fa90b4ae6", "url": "https://repo1.maven.org/maven2/net/bytebuddy/byte-buddy/1.9.3/byte-buddy-1.9.3-sources.jar" }, + { + "coord": "net.java.dev.jna:jna:5.8.0", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/net/java/dev/jna/jna/5.8.0/jna-5.8.0.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.8.0/jna-5.8.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/net/java/dev/jna/jna/5.8.0/jna-5.8.0.jar", + "https://jcenter.bintray.com/net/java/dev/jna/jna/5.8.0/jna-5.8.0.jar", + "https://maven.google.com/net/java/dev/jna/jna/5.8.0/jna-5.8.0.jar" + ], + "sha256": "930273cc1c492f25661ea62413a6da3fd7f6e01bf1c4dcc0817fc8696a7b07ac", + "url": "https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.8.0/jna-5.8.0.jar" + }, + { + "coord": "net.java.dev.jna:jna:jar:sources:5.8.0", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/net/java/dev/jna/jna/5.8.0/jna-5.8.0-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.8.0/jna-5.8.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/net/java/dev/jna/jna/5.8.0/jna-5.8.0-sources.jar", + "https://jcenter.bintray.com/net/java/dev/jna/jna/5.8.0/jna-5.8.0-sources.jar", + "https://maven.google.com/net/java/dev/jna/jna/5.8.0/jna-5.8.0-sources.jar" + ], + "sha256": "4616cf507a47462ea5ec04654778d2e41624e21db7f41188cc553e8dce1123da", + "url": "https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.8.0/jna-5.8.0-sources.jar" + }, { "coord": "net.sourceforge.argparse4j:argparse4j:0.8.1", "dependencies": [], @@ -12029,125 +13763,108 @@ "url": "https://repo1.maven.org/maven2/net/sourceforge/argparse4j/argparse4j/0.8.1/argparse4j-0.8.1-sources.jar" }, { - "coord": "org.apache.commons:commons-exec:1.3", + "coord": "org.apache.commons:commons-compress:1.20", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/commons/commons-exec/1.3/commons-exec-1.3.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/commons/commons-compress/1.20/commons-compress-1.20.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/apache/commons/commons-exec/1.3/commons-exec-1.3.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/apache/commons/commons-exec/1.3/commons-exec-1.3.jar", - "https://jcenter.bintray.com/org/apache/commons/commons-exec/1.3/commons-exec-1.3.jar", - "https://maven.google.com/org/apache/commons/commons-exec/1.3/commons-exec-1.3.jar" + "https://repo1.maven.org/maven2/org/apache/commons/commons-compress/1.20/commons-compress-1.20.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/apache/commons/commons-compress/1.20/commons-compress-1.20.jar", + "https://jcenter.bintray.com/org/apache/commons/commons-compress/1.20/commons-compress-1.20.jar", + "https://maven.google.com/org/apache/commons/commons-compress/1.20/commons-compress-1.20.jar" ], - "sha256": "cb49812dc1bfb0ea4f20f398bcae1a88c6406e213e67f7524fb10d4f8ad9347b", - "url": "https://repo1.maven.org/maven2/org/apache/commons/commons-exec/1.3/commons-exec-1.3.jar" + "sha256": "0aeb625c948c697ea7b205156e112363b59ed5e2551212cd4e460bdb72c7c06e", + "url": "https://repo1.maven.org/maven2/org/apache/commons/commons-compress/1.20/commons-compress-1.20.jar" }, { - "coord": "org.apache.commons:commons-exec:jar:sources:1.3", + "coord": "org.apache.commons:commons-compress:jar:sources:1.20", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/commons/commons-exec/1.3/commons-exec-1.3-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/commons/commons-compress/1.20/commons-compress-1.20-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/apache/commons/commons-exec/1.3/commons-exec-1.3-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/apache/commons/commons-exec/1.3/commons-exec-1.3-sources.jar", - "https://jcenter.bintray.com/org/apache/commons/commons-exec/1.3/commons-exec-1.3-sources.jar", - "https://maven.google.com/org/apache/commons/commons-exec/1.3/commons-exec-1.3-sources.jar" + "https://repo1.maven.org/maven2/org/apache/commons/commons-compress/1.20/commons-compress-1.20-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/apache/commons/commons-compress/1.20/commons-compress-1.20-sources.jar", + "https://jcenter.bintray.com/org/apache/commons/commons-compress/1.20/commons-compress-1.20-sources.jar", + "https://maven.google.com/org/apache/commons/commons-compress/1.20/commons-compress-1.20-sources.jar" ], - "sha256": "c121d8e70010092bafc56f358e7259ac484653db595aafea1e67a040f51aea66", - "url": "https://repo1.maven.org/maven2/org/apache/commons/commons-exec/1.3/commons-exec-1.3-sources.jar" + "sha256": "0eb5d7f270c2fccdab31daa5f7091b038ad0099b29885040604d66e07fd46a18", + "url": "https://repo1.maven.org/maven2/org/apache/commons/commons-compress/1.20/commons-compress-1.20-sources.jar" }, { - "coord": "org.apache.commons:commons-lang3:3.5", + "coord": "org.apache.commons:commons-exec:1.3", "dependencies": [], "directDependencies": [], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/commons/commons-exec/1.3/commons-exec-1.3.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar", - "https://jcenter.bintray.com/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar", - "https://maven.google.com/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar" + "https://repo1.maven.org/maven2/org/apache/commons/commons-exec/1.3/commons-exec-1.3.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/apache/commons/commons-exec/1.3/commons-exec-1.3.jar", + "https://jcenter.bintray.com/org/apache/commons/commons-exec/1.3/commons-exec-1.3.jar", + "https://maven.google.com/org/apache/commons/commons-exec/1.3/commons-exec-1.3.jar" ], - "sha256": "8ac96fc686512d777fca85e144f196cd7cfe0c0aec23127229497d1a38ff651c", - "url": "https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar" + "sha256": "cb49812dc1bfb0ea4f20f398bcae1a88c6406e213e67f7524fb10d4f8ad9347b", + "url": "https://repo1.maven.org/maven2/org/apache/commons/commons-exec/1.3/commons-exec-1.3.jar" }, { - "coord": "org.apache.commons:commons-lang3:jar:sources:3.5", + "coord": "org.apache.commons:commons-exec:jar:sources:1.3", "dependencies": [], "directDependencies": [], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/commons/commons-exec/1.3/commons-exec-1.3-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5-sources.jar", - "https://jcenter.bintray.com/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5-sources.jar", - "https://maven.google.com/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5-sources.jar" + "https://repo1.maven.org/maven2/org/apache/commons/commons-exec/1.3/commons-exec-1.3-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/apache/commons/commons-exec/1.3/commons-exec-1.3-sources.jar", + "https://jcenter.bintray.com/org/apache/commons/commons-exec/1.3/commons-exec-1.3-sources.jar", + "https://maven.google.com/org/apache/commons/commons-exec/1.3/commons-exec-1.3-sources.jar" ], - "sha256": "1f7adeee4d483a6ca8d479d522cb2b07e39d976b758f3c2b6e1a0fed20dcbd2d", - "url": "https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5-sources.jar" + "sha256": "c121d8e70010092bafc56f358e7259ac484653db595aafea1e67a040f51aea66", + "url": "https://repo1.maven.org/maven2/org/apache/commons/commons-exec/1.3/commons-exec-1.3-sources.jar" }, { "coord": "org.apache.httpcomponents:httpclient:4.5.13", "dependencies": [ - "org.apache.httpcomponents:httpcore:4.4.13", + "commons-codec:commons-codec:1.15", "commons-logging:commons-logging:1.2", - "commons-codec:commons-codec:1.11" + "org.apache.httpcomponents:httpcore:4.4.14" ], "directDependencies": [ - "commons-codec:commons-codec:1.11", + "commons-codec:commons-codec:1.15", "commons-logging:commons-logging:1.2", - "org.apache.httpcomponents:httpcore:4.4.13" + "org.apache.httpcomponents:httpcore:4.4.14" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], "file": "v1/https/repo1.maven.org/maven2/org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.jar", "mirror_urls": [ @@ -12162,29 +13879,34 @@ { "coord": "org.apache.httpcomponents:httpclient:jar:sources:4.5.13", "dependencies": [ - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13", - "commons-codec:commons-codec:jar:sources:1.11", + "org.apache.httpcomponents:httpcore:jar:sources:4.4.14", + "commons-codec:commons-codec:jar:sources:1.15", "commons-logging:commons-logging:jar:sources:1.2" ], "directDependencies": [ - "commons-codec:commons-codec:jar:sources:1.11", + "commons-codec:commons-codec:jar:sources:1.15", "commons-logging:commons-logging:jar:sources:1.2", - "org.apache.httpcomponents:httpcore:jar:sources:4.4.13" + "org.apache.httpcomponents:httpcore:jar:sources:4.4.14" ], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], "file": "v1/https/repo1.maven.org/maven2/org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13-sources.jar", "mirror_urls": [ @@ -12197,62 +13919,56 @@ "url": "https://repo1.maven.org/maven2/org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13-sources.jar" }, { - "coord": "org.apache.httpcomponents:httpcore:4.4.13", + "coord": "org.apache.httpcomponents:httpcore:4.4.14", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.jar", - "https://jcenter.bintray.com/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.jar", - "https://maven.google.com/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.jar" + "https://repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14.jar", + "https://jcenter.bintray.com/org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14.jar", + "https://maven.google.com/org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14.jar" ], - "sha256": "e06e89d40943245fcfa39ec537cdbfce3762aecde8f9c597780d2b00c2b43424", - "url": "https://repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.jar" + "sha256": "f956209e450cb1d0c51776dfbd23e53e9dd8db9a1298ed62b70bf0944ba63b28", + "url": "https://repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14.jar" }, { - "coord": "org.apache.httpcomponents:httpcore:jar:sources:4.4.13", + "coord": "org.apache.httpcomponents:httpcore:jar:sources:4.4.14", "dependencies": [], "directDependencies": [], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13-sources.jar", - "https://jcenter.bintray.com/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13-sources.jar", - "https://maven.google.com/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13-sources.jar" + "https://repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14-sources.jar", + "https://jcenter.bintray.com/org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14-sources.jar", + "https://maven.google.com/org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14-sources.jar" ], - "sha256": "c0418a6ee8c32e9de37e4ba515e9562a2acc6a36b684b618fee56d41b81ef2a9", - "url": "https://repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13-sources.jar" + "sha256": "7a7f721f3bd1b09de508bf6811931dd9527e4fa3d643d5ccc7cef3fa2d4ee3df", + "url": "https://repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.14/httpcore-4.4.14-sources.jar" }, { "coord": "org.apache.tomcat:annotations-api:6.0.53", @@ -12273,59 +13989,48 @@ "url": "https://repo1.maven.org/maven2/org/apache/tomcat/annotations-api/6.0.53/annotations-api-6.0.53.jar" }, { - "coord": "org.apiguardian:apiguardian-api:1.1.0", + "coord": "org.apiguardian:apiguardian-api:1.1.1", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.1/apiguardian-api-1.1.1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar", - "https://jcenter.bintray.com/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar", - "https://maven.google.com/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar" + "https://repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.1/apiguardian-api-1.1.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/apiguardian/apiguardian-api/1.1.1/apiguardian-api-1.1.1.jar", + "https://jcenter.bintray.com/org/apiguardian/apiguardian-api/1.1.1/apiguardian-api-1.1.1.jar", + "https://maven.google.com/org/apiguardian/apiguardian-api/1.1.1/apiguardian-api-1.1.1.jar" ], - "sha256": "a9aae9ff8ae3e17a2a18f79175e82b16267c246fbbd3ca9dfbbb290b08dcfdd4", - "url": "https://repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar" + "sha256": "fc68f0d28633caccf3980fdf1e99628fba9c49424ee56dc685cd8b4d2a9fefde", + "url": "https://repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.1/apiguardian-api-1.1.1.jar" }, { - "coord": "org.apiguardian:apiguardian-api:jar:sources:1.1.0", + "coord": "org.apiguardian:apiguardian-api:jar:sources:1.1.1", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.1/apiguardian-api-1.1.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0-sources.jar", - "https://jcenter.bintray.com/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0-sources.jar", - "https://maven.google.com/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0-sources.jar" + "https://repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.1/apiguardian-api-1.1.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/apiguardian/apiguardian-api/1.1.1/apiguardian-api-1.1.1-sources.jar", + "https://jcenter.bintray.com/org/apiguardian/apiguardian-api/1.1.1/apiguardian-api-1.1.1-sources.jar", + "https://maven.google.com/org/apiguardian/apiguardian-api/1.1.1/apiguardian-api-1.1.1-sources.jar" ], - "sha256": "d39a5bb9b4b57e7584ac81f714ba8ef73b08ca462a48d7828d4a93fa5013fe1e", - "url": "https://repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0-sources.jar" + "sha256": "32fdb6401e4fd2d8f12b0970884b2936d4ef2f95cc574809b984ce75d439ea7c", + "url": "https://repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.1/apiguardian-api-1.1.1-sources.jar" }, { "coord": "org.checkerframework:checker-compat-qual:2.5.5", "dependencies": [], "directDependencies": [], "exclusions": [ - "com.google.guava:guava", "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], "file": "v1/https/repo1.maven.org/maven2/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar", "mirror_urls": [ @@ -12340,21 +14045,10 @@ { "coord": "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", "dependencies": [], - "directDependencies": [], - "exclusions": [ - "com.google.guava:guava", - "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "directDependencies": [], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" ], "file": "v1/https/repo1.maven.org/maven2/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5-sources.jar", "mirror_urls": [ @@ -12367,115 +14061,100 @@ "url": "https://repo1.maven.org/maven2/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5-sources.jar" }, { - "coord": "org.checkerframework:checker-qual:3.4.1", + "coord": "org.checkerframework:checker-qual:3.13.0", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/checkerframework/checker-qual/3.4.1/checker-qual-3.4.1.jar", + "file": "v1/https/repo1.maven.org/maven2/org/checkerframework/checker-qual/3.13.0/checker-qual-3.13.0.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/checkerframework/checker-qual/3.4.1/checker-qual-3.4.1.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/checkerframework/checker-qual/3.4.1/checker-qual-3.4.1.jar", - "https://jcenter.bintray.com/org/checkerframework/checker-qual/3.4.1/checker-qual-3.4.1.jar", - "https://maven.google.com/org/checkerframework/checker-qual/3.4.1/checker-qual-3.4.1.jar" + "https://repo1.maven.org/maven2/org/checkerframework/checker-qual/3.13.0/checker-qual-3.13.0.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/checkerframework/checker-qual/3.13.0/checker-qual-3.13.0.jar", + "https://jcenter.bintray.com/org/checkerframework/checker-qual/3.13.0/checker-qual-3.13.0.jar", + "https://maven.google.com/org/checkerframework/checker-qual/3.13.0/checker-qual-3.13.0.jar" ], - "sha256": "bce5c887460542d69c0ffce05919fef8f56f9964a1505a99f6ae69a58351507e", - "url": "https://repo1.maven.org/maven2/org/checkerframework/checker-qual/3.4.1/checker-qual-3.4.1.jar" + "sha256": "3ea0dcd73b4d6cb2fb34bd7ed4dad6db327a01ebad7db05eb7894076b3d64491", + "url": "https://repo1.maven.org/maven2/org/checkerframework/checker-qual/3.13.0/checker-qual-3.13.0.jar" }, { - "coord": "org.checkerframework:checker-qual:jar:sources:3.4.1", + "coord": "org.checkerframework:checker-qual:jar:sources:3.13.0", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/checkerframework/checker-qual/3.4.1/checker-qual-3.4.1-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/org/checkerframework/checker-qual/3.13.0/checker-qual-3.13.0-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/checkerframework/checker-qual/3.4.1/checker-qual-3.4.1-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/checkerframework/checker-qual/3.4.1/checker-qual-3.4.1-sources.jar", - "https://jcenter.bintray.com/org/checkerframework/checker-qual/3.4.1/checker-qual-3.4.1-sources.jar", - "https://maven.google.com/org/checkerframework/checker-qual/3.4.1/checker-qual-3.4.1-sources.jar" + "https://repo1.maven.org/maven2/org/checkerframework/checker-qual/3.13.0/checker-qual-3.13.0-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/checkerframework/checker-qual/3.13.0/checker-qual-3.13.0-sources.jar", + "https://jcenter.bintray.com/org/checkerframework/checker-qual/3.13.0/checker-qual-3.13.0-sources.jar", + "https://maven.google.com/org/checkerframework/checker-qual/3.13.0/checker-qual-3.13.0-sources.jar" ], - "sha256": "66e4ad03e733e26a89cc7d4d48bc78457af3a1e8ddc53017ce732a624da1eb83", - "url": "https://repo1.maven.org/maven2/org/checkerframework/checker-qual/3.4.1/checker-qual-3.4.1-sources.jar" + "sha256": "52fd5b908ed38b2c543fac371c2192ff2896fec0f3ddea29f23b5db117a7ea6e", + "url": "https://repo1.maven.org/maven2/org/checkerframework/checker-qual/3.13.0/checker-qual-3.13.0-sources.jar" }, { - "coord": "org.codehaus.mojo:animal-sniffer-annotations:1.19", + "coord": "org.codehaus.mojo:animal-sniffer-annotations:1.20", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/codehaus/mojo/animal-sniffer-annotations/1.19/animal-sniffer-annotations-1.19.jar", + "file": "v1/https/repo1.maven.org/maven2/org/codehaus/mojo/animal-sniffer-annotations/1.20/animal-sniffer-annotations-1.20.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/codehaus/mojo/animal-sniffer-annotations/1.19/animal-sniffer-annotations-1.19.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/codehaus/mojo/animal-sniffer-annotations/1.19/animal-sniffer-annotations-1.19.jar", - "https://jcenter.bintray.com/org/codehaus/mojo/animal-sniffer-annotations/1.19/animal-sniffer-annotations-1.19.jar", - "https://maven.google.com/org/codehaus/mojo/animal-sniffer-annotations/1.19/animal-sniffer-annotations-1.19.jar" + "https://repo1.maven.org/maven2/org/codehaus/mojo/animal-sniffer-annotations/1.20/animal-sniffer-annotations-1.20.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/codehaus/mojo/animal-sniffer-annotations/1.20/animal-sniffer-annotations-1.20.jar", + "https://jcenter.bintray.com/org/codehaus/mojo/animal-sniffer-annotations/1.20/animal-sniffer-annotations-1.20.jar", + "https://maven.google.com/org/codehaus/mojo/animal-sniffer-annotations/1.20/animal-sniffer-annotations-1.20.jar" ], - "sha256": "e67ec27ceeaf13ab5d54cf5fdbcc544c41b4db8d02d9f006678cca2c7c13ee9d", - "url": "https://repo1.maven.org/maven2/org/codehaus/mojo/animal-sniffer-annotations/1.19/animal-sniffer-annotations-1.19.jar" + "sha256": "bedd44dfca2dc2b8f5c08cd1d6f0e0ce74094ec67781260968e03fc0e77522ac", + "url": "https://repo1.maven.org/maven2/org/codehaus/mojo/animal-sniffer-annotations/1.20/animal-sniffer-annotations-1.20.jar" }, { - "coord": "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.19", + "coord": "org.codehaus.mojo:animal-sniffer-annotations:jar:sources:1.20", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/codehaus/mojo/animal-sniffer-annotations/1.19/animal-sniffer-annotations-1.19-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/org/codehaus/mojo/animal-sniffer-annotations/1.20/animal-sniffer-annotations-1.20-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/codehaus/mojo/animal-sniffer-annotations/1.19/animal-sniffer-annotations-1.19-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/codehaus/mojo/animal-sniffer-annotations/1.19/animal-sniffer-annotations-1.19-sources.jar", - "https://jcenter.bintray.com/org/codehaus/mojo/animal-sniffer-annotations/1.19/animal-sniffer-annotations-1.19-sources.jar", - "https://maven.google.com/org/codehaus/mojo/animal-sniffer-annotations/1.19/animal-sniffer-annotations-1.19-sources.jar" + "https://repo1.maven.org/maven2/org/codehaus/mojo/animal-sniffer-annotations/1.20/animal-sniffer-annotations-1.20-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/codehaus/mojo/animal-sniffer-annotations/1.20/animal-sniffer-annotations-1.20-sources.jar", + "https://jcenter.bintray.com/org/codehaus/mojo/animal-sniffer-annotations/1.20/animal-sniffer-annotations-1.20-sources.jar", + "https://maven.google.com/org/codehaus/mojo/animal-sniffer-annotations/1.20/animal-sniffer-annotations-1.20-sources.jar" ], - "sha256": "da2e67cba66639197d23c1976e6c27d0967ad4dc69e6228b934e05a0d39e2991", - "url": "https://repo1.maven.org/maven2/org/codehaus/mojo/animal-sniffer-annotations/1.19/animal-sniffer-annotations-1.19-sources.jar" + "sha256": "cabb3c62262175f026508bfc8d38b83999163922581a538292edce8f0473ad6d", + "url": "https://repo1.maven.org/maven2/org/codehaus/mojo/animal-sniffer-annotations/1.20/animal-sniffer-annotations-1.20-sources.jar" }, { "coord": "org.conscrypt:conscrypt-openjdk-uber:2.5.1", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], "file": "v1/https/repo1.maven.org/maven2/org/conscrypt/conscrypt-openjdk-uber/2.5.1/conscrypt-openjdk-uber-2.5.1.jar", "mirror_urls": [ @@ -12492,19 +14171,24 @@ "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], "file": "v1/https/repo1.maven.org/maven2/org/conscrypt/conscrypt-openjdk-uber/2.5.1/conscrypt-openjdk-uber-2.5.1-sources.jar", "mirror_urls": [ @@ -12564,6 +14248,42 @@ "sha256": "c9ad79998548090d580bf8f659d7e6cbdc54676a7f3174a9f819cdede5e5eb34", "url": "https://repo1.maven.org/maven2/org/eclipse/jgit/org.eclipse.jgit/4.4.1.201607150455-r/org.eclipse.jgit-4.4.1.201607150455-r-sources.jar" }, + { + "coord": "org.hamcrest:hamcrest-core:1.3", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar", + "https://jcenter.bintray.com/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar", + "https://maven.google.com/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar" + ], + "sha256": "66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9", + "url": "https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar" + }, + { + "coord": "org.hamcrest:hamcrest-core:jar:sources:1.3", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-sources.jar", + "https://jcenter.bintray.com/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-sources.jar", + "https://maven.google.com/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-sources.jar" + ], + "sha256": "e223d2d8fbafd66057a8848cc94222d63c3cedd652cc48eddc0ab5c39c0f84df", + "url": "https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-sources.jar" + }, { "coord": "org.jetbrains.kotlin:kotlin-reflect:1.4.10", "dependencies": [ @@ -12844,7 +14564,6 @@ "coord": "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1", "dependencies": [ "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.1.1", - "org.jetbrains:annotations:13.0", "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib:1.4.10" ], @@ -12869,7 +14588,6 @@ { "coord": "org.jetbrains.kotlinx:kotlinx-coroutines-core:jar:sources:1.1.1", "dependencies": [ - "org.jetbrains:annotations:jar:sources:13.0", "org.jetbrains.kotlin:kotlin-stdlib-common:jar:sources:1.4.10", "org.jetbrains.kotlin:kotlin-stdlib:jar:sources:1.4.10", "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:jar:sources:1.1.1" @@ -12931,13 +14649,13 @@ { "coord": "org.junit.jupiter:junit-jupiter-api:5.7.0", "dependencies": [ - "org.apiguardian:apiguardian-api:1.1.0", - "org.opentest4j:opentest4j:1.2.0", - "org.junit.platform:junit-platform-commons:1.7.0" + "org.apiguardian:apiguardian-api:1.1.1", + "org.junit.platform:junit-platform-commons:1.8.0-M1", + "org.opentest4j:opentest4j:1.2.0" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:1.1.0", - "org.junit.platform:junit-platform-commons:1.7.0", + "org.apiguardian:apiguardian-api:1.1.1", + "org.junit.platform:junit-platform-commons:1.8.0-M1", "org.opentest4j:opentest4j:1.2.0" ], "exclusions": [ @@ -12957,13 +14675,13 @@ { "coord": "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0", "dependencies": [ - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", - "org.junit.platform:junit-platform-commons:jar:sources:1.7.0", + "org.junit.platform:junit-platform-commons:jar:sources:1.8.0-M1", + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", "org.opentest4j:opentest4j:jar:sources:1.2.0" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", - "org.junit.platform:junit-platform-commons:jar:sources:1.7.0", + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", + "org.junit.platform:junit-platform-commons:jar:sources:1.8.0-M1", "org.opentest4j:opentest4j:jar:sources:1.2.0" ], "exclusions": [ @@ -12983,16 +14701,16 @@ { "coord": "org.junit.jupiter:junit-jupiter-engine:5.6.0", "dependencies": [ - "org.junit.platform:junit-platform-engine:1.6.0", + "org.apiguardian:apiguardian-api:1.1.1", "org.junit.jupiter:junit-jupiter-api:5.7.0", "org.opentest4j:opentest4j:1.2.0", - "org.apiguardian:apiguardian-api:1.1.0", - "org.junit.platform:junit-platform-commons:1.7.0" + "org.junit.platform:junit-platform-commons:1.8.0-M1", + "org.junit.platform:junit-platform-engine:1.8.0-M1" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:1.1.0", + "org.apiguardian:apiguardian-api:1.1.1", "org.junit.jupiter:junit-jupiter-api:5.7.0", - "org.junit.platform:junit-platform-engine:1.6.0" + "org.junit.platform:junit-platform-engine:1.8.0-M1" ], "exclusions": [ "com.google.template:soy", @@ -13011,16 +14729,16 @@ { "coord": "org.junit.jupiter:junit-jupiter-engine:jar:sources:5.6.0", "dependencies": [ - "org.junit.platform:junit-platform-commons:jar:sources:1.7.0", "org.opentest4j:opentest4j:jar:sources:1.2.0", - "org.junit.platform:junit-platform-engine:jar:sources:1.6.0", - "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0", - "org.apiguardian:apiguardian-api:jar:sources:1.1.0" + "org.junit.platform:junit-platform-commons:jar:sources:1.8.0-M1", + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", + "org.junit.platform:junit-platform-engine:jar:sources:1.8.0-M1", + "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0", - "org.junit.platform:junit-platform-engine:jar:sources:1.6.0" + "org.junit.platform:junit-platform-engine:jar:sources:1.8.0-M1" ], "exclusions": [ "com.google.template:soy", @@ -13039,13 +14757,13 @@ { "coord": "org.junit.jupiter:junit-jupiter-params:5.6.0", "dependencies": [ - "org.apiguardian:apiguardian-api:1.1.0", + "org.apiguardian:apiguardian-api:1.1.1", "org.junit.jupiter:junit-jupiter-api:5.7.0", - "org.opentest4j:opentest4j:1.2.0", - "org.junit.platform:junit-platform-commons:1.7.0" + "org.junit.platform:junit-platform-commons:1.8.0-M1", + "org.opentest4j:opentest4j:1.2.0" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:1.1.0", + "org.apiguardian:apiguardian-api:1.1.1", "org.junit.jupiter:junit-jupiter-api:5.7.0" ], "exclusions": [ @@ -13065,13 +14783,13 @@ { "coord": "org.junit.jupiter:junit-jupiter-params:jar:sources:5.6.0", "dependencies": [ - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", - "org.junit.platform:junit-platform-commons:jar:sources:1.7.0", + "org.junit.platform:junit-platform-commons:jar:sources:1.8.0-M1", + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", "org.opentest4j:opentest4j:jar:sources:1.2.0", "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0" ], "exclusions": [ @@ -13089,61 +14807,61 @@ "url": "https://repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-params/5.6.0/junit-jupiter-params-5.6.0-sources.jar" }, { - "coord": "org.junit.platform:junit-platform-commons:1.7.0", + "coord": "org.junit.platform:junit-platform-commons:1.8.0-M1", "dependencies": [ - "org.apiguardian:apiguardian-api:1.1.0" + "org.apiguardian:apiguardian-api:1.1.1" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:1.1.0" + "org.apiguardian:apiguardian-api:1.1.1" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.7.0/junit-platform-commons-1.7.0.jar", + "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.8.0-M1/junit-platform-commons-1.8.0-M1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.7.0/junit-platform-commons-1.7.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-commons/1.7.0/junit-platform-commons-1.7.0.jar", - "https://jcenter.bintray.com/org/junit/platform/junit-platform-commons/1.7.0/junit-platform-commons-1.7.0.jar", - "https://maven.google.com/org/junit/platform/junit-platform-commons/1.7.0/junit-platform-commons-1.7.0.jar" + "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.8.0-M1/junit-platform-commons-1.8.0-M1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-commons/1.8.0-M1/junit-platform-commons-1.8.0-M1.jar", + "https://jcenter.bintray.com/org/junit/platform/junit-platform-commons/1.8.0-M1/junit-platform-commons-1.8.0-M1.jar", + "https://maven.google.com/org/junit/platform/junit-platform-commons/1.8.0-M1/junit-platform-commons-1.8.0-M1.jar" ], - "sha256": "5330ee87cc7586e6e25175a34e9251624ff12ff525269d3415d0b4ca519b6fea", - "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.7.0/junit-platform-commons-1.7.0.jar" + "sha256": "47dc95db894125372aa2ba7f9cbf4611311aeb430dcdd423b9806e64666beb3e", + "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.8.0-M1/junit-platform-commons-1.8.0-M1.jar" }, { - "coord": "org.junit.platform:junit-platform-commons:jar:sources:1.7.0", + "coord": "org.junit.platform:junit-platform-commons:jar:sources:1.8.0-M1", "dependencies": [ - "org.apiguardian:apiguardian-api:jar:sources:1.1.0" + "org.apiguardian:apiguardian-api:jar:sources:1.1.1" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:jar:sources:1.1.0" + "org.apiguardian:apiguardian-api:jar:sources:1.1.1" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.7.0/junit-platform-commons-1.7.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.8.0-M1/junit-platform-commons-1.8.0-M1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.7.0/junit-platform-commons-1.7.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-commons/1.7.0/junit-platform-commons-1.7.0-sources.jar", - "https://jcenter.bintray.com/org/junit/platform/junit-platform-commons/1.7.0/junit-platform-commons-1.7.0-sources.jar", - "https://maven.google.com/org/junit/platform/junit-platform-commons/1.7.0/junit-platform-commons-1.7.0-sources.jar" + "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.8.0-M1/junit-platform-commons-1.8.0-M1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-commons/1.8.0-M1/junit-platform-commons-1.8.0-M1-sources.jar", + "https://jcenter.bintray.com/org/junit/platform/junit-platform-commons/1.8.0-M1/junit-platform-commons-1.8.0-M1-sources.jar", + "https://maven.google.com/org/junit/platform/junit-platform-commons/1.8.0-M1/junit-platform-commons-1.8.0-M1-sources.jar" ], - "sha256": "45678a6a6efa13542971fbd1e81a86b2038698aed8039a2438e39e5dc24c99a0", - "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.7.0/junit-platform-commons-1.7.0-sources.jar" + "sha256": "7384658d71b43dd90dda11cff3edd55cb82eaacd94bcb7937b8b3c3ed5576187", + "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.8.0-M1/junit-platform-commons-1.8.0-M1-sources.jar" }, { "coord": "org.junit.platform:junit-platform-console:1.6.0", "dependencies": [ "org.junit.platform:junit-platform-reporting:1.6.0", - "org.junit.platform:junit-platform-engine:1.6.0", - "org.junit.platform:junit-platform-launcher:1.6.0", + "org.apiguardian:apiguardian-api:1.1.1", + "org.junit.platform:junit-platform-launcher:1.8.0-M1", "org.opentest4j:opentest4j:1.2.0", - "org.apiguardian:apiguardian-api:1.1.0", - "org.junit.platform:junit-platform-commons:1.7.0" + "org.junit.platform:junit-platform-commons:1.8.0-M1", + "org.junit.platform:junit-platform-engine:1.8.0-M1" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:1.1.0", + "org.apiguardian:apiguardian-api:1.1.1", "org.junit.platform:junit-platform-reporting:1.6.0" ], "exclusions": [ @@ -13163,15 +14881,15 @@ { "coord": "org.junit.platform:junit-platform-console:jar:sources:1.6.0", "dependencies": [ - "org.junit.platform:junit-platform-commons:jar:sources:1.7.0", "org.opentest4j:opentest4j:jar:sources:1.2.0", - "org.junit.platform:junit-platform-engine:jar:sources:1.6.0", + "org.junit.platform:junit-platform-launcher:jar:sources:1.8.0-M1", "org.junit.platform:junit-platform-reporting:jar:sources:1.6.0", - "org.junit.platform:junit-platform-launcher:jar:sources:1.6.0", - "org.apiguardian:apiguardian-api:jar:sources:1.1.0" + "org.junit.platform:junit-platform-commons:jar:sources:1.8.0-M1", + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", + "org.junit.platform:junit-platform-engine:jar:sources:1.8.0-M1" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", "org.junit.platform:junit-platform-reporting:jar:sources:1.6.0" ], "exclusions": [ @@ -13189,121 +14907,121 @@ "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console/1.6.0/junit-platform-console-1.6.0-sources.jar" }, { - "coord": "org.junit.platform:junit-platform-engine:1.6.0", + "coord": "org.junit.platform:junit-platform-engine:1.8.0-M1", "dependencies": [ - "org.apiguardian:apiguardian-api:1.1.0", - "org.opentest4j:opentest4j:1.2.0", - "org.junit.platform:junit-platform-commons:1.7.0" + "org.apiguardian:apiguardian-api:1.1.1", + "org.junit.platform:junit-platform-commons:1.8.0-M1", + "org.opentest4j:opentest4j:1.2.0" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:1.1.0", - "org.junit.platform:junit-platform-commons:1.7.0", + "org.apiguardian:apiguardian-api:1.1.1", + "org.junit.platform:junit-platform-commons:1.8.0-M1", "org.opentest4j:opentest4j:1.2.0" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.6.0/junit-platform-engine-1.6.0.jar", + "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.8.0-M1/junit-platform-engine-1.8.0-M1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.6.0/junit-platform-engine-1.6.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-engine/1.6.0/junit-platform-engine-1.6.0.jar", - "https://jcenter.bintray.com/org/junit/platform/junit-platform-engine/1.6.0/junit-platform-engine-1.6.0.jar", - "https://maven.google.com/org/junit/platform/junit-platform-engine/1.6.0/junit-platform-engine-1.6.0.jar" + "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.8.0-M1/junit-platform-engine-1.8.0-M1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-engine/1.8.0-M1/junit-platform-engine-1.8.0-M1.jar", + "https://jcenter.bintray.com/org/junit/platform/junit-platform-engine/1.8.0-M1/junit-platform-engine-1.8.0-M1.jar", + "https://maven.google.com/org/junit/platform/junit-platform-engine/1.8.0-M1/junit-platform-engine-1.8.0-M1.jar" ], - "sha256": "7aed5424cb31a8255daecb1fcb0c173b0b64b1262e1eb2eaf87bbc7aec5e6d76", - "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.6.0/junit-platform-engine-1.6.0.jar" + "sha256": "1dc38aed98fbc05855a979c2f9b2e12d6e0e164092c3576cad3837710aef3bfb", + "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.8.0-M1/junit-platform-engine-1.8.0-M1.jar" }, { - "coord": "org.junit.platform:junit-platform-engine:jar:sources:1.6.0", + "coord": "org.junit.platform:junit-platform-engine:jar:sources:1.8.0-M1", "dependencies": [ - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", - "org.junit.platform:junit-platform-commons:jar:sources:1.7.0", + "org.junit.platform:junit-platform-commons:jar:sources:1.8.0-M1", + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", "org.opentest4j:opentest4j:jar:sources:1.2.0" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", - "org.junit.platform:junit-platform-commons:jar:sources:1.7.0", + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", + "org.junit.platform:junit-platform-commons:jar:sources:1.8.0-M1", "org.opentest4j:opentest4j:jar:sources:1.2.0" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.6.0/junit-platform-engine-1.6.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.8.0-M1/junit-platform-engine-1.8.0-M1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.6.0/junit-platform-engine-1.6.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-engine/1.6.0/junit-platform-engine-1.6.0-sources.jar", - "https://jcenter.bintray.com/org/junit/platform/junit-platform-engine/1.6.0/junit-platform-engine-1.6.0-sources.jar", - "https://maven.google.com/org/junit/platform/junit-platform-engine/1.6.0/junit-platform-engine-1.6.0-sources.jar" + "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.8.0-M1/junit-platform-engine-1.8.0-M1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-engine/1.8.0-M1/junit-platform-engine-1.8.0-M1-sources.jar", + "https://jcenter.bintray.com/org/junit/platform/junit-platform-engine/1.8.0-M1/junit-platform-engine-1.8.0-M1-sources.jar", + "https://maven.google.com/org/junit/platform/junit-platform-engine/1.8.0-M1/junit-platform-engine-1.8.0-M1-sources.jar" ], - "sha256": "13b0574aa0ae228930ae930f7285002ec267fc6c273ed608f2c35d440de9b070", - "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.6.0/junit-platform-engine-1.6.0-sources.jar" + "sha256": "901843bd7922b746a5bf4e96c680bc8c1cb51a452ac6d6a575a8bb4dad1295d9", + "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.8.0-M1/junit-platform-engine-1.8.0-M1-sources.jar" }, { - "coord": "org.junit.platform:junit-platform-launcher:1.6.0", + "coord": "org.junit.platform:junit-platform-launcher:1.8.0-M1", "dependencies": [ - "org.apiguardian:apiguardian-api:1.1.0", - "org.junit.platform:junit-platform-engine:1.6.0", - "org.opentest4j:opentest4j:1.2.0", - "org.junit.platform:junit-platform-commons:1.7.0" + "org.apiguardian:apiguardian-api:1.1.1", + "org.junit.platform:junit-platform-engine:1.8.0-M1", + "org.junit.platform:junit-platform-commons:1.8.0-M1", + "org.opentest4j:opentest4j:1.2.0" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:1.1.0", - "org.junit.platform:junit-platform-engine:1.6.0" + "org.apiguardian:apiguardian-api:1.1.1", + "org.junit.platform:junit-platform-engine:1.8.0-M1" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.6.0/junit-platform-launcher-1.6.0.jar", + "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.8.0-M1/junit-platform-launcher-1.8.0-M1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.6.0/junit-platform-launcher-1.6.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-launcher/1.6.0/junit-platform-launcher-1.6.0.jar", - "https://jcenter.bintray.com/org/junit/platform/junit-platform-launcher/1.6.0/junit-platform-launcher-1.6.0.jar", - "https://maven.google.com/org/junit/platform/junit-platform-launcher/1.6.0/junit-platform-launcher-1.6.0.jar" + "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.8.0-M1/junit-platform-launcher-1.8.0-M1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-launcher/1.8.0-M1/junit-platform-launcher-1.8.0-M1.jar", + "https://jcenter.bintray.com/org/junit/platform/junit-platform-launcher/1.8.0-M1/junit-platform-launcher-1.8.0-M1.jar", + "https://maven.google.com/org/junit/platform/junit-platform-launcher/1.8.0-M1/junit-platform-launcher-1.8.0-M1.jar" ], - "sha256": "11490be3f7488098fc460f0f754e95bbf6667473ab7f17d7d200557f9398c248", - "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.6.0/junit-platform-launcher-1.6.0.jar" + "sha256": "f31f466bc2da83e9e8639bc13dbcc29d20a1c63c6f0f59b47f2d29ae0ade216b", + "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.8.0-M1/junit-platform-launcher-1.8.0-M1.jar" }, { - "coord": "org.junit.platform:junit-platform-launcher:jar:sources:1.6.0", + "coord": "org.junit.platform:junit-platform-launcher:jar:sources:1.8.0-M1", "dependencies": [ - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", - "org.junit.platform:junit-platform-commons:jar:sources:1.7.0", - "org.opentest4j:opentest4j:jar:sources:1.2.0", - "org.junit.platform:junit-platform-engine:jar:sources:1.6.0" + "org.junit.platform:junit-platform-commons:jar:sources:1.8.0-M1", + "org.junit.platform:junit-platform-engine:jar:sources:1.8.0-M1", + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", + "org.opentest4j:opentest4j:jar:sources:1.2.0" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", - "org.junit.platform:junit-platform-engine:jar:sources:1.6.0" + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", + "org.junit.platform:junit-platform-engine:jar:sources:1.8.0-M1" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.6.0/junit-platform-launcher-1.6.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.8.0-M1/junit-platform-launcher-1.8.0-M1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.6.0/junit-platform-launcher-1.6.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-launcher/1.6.0/junit-platform-launcher-1.6.0-sources.jar", - "https://jcenter.bintray.com/org/junit/platform/junit-platform-launcher/1.6.0/junit-platform-launcher-1.6.0-sources.jar", - "https://maven.google.com/org/junit/platform/junit-platform-launcher/1.6.0/junit-platform-launcher-1.6.0-sources.jar" + "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.8.0-M1/junit-platform-launcher-1.8.0-M1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-launcher/1.8.0-M1/junit-platform-launcher-1.8.0-M1-sources.jar", + "https://jcenter.bintray.com/org/junit/platform/junit-platform-launcher/1.8.0-M1/junit-platform-launcher-1.8.0-M1-sources.jar", + "https://maven.google.com/org/junit/platform/junit-platform-launcher/1.8.0-M1/junit-platform-launcher-1.8.0-M1-sources.jar" ], - "sha256": "8b8a87f59a0a2fd94435a1e3ba93b142dc8b921fa39e25a4cf231ad7a4e5c01f", - "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.6.0/junit-platform-launcher-1.6.0-sources.jar" + "sha256": "beb98de2f7b0a9ea4720ec6e6fac63a02c0bdb1f34912467fb94979045f04b34", + "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.8.0-M1/junit-platform-launcher-1.8.0-M1-sources.jar" }, { "coord": "org.junit.platform:junit-platform-reporting:1.6.0", "dependencies": [ - "org.junit.platform:junit-platform-engine:1.6.0", - "org.junit.platform:junit-platform-launcher:1.6.0", + "org.apiguardian:apiguardian-api:1.1.1", + "org.junit.platform:junit-platform-launcher:1.8.0-M1", "org.opentest4j:opentest4j:1.2.0", - "org.apiguardian:apiguardian-api:1.1.0", - "org.junit.platform:junit-platform-commons:1.7.0" + "org.junit.platform:junit-platform-commons:1.8.0-M1", + "org.junit.platform:junit-platform-engine:1.8.0-M1" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:1.1.0", - "org.junit.platform:junit-platform-launcher:1.6.0" + "org.apiguardian:apiguardian-api:1.1.1", + "org.junit.platform:junit-platform-launcher:1.8.0-M1" ], "exclusions": [ "com.google.template:soy", @@ -13322,15 +15040,15 @@ { "coord": "org.junit.platform:junit-platform-reporting:jar:sources:1.6.0", "dependencies": [ - "org.junit.platform:junit-platform-commons:jar:sources:1.7.0", "org.opentest4j:opentest4j:jar:sources:1.2.0", - "org.junit.platform:junit-platform-engine:jar:sources:1.6.0", - "org.junit.platform:junit-platform-launcher:jar:sources:1.6.0", - "org.apiguardian:apiguardian-api:jar:sources:1.1.0" + "org.junit.platform:junit-platform-launcher:jar:sources:1.8.0-M1", + "org.junit.platform:junit-platform-commons:jar:sources:1.8.0-M1", + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", + "org.junit.platform:junit-platform-engine:jar:sources:1.8.0-M1" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:jar:sources:1.1.0", - "org.junit.platform:junit-platform-launcher:jar:sources:1.6.0" + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", + "org.junit.platform:junit-platform-launcher:jar:sources:1.8.0-M1" ], "exclusions": [ "com.google.template:soy", @@ -13347,48 +15065,112 @@ "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-reporting/1.6.0/junit-platform-reporting-1.6.0-sources.jar" }, { - "coord": "org.junit.platform:junit-platform-suite-api:1.6.0", + "coord": "org.junit.platform:junit-platform-suite-api:1.8.0-M1", + "dependencies": [ + "org.apiguardian:apiguardian-api:1.1.1", + "org.junit.platform:junit-platform-commons:1.8.0-M1" + ], + "directDependencies": [ + "org.apiguardian:apiguardian-api:1.1.1", + "org.junit.platform:junit-platform-commons:1.8.0-M1" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-api/1.8.0-M1/junit-platform-suite-api-1.8.0-M1.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-api/1.8.0-M1/junit-platform-suite-api-1.8.0-M1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-suite-api/1.8.0-M1/junit-platform-suite-api-1.8.0-M1.jar", + "https://jcenter.bintray.com/org/junit/platform/junit-platform-suite-api/1.8.0-M1/junit-platform-suite-api-1.8.0-M1.jar", + "https://maven.google.com/org/junit/platform/junit-platform-suite-api/1.8.0-M1/junit-platform-suite-api-1.8.0-M1.jar" + ], + "sha256": "d2de9e33af6277e96c56578b379b63380d197ab24848613bad70617a4fee7276", + "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-api/1.8.0-M1/junit-platform-suite-api-1.8.0-M1.jar" + }, + { + "coord": "org.junit.platform:junit-platform-suite-api:jar:sources:1.8.0-M1", + "dependencies": [ + "org.junit.platform:junit-platform-commons:jar:sources:1.8.0-M1", + "org.apiguardian:apiguardian-api:jar:sources:1.1.1" + ], + "directDependencies": [ + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", + "org.junit.platform:junit-platform-commons:jar:sources:1.8.0-M1" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-api/1.8.0-M1/junit-platform-suite-api-1.8.0-M1-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-api/1.8.0-M1/junit-platform-suite-api-1.8.0-M1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-suite-api/1.8.0-M1/junit-platform-suite-api-1.8.0-M1-sources.jar", + "https://jcenter.bintray.com/org/junit/platform/junit-platform-suite-api/1.8.0-M1/junit-platform-suite-api-1.8.0-M1-sources.jar", + "https://maven.google.com/org/junit/platform/junit-platform-suite-api/1.8.0-M1/junit-platform-suite-api-1.8.0-M1-sources.jar" + ], + "sha256": "f5982c71b3f1bca908a622afe5c24ec82a7e59a2bdb0af9e80df8cdd1e6d88c3", + "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-api/1.8.0-M1/junit-platform-suite-api-1.8.0-M1-sources.jar" + }, + { + "coord": "org.junit.platform:junit-platform-suite-commons:1.8.0-M1", "dependencies": [ - "org.apiguardian:apiguardian-api:1.1.0" + "org.junit.platform:junit-platform-suite-api:1.8.0-M1", + "org.apiguardian:apiguardian-api:1.1.1", + "org.junit.platform:junit-platform-launcher:1.8.0-M1", + "org.opentest4j:opentest4j:1.2.0", + "org.junit.platform:junit-platform-commons:1.8.0-M1", + "org.junit.platform:junit-platform-engine:1.8.0-M1" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:1.1.0" + "org.apiguardian:apiguardian-api:1.1.1", + "org.junit.platform:junit-platform-engine:1.8.0-M1", + "org.junit.platform:junit-platform-launcher:1.8.0-M1", + "org.junit.platform:junit-platform-suite-api:1.8.0-M1" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-api/1.6.0/junit-platform-suite-api-1.6.0.jar", + "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-commons/1.8.0-M1/junit-platform-suite-commons-1.8.0-M1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-api/1.6.0/junit-platform-suite-api-1.6.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-suite-api/1.6.0/junit-platform-suite-api-1.6.0.jar", - "https://jcenter.bintray.com/org/junit/platform/junit-platform-suite-api/1.6.0/junit-platform-suite-api-1.6.0.jar", - "https://maven.google.com/org/junit/platform/junit-platform-suite-api/1.6.0/junit-platform-suite-api-1.6.0.jar" + "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-commons/1.8.0-M1/junit-platform-suite-commons-1.8.0-M1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-suite-commons/1.8.0-M1/junit-platform-suite-commons-1.8.0-M1.jar", + "https://jcenter.bintray.com/org/junit/platform/junit-platform-suite-commons/1.8.0-M1/junit-platform-suite-commons-1.8.0-M1.jar", + "https://maven.google.com/org/junit/platform/junit-platform-suite-commons/1.8.0-M1/junit-platform-suite-commons-1.8.0-M1.jar" ], - "sha256": "bdcb140689ebeb49bf6164709a0ec7f1203a6c6ae3dc7cf252a5e2aa57833a4d", - "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-api/1.6.0/junit-platform-suite-api-1.6.0.jar" + "sha256": "90e93541adb0b573f002d8546edda32f3733ca210ce55766ccf9fa670db8371e", + "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-commons/1.8.0-M1/junit-platform-suite-commons-1.8.0-M1.jar" }, { - "coord": "org.junit.platform:junit-platform-suite-api:jar:sources:1.6.0", + "coord": "org.junit.platform:junit-platform-suite-commons:jar:sources:1.8.0-M1", "dependencies": [ - "org.apiguardian:apiguardian-api:jar:sources:1.1.0" + "org.junit.platform:junit-platform-suite-api:jar:sources:1.8.0-M1", + "org.opentest4j:opentest4j:jar:sources:1.2.0", + "org.junit.platform:junit-platform-launcher:jar:sources:1.8.0-M1", + "org.junit.platform:junit-platform-commons:jar:sources:1.8.0-M1", + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", + "org.junit.platform:junit-platform-engine:jar:sources:1.8.0-M1" ], "directDependencies": [ - "org.apiguardian:apiguardian-api:jar:sources:1.1.0" + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", + "org.junit.platform:junit-platform-engine:jar:sources:1.8.0-M1", + "org.junit.platform:junit-platform-launcher:jar:sources:1.8.0-M1", + "org.junit.platform:junit-platform-suite-api:jar:sources:1.8.0-M1" ], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-api/1.6.0/junit-platform-suite-api-1.6.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-commons/1.8.0-M1/junit-platform-suite-commons-1.8.0-M1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-api/1.6.0/junit-platform-suite-api-1.6.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-suite-api/1.6.0/junit-platform-suite-api-1.6.0-sources.jar", - "https://jcenter.bintray.com/org/junit/platform/junit-platform-suite-api/1.6.0/junit-platform-suite-api-1.6.0-sources.jar", - "https://maven.google.com/org/junit/platform/junit-platform-suite-api/1.6.0/junit-platform-suite-api-1.6.0-sources.jar" + "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-commons/1.8.0-M1/junit-platform-suite-commons-1.8.0-M1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/junit/platform/junit-platform-suite-commons/1.8.0-M1/junit-platform-suite-commons-1.8.0-M1-sources.jar", + "https://jcenter.bintray.com/org/junit/platform/junit-platform-suite-commons/1.8.0-M1/junit-platform-suite-commons-1.8.0-M1-sources.jar", + "https://maven.google.com/org/junit/platform/junit-platform-suite-commons/1.8.0-M1/junit-platform-suite-commons-1.8.0-M1-sources.jar" ], - "sha256": "41f1ea37b705b2232706915d5912bebc1387088265baf970140285e110665768", - "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-api/1.6.0/junit-platform-suite-api-1.6.0-sources.jar" + "sha256": "a2c0a3619a098d90cd397c29d6692538cfb8bb52948611ded10fce6098ef0ef7", + "url": "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-suite-commons/1.8.0-M1/junit-platform-suite-commons-1.8.0-M1-sources.jar" }, { "coord": "org.objenesis:objenesis:2.6", @@ -13463,40 +15245,40 @@ "url": "https://repo1.maven.org/maven2/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0-sources.jar" }, { - "coord": "org.ow2.asm:asm:7.0", + "coord": "org.ow2.asm:asm:9.2", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/ow2/asm/asm/7.0/asm-7.0.jar", + "file": "v1/https/repo1.maven.org/maven2/org/ow2/asm/asm/9.2/asm-9.2.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/ow2/asm/asm/7.0/asm-7.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/ow2/asm/asm/7.0/asm-7.0.jar", - "https://jcenter.bintray.com/org/ow2/asm/asm/7.0/asm-7.0.jar", - "https://maven.google.com/org/ow2/asm/asm/7.0/asm-7.0.jar" + "https://repo1.maven.org/maven2/org/ow2/asm/asm/9.2/asm-9.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/ow2/asm/asm/9.2/asm-9.2.jar", + "https://jcenter.bintray.com/org/ow2/asm/asm/9.2/asm-9.2.jar", + "https://maven.google.com/org/ow2/asm/asm/9.2/asm-9.2.jar" ], - "sha256": "b88ef66468b3c978ad0c97fd6e90979e56155b4ac69089ba7a44e9aa7ffe9acf", - "url": "https://repo1.maven.org/maven2/org/ow2/asm/asm/7.0/asm-7.0.jar" + "sha256": "b9d4fe4d71938df38839f0eca42aaaa64cf8b313d678da036f0cb3ca199b47f5", + "url": "https://repo1.maven.org/maven2/org/ow2/asm/asm/9.2/asm-9.2.jar" }, { - "coord": "org.ow2.asm:asm:jar:sources:7.0", + "coord": "org.ow2.asm:asm:jar:sources:9.2", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/ow2/asm/asm/7.0/asm-7.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/org/ow2/asm/asm/9.2/asm-9.2-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/ow2/asm/asm/7.0/asm-7.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/ow2/asm/asm/7.0/asm-7.0-sources.jar", - "https://jcenter.bintray.com/org/ow2/asm/asm/7.0/asm-7.0-sources.jar", - "https://maven.google.com/org/ow2/asm/asm/7.0/asm-7.0-sources.jar" + "https://repo1.maven.org/maven2/org/ow2/asm/asm/9.2/asm-9.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/ow2/asm/asm/9.2/asm-9.2-sources.jar", + "https://jcenter.bintray.com/org/ow2/asm/asm/9.2/asm-9.2-sources.jar", + "https://maven.google.com/org/ow2/asm/asm/9.2/asm-9.2-sources.jar" ], - "sha256": "51a538468a788fa543e80e6bccbe05d2a738fa0da553b710a1fd8ed574504982", - "url": "https://repo1.maven.org/maven2/org/ow2/asm/asm/7.0/asm-7.0-sources.jar" + "sha256": "81e807010631f0e8074b0fb85e80afd6efbbd7e4b3694aad19e944c171980fb7", + "url": "https://repo1.maven.org/maven2/org/ow2/asm/asm/9.2/asm-9.2-sources.jar" }, { "coord": "org.reactivestreams:reactive-streams:1.0.3", @@ -13522,17 +15304,7 @@ "directDependencies": [], "exclusions": [ "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "com.google.common.html.types:types" ], "file": "v1/https/repo1.maven.org/maven2/org/reactivestreams/reactive-streams/1.0.3/reactive-streams-1.0.3-sources.jar", "mirror_urls": [ @@ -13544,6 +15316,88 @@ "sha256": "d5b4070a22c9b1ca5b9b5aa668466bcca391dbe5d5fe8311c300765c1621feba", "url": "https://repo1.maven.org/maven2/org/reactivestreams/reactive-streams/1.0.3/reactive-streams-1.0.3-sources.jar" }, + { + "coord": "org.rnorth.duct-tape:duct-tape:1.0.8", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "org.jetbrains:annotations", + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/org/rnorth/duct-tape/duct-tape/1.0.8/duct-tape-1.0.8.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/rnorth/duct-tape/duct-tape/1.0.8/duct-tape-1.0.8.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/rnorth/duct-tape/duct-tape/1.0.8/duct-tape-1.0.8.jar", + "https://jcenter.bintray.com/org/rnorth/duct-tape/duct-tape/1.0.8/duct-tape-1.0.8.jar", + "https://maven.google.com/org/rnorth/duct-tape/duct-tape/1.0.8/duct-tape-1.0.8.jar" + ], + "sha256": "31cef12ddec979d1f86d7cf708c41a17da523d05c685fd6642e9d0b2addb7240", + "url": "https://repo1.maven.org/maven2/org/rnorth/duct-tape/duct-tape/1.0.8/duct-tape-1.0.8.jar" + }, + { + "coord": "org.rnorth.duct-tape:duct-tape:jar:sources:1.0.8", + "dependencies": [], + "directDependencies": [], + "exclusions": [ + "org.jetbrains:annotations", + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/org/rnorth/duct-tape/duct-tape/1.0.8/duct-tape-1.0.8-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/rnorth/duct-tape/duct-tape/1.0.8/duct-tape-1.0.8-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/rnorth/duct-tape/duct-tape/1.0.8/duct-tape-1.0.8-sources.jar", + "https://jcenter.bintray.com/org/rnorth/duct-tape/duct-tape/1.0.8/duct-tape-1.0.8-sources.jar", + "https://maven.google.com/org/rnorth/duct-tape/duct-tape/1.0.8/duct-tape-1.0.8-sources.jar" + ], + "sha256": "b385fd2c2b435c313b3f02988d351503230c9631bfb432261cbd8ce9765d2a26", + "url": "https://repo1.maven.org/maven2/org/rnorth/duct-tape/duct-tape/1.0.8/duct-tape-1.0.8-sources.jar" + }, + { + "coord": "org.rnorth.visible-assertions:visible-assertions:2.1.2", + "dependencies": [ + "net.java.dev.jna:jna:5.8.0" + ], + "directDependencies": [ + "net.java.dev.jna:jna:5.8.0" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/org/rnorth/visible-assertions/visible-assertions/2.1.2/visible-assertions-2.1.2.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/rnorth/visible-assertions/visible-assertions/2.1.2/visible-assertions-2.1.2.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/rnorth/visible-assertions/visible-assertions/2.1.2/visible-assertions-2.1.2.jar", + "https://jcenter.bintray.com/org/rnorth/visible-assertions/visible-assertions/2.1.2/visible-assertions-2.1.2.jar", + "https://maven.google.com/org/rnorth/visible-assertions/visible-assertions/2.1.2/visible-assertions-2.1.2.jar" + ], + "sha256": "4504ae968b237cdcdcb68ff5b07aa63abe4992f907a77c3d6120aa9b9041401c", + "url": "https://repo1.maven.org/maven2/org/rnorth/visible-assertions/visible-assertions/2.1.2/visible-assertions-2.1.2.jar" + }, + { + "coord": "org.rnorth.visible-assertions:visible-assertions:jar:sources:2.1.2", + "dependencies": [ + "net.java.dev.jna:jna:jar:sources:5.8.0" + ], + "directDependencies": [ + "net.java.dev.jna:jna:jar:sources:5.8.0" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/org/rnorth/visible-assertions/visible-assertions/2.1.2/visible-assertions-2.1.2-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/rnorth/visible-assertions/visible-assertions/2.1.2/visible-assertions-2.1.2-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/rnorth/visible-assertions/visible-assertions/2.1.2/visible-assertions-2.1.2-sources.jar", + "https://jcenter.bintray.com/org/rnorth/visible-assertions/visible-assertions/2.1.2/visible-assertions-2.1.2-sources.jar", + "https://maven.google.com/org/rnorth/visible-assertions/visible-assertions/2.1.2/visible-assertions-2.1.2-sources.jar" + ], + "sha256": "b6dc190fdf53bbeb6be7012487a35a0cfc623ad56163a611d1c8ad04709aba22", + "url": "https://repo1.maven.org/maven2/org/rnorth/visible-assertions/visible-assertions/2.1.2/visible-assertions-2.1.2-sources.jar" + }, { "coord": "org.seleniumhq.selenium:selenium-api:3.141.59", "dependencies": [], @@ -13583,23 +15437,17 @@ { "coord": "org.seleniumhq.selenium:selenium-remote-driver:3.141.59", "dependencies": [ - "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava", "com.squareup.okio:okio:1.14.0", - "com.google.j2objc:j2objc-annotations:1.3", - "com.google.code.findbugs:jsr305:3.0.2", + "com.google.guava:guava:30.1.1-android", "net.bytebuddy:byte-buddy:1.9.3", - "com.google.guava:guava:30.0-android", "org.seleniumhq.selenium:selenium-api:3.141.59", "com.squareup.okhttp3:okhttp:3.11.0", - "com.google.guava:failureaccess:1.0.1", - "org.apache.commons:commons-exec:1.3", - "com.google.errorprone:error_prone_annotations:2.4.0", - "org.checkerframework:checker-compat-qual:2.5.5" + "org.apache.commons:commons-exec:1.3" ], "directDependencies": [ "com.squareup.okio:okio:1.14.0", + "com.google.guava:guava:30.1.1-android", "net.bytebuddy:byte-buddy:1.9.3", - "com.google.guava:guava:30.0-android", "org.seleniumhq.selenium:selenium-api:3.141.59", "com.squareup.okhttp3:okhttp:3.11.0", "org.apache.commons:commons-exec:1.3" @@ -13621,25 +15469,19 @@ { "coord": "org.seleniumhq.selenium:selenium-remote-driver:jar:sources:3.141.59", "dependencies": [ - "com.google.code.findbugs:jsr305:jar:sources:3.0.2", - "com.google.j2objc:j2objc-annotations:jar:sources:1.3", + "com.google.guava:guava:jar:sources:30.1.1-android", "net.bytebuddy:byte-buddy:jar:sources:1.9.3", "org.seleniumhq.selenium:selenium-api:jar:sources:3.141.59", "org.apache.commons:commons-exec:jar:sources:1.3", "com.squareup.okhttp3:okhttp:jar:sources:3.11.0", - "org.checkerframework:checker-compat-qual:jar:sources:2.5.5", - "com.google.errorprone:error_prone_annotations:jar:sources:2.4.0", - "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", - "com.google.guava:guava:jar:sources:30.0-android", - "com.google.guava:failureaccess:jar:sources:1.0.1", "com.squareup.okio:okio:jar:sources:1.14.0" ], "directDependencies": [ + "com.google.guava:guava:jar:sources:30.1.1-android", "net.bytebuddy:byte-buddy:jar:sources:1.9.3", "org.seleniumhq.selenium:selenium-api:jar:sources:3.141.59", "org.apache.commons:commons-exec:jar:sources:1.3", "com.squareup.okhttp3:okhttp:jar:sources:3.11.0", - "com.google.guava:guava:jar:sources:30.0-android", "com.squareup.okio:okio:jar:sources:1.14.0" ], "exclusions": [ @@ -13657,98 +15499,320 @@ "url": "https://repo1.maven.org/maven2/org/seleniumhq/selenium/selenium-remote-driver/3.141.59/selenium-remote-driver-3.141.59-sources.jar" }, { - "coord": "org.slf4j:slf4j-api:1.7.30", + "coord": "org.slf4j:slf4j-api:1.7.31", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar", + "file": "v1/https/repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.31/slf4j-api-1.7.31.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar", - "https://jcenter.bintray.com/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar", - "https://maven.google.com/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar" + "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.31/slf4j-api-1.7.31.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/slf4j/slf4j-api/1.7.31/slf4j-api-1.7.31.jar", + "https://jcenter.bintray.com/org/slf4j/slf4j-api/1.7.31/slf4j-api-1.7.31.jar", + "https://maven.google.com/org/slf4j/slf4j-api/1.7.31/slf4j-api-1.7.31.jar" ], - "sha256": "cdba07964d1bb40a0761485c6b1e8c2f8fd9eb1d19c53928ac0d7f9510105c57", - "url": "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar" + "sha256": "cda862aa69683ac072ee2afd0b93d070ceab291ef5b857d318b24c1595b36b8a", + "url": "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.31/slf4j-api-1.7.31.jar" }, { - "coord": "org.slf4j:slf4j-api:jar:sources:1.7.30", + "coord": "org.slf4j:slf4j-api:jar:sources:1.7.31", "dependencies": [], "directDependencies": [], "exclusions": [ "com.google.template:soy", "com.google.common.html.types:types" ], - "file": "v1/https/repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.31/slf4j-api-1.7.31-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.31/slf4j-api-1.7.31-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/slf4j/slf4j-api/1.7.31/slf4j-api-1.7.31-sources.jar", + "https://jcenter.bintray.com/org/slf4j/slf4j-api/1.7.31/slf4j-api-1.7.31-sources.jar", + "https://maven.google.com/org/slf4j/slf4j-api/1.7.31/slf4j-api-1.7.31-sources.jar" + ], + "sha256": "9dab23a241cb61f4c23046d134e6fa1f4a198dbb92c6506ff4e95733600c0775", + "url": "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.31/slf4j-api-1.7.31-sources.jar" + }, + { + "coord": "org.testcontainers:gcloud:1.15.3", + "dependencies": [ + "com.github.docker-java:docker-java-transport-zerodep:3.2.8", + "org.apache.commons:commons-compress:1.20", + "com.github.docker-java:docker-java-api:3.2.8", + "org.rnorth.visible-assertions:visible-assertions:2.1.2", + "net.java.dev.jna:jna:5.8.0", + "org.rnorth.duct-tape:duct-tape:1.0.8", + "org.testcontainers:testcontainers:1.15.3", + "junit:junit:4.13.2", + "com.github.docker-java:docker-java-transport:3.2.8", + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "org.slf4j:slf4j-api:1.7.31" + ], + "directDependencies": [ + "org.testcontainers:testcontainers:1.15.3" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/org/testcontainers/gcloud/1.15.3/gcloud-1.15.3.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/testcontainers/gcloud/1.15.3/gcloud-1.15.3.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/testcontainers/gcloud/1.15.3/gcloud-1.15.3.jar", + "https://jcenter.bintray.com/org/testcontainers/gcloud/1.15.3/gcloud-1.15.3.jar", + "https://maven.google.com/org/testcontainers/gcloud/1.15.3/gcloud-1.15.3.jar" + ], + "sha256": "fb96f9edc9c1598161637f43f1f73f660a2bf6792eb18928bdd95cb3d5b06a6b", + "url": "https://repo1.maven.org/maven2/org/testcontainers/gcloud/1.15.3/gcloud-1.15.3.jar" + }, + { + "coord": "org.testcontainers:gcloud:jar:sources:1.15.3", + "dependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "com.github.docker-java:docker-java-transport:jar:sources:3.2.8", + "com.github.docker-java:docker-java-api:jar:sources:3.2.8", + "junit:junit:jar:sources:4.13.2", + "org.apache.commons:commons-compress:jar:sources:1.20", + "org.rnorth.visible-assertions:visible-assertions:jar:sources:2.1.2", + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "com.github.docker-java:docker-java-transport-zerodep:jar:sources:3.2.8", + "org.testcontainers:testcontainers:jar:sources:1.15.3", + "org.rnorth.duct-tape:duct-tape:jar:sources:1.0.8", + "net.java.dev.jna:jna:jar:sources:5.8.0" + ], + "directDependencies": [ + "org.testcontainers:testcontainers:jar:sources:1.15.3" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/org/testcontainers/gcloud/1.15.3/gcloud-1.15.3-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/testcontainers/gcloud/1.15.3/gcloud-1.15.3-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/testcontainers/gcloud/1.15.3/gcloud-1.15.3-sources.jar", + "https://jcenter.bintray.com/org/testcontainers/gcloud/1.15.3/gcloud-1.15.3-sources.jar", + "https://maven.google.com/org/testcontainers/gcloud/1.15.3/gcloud-1.15.3-sources.jar" + ], + "sha256": "a0bbead284f364917fc3c763769c6b24faa0b1c8bf190b36239682acd20ffdc5", + "url": "https://repo1.maven.org/maven2/org/testcontainers/gcloud/1.15.3/gcloud-1.15.3-sources.jar" + }, + { + "coord": "org.testcontainers:junit-jupiter:1.15.3", + "dependencies": [ + "com.github.docker-java:docker-java-transport-zerodep:3.2.8", + "org.apiguardian:apiguardian-api:1.1.1", + "org.apache.commons:commons-compress:1.20", + "com.github.docker-java:docker-java-api:3.2.8", + "org.junit.jupiter:junit-jupiter-api:5.7.0", + "org.opentest4j:opentest4j:1.2.0", + "org.rnorth.visible-assertions:visible-assertions:2.1.2", + "net.java.dev.jna:jna:5.8.0", + "org.junit.platform:junit-platform-commons:1.8.0-M1", + "org.rnorth.duct-tape:duct-tape:1.0.8", + "org.testcontainers:testcontainers:1.15.3", + "junit:junit:4.13.2", + "com.github.docker-java:docker-java-transport:3.2.8", + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "org.slf4j:slf4j-api:1.7.31" + ], + "directDependencies": [ + "org.junit.jupiter:junit-jupiter-api:5.7.0", + "org.testcontainers:testcontainers:1.15.3" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/org/testcontainers/junit-jupiter/1.15.3/junit-jupiter-1.15.3.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/testcontainers/junit-jupiter/1.15.3/junit-jupiter-1.15.3.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/testcontainers/junit-jupiter/1.15.3/junit-jupiter-1.15.3.jar", + "https://jcenter.bintray.com/org/testcontainers/junit-jupiter/1.15.3/junit-jupiter-1.15.3.jar", + "https://maven.google.com/org/testcontainers/junit-jupiter/1.15.3/junit-jupiter-1.15.3.jar" + ], + "sha256": "452f3ebd60e1dfb3d44b001cbf0f2d1d85e42998fe1a8c66255fd51074a6d5d7", + "url": "https://repo1.maven.org/maven2/org/testcontainers/junit-jupiter/1.15.3/junit-jupiter-1.15.3.jar" + }, + { + "coord": "org.testcontainers:junit-jupiter:jar:sources:1.15.3", + "dependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "org.opentest4j:opentest4j:jar:sources:1.2.0", + "com.github.docker-java:docker-java-transport:jar:sources:3.2.8", + "com.github.docker-java:docker-java-api:jar:sources:3.2.8", + "junit:junit:jar:sources:4.13.2", + "org.apache.commons:commons-compress:jar:sources:1.20", + "org.rnorth.visible-assertions:visible-assertions:jar:sources:2.1.2", + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "com.github.docker-java:docker-java-transport-zerodep:jar:sources:3.2.8", + "org.junit.platform:junit-platform-commons:jar:sources:1.8.0-M1", + "org.testcontainers:testcontainers:jar:sources:1.15.3", + "org.rnorth.duct-tape:duct-tape:jar:sources:1.0.8", + "org.apiguardian:apiguardian-api:jar:sources:1.1.1", + "net.java.dev.jna:jna:jar:sources:5.8.0", + "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0" + ], + "directDependencies": [ + "org.junit.jupiter:junit-jupiter-api:jar:sources:5.7.0", + "org.testcontainers:testcontainers:jar:sources:1.15.3" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/org/testcontainers/junit-jupiter/1.15.3/junit-jupiter-1.15.3-sources.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/testcontainers/junit-jupiter/1.15.3/junit-jupiter-1.15.3-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/testcontainers/junit-jupiter/1.15.3/junit-jupiter-1.15.3-sources.jar", + "https://jcenter.bintray.com/org/testcontainers/junit-jupiter/1.15.3/junit-jupiter-1.15.3-sources.jar", + "https://maven.google.com/org/testcontainers/junit-jupiter/1.15.3/junit-jupiter-1.15.3-sources.jar" + ], + "sha256": "db3f90b88f93b1daa120516e81d8d9ea72f93429c4d5b9c6f5a7c9ac91d26f04", + "url": "https://repo1.maven.org/maven2/org/testcontainers/junit-jupiter/1.15.3/junit-jupiter-1.15.3-sources.jar" + }, + { + "coord": "org.testcontainers:testcontainers:1.15.3", + "dependencies": [ + "com.github.docker-java:docker-java-transport-zerodep:3.2.8", + "org.apache.commons:commons-compress:1.20", + "com.github.docker-java:docker-java-api:3.2.8", + "org.rnorth.visible-assertions:visible-assertions:2.1.2", + "net.java.dev.jna:jna:5.8.0", + "org.rnorth.duct-tape:duct-tape:1.0.8", + "junit:junit:4.13.2", + "com.github.docker-java:docker-java-transport:3.2.8", + "com.fasterxml.jackson.core:jackson-annotations:2.12.2", + "org.slf4j:slf4j-api:1.7.31" + ], + "directDependencies": [ + "com.github.docker-java:docker-java-transport-zerodep:3.2.8", + "org.apache.commons:commons-compress:1.20", + "com.github.docker-java:docker-java-api:3.2.8", + "org.rnorth.visible-assertions:visible-assertions:2.1.2", + "org.rnorth.duct-tape:duct-tape:1.0.8", + "junit:junit:4.13.2", + "org.slf4j:slf4j-api:1.7.31" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/org/testcontainers/testcontainers/1.15.3/testcontainers-1.15.3.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/testcontainers/testcontainers/1.15.3/testcontainers-1.15.3.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/testcontainers/testcontainers/1.15.3/testcontainers-1.15.3.jar", + "https://jcenter.bintray.com/org/testcontainers/testcontainers/1.15.3/testcontainers-1.15.3.jar", + "https://maven.google.com/org/testcontainers/testcontainers/1.15.3/testcontainers-1.15.3.jar" + ], + "sha256": "6c56448dad8649ce88285bda7c053e20ae7759b0a5ffcf30204ff717ec586c92", + "url": "https://repo1.maven.org/maven2/org/testcontainers/testcontainers/1.15.3/testcontainers-1.15.3.jar" + }, + { + "coord": "org.testcontainers:testcontainers:jar:sources:1.15.3", + "dependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "com.github.docker-java:docker-java-transport:jar:sources:3.2.8", + "com.github.docker-java:docker-java-api:jar:sources:3.2.8", + "junit:junit:jar:sources:4.13.2", + "org.apache.commons:commons-compress:jar:sources:1.20", + "org.rnorth.visible-assertions:visible-assertions:jar:sources:2.1.2", + "com.fasterxml.jackson.core:jackson-annotations:jar:sources:2.12.2", + "com.github.docker-java:docker-java-transport-zerodep:jar:sources:3.2.8", + "org.rnorth.duct-tape:duct-tape:jar:sources:1.0.8", + "net.java.dev.jna:jna:jar:sources:5.8.0" + ], + "directDependencies": [ + "org.slf4j:slf4j-api:jar:sources:1.7.31", + "com.github.docker-java:docker-java-api:jar:sources:3.2.8", + "junit:junit:jar:sources:4.13.2", + "org.apache.commons:commons-compress:jar:sources:1.20", + "org.rnorth.visible-assertions:visible-assertions:jar:sources:2.1.2", + "com.github.docker-java:docker-java-transport-zerodep:jar:sources:3.2.8", + "org.rnorth.duct-tape:duct-tape:jar:sources:1.0.8" + ], + "exclusions": [ + "com.google.template:soy", + "com.google.common.html.types:types" + ], + "file": "v1/https/repo1.maven.org/maven2/org/testcontainers/testcontainers/1.15.3/testcontainers-1.15.3-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30-sources.jar", - "https://jcenter.bintray.com/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30-sources.jar", - "https://maven.google.com/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30-sources.jar" + "https://repo1.maven.org/maven2/org/testcontainers/testcontainers/1.15.3/testcontainers-1.15.3-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/testcontainers/testcontainers/1.15.3/testcontainers-1.15.3-sources.jar", + "https://jcenter.bintray.com/org/testcontainers/testcontainers/1.15.3/testcontainers-1.15.3-sources.jar", + "https://maven.google.com/org/testcontainers/testcontainers/1.15.3/testcontainers-1.15.3-sources.jar" ], - "sha256": "9ee459644577590fed7ea94afae781fa3cc9311d4553faee8a3219ffbd7cc386", - "url": "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30-sources.jar" + "sha256": "fd513c2525e16a045a262b90ae37057b5bb8e5912ecce66dc3b03588d7ca6e34", + "url": "https://repo1.maven.org/maven2/org/testcontainers/testcontainers/1.15.3/testcontainers-1.15.3-sources.jar" }, { - "coord": "org.threeten:threetenbp:1.5.0", + "coord": "org.threeten:threetenbp:1.5.1", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/org/threeten/threetenbp/1.5.0/threetenbp-1.5.0.jar", + "file": "v1/https/repo1.maven.org/maven2/org/threeten/threetenbp/1.5.1/threetenbp-1.5.1.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/threeten/threetenbp/1.5.0/threetenbp-1.5.0.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/threeten/threetenbp/1.5.0/threetenbp-1.5.0.jar", - "https://jcenter.bintray.com/org/threeten/threetenbp/1.5.0/threetenbp-1.5.0.jar", - "https://maven.google.com/org/threeten/threetenbp/1.5.0/threetenbp-1.5.0.jar" + "https://repo1.maven.org/maven2/org/threeten/threetenbp/1.5.1/threetenbp-1.5.1.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/threeten/threetenbp/1.5.1/threetenbp-1.5.1.jar", + "https://jcenter.bintray.com/org/threeten/threetenbp/1.5.1/threetenbp-1.5.1.jar", + "https://maven.google.com/org/threeten/threetenbp/1.5.1/threetenbp-1.5.1.jar" ], - "sha256": "dcf9c0f940739f2a825cd8626ff27113459a2f6eb18797c7152f93fff69c264f", - "url": "https://repo1.maven.org/maven2/org/threeten/threetenbp/1.5.0/threetenbp-1.5.0.jar" + "sha256": "4342ee04d87040f71b0aa9188ee960780ef2da734e32a8d43a522a580b5e0f3b", + "url": "https://repo1.maven.org/maven2/org/threeten/threetenbp/1.5.1/threetenbp-1.5.1.jar" }, { - "coord": "org.threeten:threetenbp:jar:sources:1.5.0", + "coord": "org.threeten:threetenbp:jar:sources:1.5.1", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], - "file": "v1/https/repo1.maven.org/maven2/org/threeten/threetenbp/1.5.0/threetenbp-1.5.0-sources.jar", + "file": "v1/https/repo1.maven.org/maven2/org/threeten/threetenbp/1.5.1/threetenbp-1.5.1-sources.jar", "mirror_urls": [ - "https://repo1.maven.org/maven2/org/threeten/threetenbp/1.5.0/threetenbp-1.5.0-sources.jar", - "https://dl.bintray.com/micronaut/core-releases-local/org/threeten/threetenbp/1.5.0/threetenbp-1.5.0-sources.jar", - "https://jcenter.bintray.com/org/threeten/threetenbp/1.5.0/threetenbp-1.5.0-sources.jar", - "https://maven.google.com/org/threeten/threetenbp/1.5.0/threetenbp-1.5.0-sources.jar" + "https://repo1.maven.org/maven2/org/threeten/threetenbp/1.5.1/threetenbp-1.5.1-sources.jar", + "https://dl.bintray.com/micronaut/core-releases-local/org/threeten/threetenbp/1.5.1/threetenbp-1.5.1-sources.jar", + "https://jcenter.bintray.com/org/threeten/threetenbp/1.5.1/threetenbp-1.5.1-sources.jar", + "https://maven.google.com/org/threeten/threetenbp/1.5.1/threetenbp-1.5.1-sources.jar" ], - "sha256": "1daab1628114dcc8905ce8be8ef274ae082cad22750a719907a6ce2642c7bc93", - "url": "https://repo1.maven.org/maven2/org/threeten/threetenbp/1.5.0/threetenbp-1.5.0-sources.jar" + "sha256": "b477820948bb90e75ebeb6adf3107a6d09a424ecf15e302b24cc48e0dcbb585c", + "url": "https://repo1.maven.org/maven2/org/threeten/threetenbp/1.5.1/threetenbp-1.5.1-sources.jar" }, { "coord": "org.yaml:snakeyaml:1.26", @@ -13787,65 +15851,54 @@ "url": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/1.26/snakeyaml-1.26-sources.jar" }, { - "coord": "com.google.cloud:google-cloud-bom:0.142.0", - "dependencies": [], - "directDependencies": [], - "exclusions": [ - "com.google.guava:guava", - "com.google.template:soy", - "io.grpc:grpc-context", - "io.grpc:grpc-services", - "io.grpc:grpc-okhttp", - "io.grpc:grpc-protobuf-lite", - "io.grpc:grpc-api", - "io.grpc:grpc-auth", - "io.grpc:grpc-protobuf", - "io.grpc:grpc-stub", - "com.google.common.html.types:types", - "io.grpc:grpc-netty", - "io.grpc:grpc-core" - ], - "file": null - }, - { - "coord": "com.google.cloud:google-cloud-bom:jar:sources:0.142.0", + "coord": "com.google.cloud:google-cloud-bom:0.157.0", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], "file": null }, { - "coord": "com.google.guava:listenablefuture:jar:sources:9999.0-empty-to-avoid-conflict-with-guava", + "coord": "com.google.cloud:google-cloud-bom:jar:sources:0.157.0", "dependencies": [], "directDependencies": [], "exclusions": [ + "io.grpc:grpc-grpclb", "com.google.guava:guava", "com.google.template:soy", "io.grpc:grpc-context", "io.grpc:grpc-services", + "io.grpc:grpc-netty-shaded", "io.grpc:grpc-okhttp", "io.grpc:grpc-protobuf-lite", "io.grpc:grpc-api", + "com.google.api.grpc:grpc-google-common-protos", "io.grpc:grpc-auth", "io.grpc:grpc-protobuf", "io.grpc:grpc-stub", "com.google.common.html.types:types", "io.grpc:grpc-netty", - "io.grpc:grpc-core" + "io.grpc:grpc-alts", + "io.grpc:grpc-core", + "com.google.api:gax-grpc" ], "file": null }, diff --git a/package.json b/package.json index 7616c0db7..b5132c0ed 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "sass": "^1.35.1", "svgo": "2.3.1", "terser": ">=4.0.0 <5.0.0", + "tmp": "^0.2.1", "ts-node": "^8.6.2", "tsickle": "0.39.1", "typescript": "3.9.7", diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000..f45d8f110 --- /dev/null +++ b/renovate.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "config:base" + ] +} diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 0aae6e303..164ac8aec 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -15,6 +15,17 @@ package( default_visibility = ["//visibility:public"], ) + exports_files([ "karma_config.js", ]) + + +test_suite( + name = "suite", + testonly = True, + tests = [ +# "//tests/dom:suite", @TODO(sgammon): fix these + "//tests/style:suite", + ], +) diff --git a/tests/dom/BUILD.bazel b/tests/dom/BUILD.bazel index 2af06d6e5..392ed42e1 100644 --- a/tests/dom/BUILD.bazel +++ b/tests/dom/BUILD.bazel @@ -63,3 +63,11 @@ js_test( # srcs = ["DomBrowserTest.java"], # test_class = "javatests.dom.DomBrowserTest", #) + +test_suite( + name = "suite", + testonly = True, + tests = [ + ":basic-dom-js-test", + ], +) diff --git a/tests/style/BUILD.bazel b/tests/style/BUILD.bazel index 5df735869..a15a9dbfc 100644 --- a/tests/style/BUILD.bazel +++ b/tests/style/BUILD.bazel @@ -70,6 +70,8 @@ style_binary( name = "vendor", src = "vendor.scss", optimize = True, + debug = True, + output_style = "expanded", plugins = ["autoprefixer"], deps = [ ":example1", @@ -90,7 +92,8 @@ style_library( style_binary( name = "gss_example", plugins = ["autoprefixer"], - optimize = True, + debug = True, + optimize = False, deps = [ ":mod1", ":mod2", @@ -167,10 +170,7 @@ file_test( background: pink; } .hello { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; + user-select: none; } .goodbye { background: green; @@ -180,11 +180,24 @@ file_test( file_test( name = "gss_opt_default_test", file = ":gss_opt_default_example", - content = ".test{color:#00f}.hello{background:pink;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.goodbye{background:green}", + content = ".test{color:blue}.hello{background:pink;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.goodbye{background:green}", ) file_test( name = "gss_opt_advanced_test", file = ":gss_opt_advanced_example", - content = ".test{color:#00f}.hello{background:pink;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.goodbye{background:green}", + content = ".test{color:blue}.hello{background:pink;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.goodbye{background:green}", +) + + +test_suite( + name = "suite", + testonly = True, + tests = [ + ":basic_test", + ":compressed_test", + ":autoprefixer_test", + ":gss_opt_default_test", + ":gss_opt_advanced_test", + ], ) diff --git a/tools/bazel.rc b/tools/bazel.rc index 1f9338d62..5af35aee5 100644 --- a/tools/bazel.rc +++ b/tools/bazel.rc @@ -26,13 +26,8 @@ build --embed_label=alpha build --define project=elide-ai build --define cluster=_cluster_ build --nojava_header_compilation - -build:dev --define=project=elide-ai -build:dev --define=todolist_release_tag=latest -build:adc --google_default_credentials=true -#build:devkey --google_credentials=crypto/build-key.json -#test:devkey --action_env=GOOGLE_APPLICATION_CREDENTIALS - +build --noexperimental_worker_multiplex +build --output_filter='^//(gust|defs|java|js|tools|config|images|javatests|tests|jstests):' build --watchfs build --symlink_prefix=dist/ build --nolegacy_external_runfiles @@ -41,46 +36,64 @@ build --javacopt="-encoding UTF-8" build --strict_java_deps=strict build --use_ijars build --interface_shared_objects - build --java_toolchain=@bazel_tools//tools/jdk:toolchain_java11 build --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_java11 -#build --java_toolchain=@bazel_tools//tools/jdk:toolchain_vanilla -#build --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_vanilla - build --workspace_status_command=./tools/bazel_stamp_vars.sh +build --worker_sandboxing +build --spawn_strategy=sandboxed,worker +build --strategy=Javac=sandboxed +build --strategy=J2cl=sandboxed +build --strategy=Closure=sandboxed +build --strategy=KotlinCompile=sandboxed +build --strategy=TypeScriptCompile=sandboxed +build --strategy=JdepsMerger=sandboxed +build --compilation_mode=fastbuild +build --experimental_ui_mode=mnemonic_histogram + +build --build_metadata=TEST_GROUPS=//javatests,//jstests,//tests +build --build_metadata=REPO_URL=https://github.com/CookiesCo/elide.git +build --build_metadata=VISIBILITY=PUBLIC +build --bes_results_url=https://app.buildbuddy.io/invocation/ +build --bes_backend=grpcs://cloud.buildbuddy.io +build --remote_cache=grpcs://cloud.buildbuddy.io +build --remote_timeout=3600 +build --remote_header=x-buildbuddy-api-key=6R3w0oRMdKhN3cobqlIl +build --experimental_persistent_javac +build --strict_proto_deps=strict +build --strict_system_includes + +build:dev --define=project=elide-ai +build:dev --define=todolist_release_tag=latest +build:dev --strategy=Javac=worker +build:dev --strategy=J2cl=worker +build:dev --strategy=Closure=worker +build:dev --strategy=KotlinCompile=worker +build:dev --strategy=TypeScriptCompile=worker +build:dev --strategy=JdepsMerger=worker + +fetch:dev-local --repository_cache=~/.cache/bazel/repo +query:dev-local --repository_cache=~/.cache/bazel/repo +build:dev-local --disk_cache=~/.cache/bazel-disk-cache +build:dev-local --repository_cache=~/.cache/bazel/repo + +#test:dev --test_strategy=standalone +#test:dev --strategy=TestRunner=worker +#test:dev --experimental_persistent_test_runner + +build:adc --google_default_credentials=true build:android --java_toolchain=@bazel_tools//tools/jdk:toolchain_java8 build:android --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_java8 build:android --workspace_status_command=./tools/bazel_stamp_vars.sh -run --incompatible_strict_action_env -run --workspace_status_command=./tools/bazel_stamp_vars.sh - -build:dev --define=todolist_release_tag=latest - -build:ci --worker_sandboxing -build:ci --spawn_strategy=sandboxed -build:ci --strategy=SassCompiler=local -build:ci --strategy=Javac=sandboxed -build:ci --strategy=J2cl=sandboxed -build:ci --strategy=Closure=sandboxed -build:ci --strategy=KotlinCompile=sandboxed -build:ci --strategy=TypeScriptCompile=sandboxed build:ci --jobs=32 - -build:ci --bes_results_url=https://app.buildbuddy.io/invocation/ -build:ci --bes_backend=grpcs://cloud.buildbuddy.io -build:ci --remote_cache=grpcs://cloud.buildbuddy.io -build:ci --remote_timeout=3600 -build:ci --remote_header=x-buildbuddy-api-key=6R3w0oRMdKhN3cobqlIl +build:ci --build_metadata=ROLE=CI build:ci --worker_max_instances=4 build:ci --local_ram_resources="HOST_RAM*.9" -build:ci --experimental_persistent_javac build:ci --strategy=JdepsMerger=worker run:ci --worker_max_instances=4 run:ci --local_ram_resources="HOST_RAM*.9" -run:ci --experimental_persistent_javac common:ci --curses=no build:ci --verbose_failures build:ci --worker_verbose @@ -104,16 +117,6 @@ build:release --copt=-Wframe-larger-than=16384 build:debug --compilation_mode=dbg build:debug --sandbox_debug -build:dev --spawn_strategy=local -build:dev --strategy=J2cl=worker -build:dev --strategy=KotlinCompile=worker -build:dev --strategy=Closure=worker -build:dev --strategy=TypeScriptCompile=worker -build:dev --experimental_persistent_javac -#build:dev --define=ABSOLUTE_JAVABASE=/Library/Java/JavaVirtualMachines/zulu-15.jdk/Contents/Home -#build:dev --define=ZULUBASE=/Library/Java/JavaVirtualMachines/zulu-15.jdk/Contents/Home -#build:dev --javabase=//defs/toolchain/java:java_runtime -build:dev --disk_cache=~/.cache/bazel-disk-cache build:android --define=ABSOLUTE_JAVABASE=/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home build:android --host_javabase=@bazel_tools//tools/jdk:absolute_javabase @@ -122,59 +125,28 @@ build:android --java_toolchain=@bazel_tools//tools/jdk:toolchain_vanilla query --output=label_kind -# This .bazelrc file contains all of the flags required for the provided -# toolchain with Remote Build Execution. -# Note your WORKSPACE must contain an rbe_autoconfig target with -# name="rbe_default" to use these flags as-is. -build:remote --jobs=5 - -# Platform flags: -# The toolchain container used for execution is defined in the target indicated -# by "extra_execution_platforms", "host_platform" and "platforms". -# More about platforms: https://docs.bazel.build/versions/master/platforms.html -build:remote --extra_toolchains=@rbe_default//config:cc-toolchain -build:remote --extra_execution_platforms=@rbe_default//config:platform -build:remote --host_platform=@rbe_default//config:platform -build:remote --platforms=@rbe_default//config:platform -build:remote --host_javabase=@rbe_default//java:jdk -build:remote --javabase=@rbe_default//java:jdk -build:remote --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_hostjdk8 -build:remote --java_toolchain=@bazel_tools//tools/jdk:toolchain_hostjdk8 -build:remote --crosstool_top=@rbe_default//cc:toolchain -build:remote --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 -build:remote --spawn_strategy=remote - -# Starting with Bazel 0.27.0 strategies do not need to be explicitly -# defined. See https://github.com/bazelbuild/bazel/issues/7480 -build:remote --define=EXECUTOR=remote - -# Enable remote execution so actions are performed on the remote systems. -build:remote --remote_executor=grpcs://remotebuildexecution.googleapis.com - -# Enforce stricter environment rules, which eliminates some non-hermetic -# behavior and therefore improves both the remote cache hit rate and the -# correctness and repeatability of the build. -build:remote --incompatible_strict_action_env=true - -# Set a higher timeout value, just in case. -build:remote --remote_timeout=3600 - -# Enable authentication. This will pick up application default credentials by -# default. You can use --google_credentials=some_file.json to use a service -# account credential instead. -build:remote --google_default_credentials=true - -test --instrumentation_filter=//java/... -#test --test_env=GOOGLE_APPLICATION_CREDENTIALS -coverage --instrumentation_filter=//java/... +test:dev --test_output=errors +test --instrumentation_filter="-/javatests[/:],-/test/java[/:],-/tests[/:],-/jstests[/:],-/protobuf,-protobuf,-/types,-types,-/java/org[/:],-js,-/js" coverage --collect_code_coverage coverage --combined_report=lcov coverage --test_env=DISPLAY coverage --noinstrument_test_targets coverage --ui_event_filters=-DEBUG -coverage --nocache_test_results coverage --experimental_fetch_all_coverage_outputs #coverage --experimental_split_coverage_postprocessing +coverage:ci --nocache_test_results + +build:labs --features=thin_lto +build:labs --experimental_inmemory_dotd_files +build:labs --experimental_inmemory_jdeps_files +build:labs --experimental_delay_virtual_input_materialization +build:labs --experimental_enable_docker_sandbox +build:labs --experimental_sandbox_async_tree_delete_idle_threads=0 +build:labs --experimental_stream_log_file_uploads + +#build:labs --experimental_use_validation_aspect +#build:labs --experimental_use_sandboxfs=auto +#build:labs --experimental_java_classpath=bazel try-import %workspace%/.bazelrc.user diff --git a/tools/bundler/BUILD.bazel b/tools/bundler/BUILD.bazel index eb78199a7..554f2df81 100644 --- a/tools/bundler/BUILD.bazel +++ b/tools/bundler/BUILD.bazel @@ -63,6 +63,7 @@ jdk_binary( "@com_google_template_soy", maven("ch.qos.logback:logback-classic"), ], + enable_debug = False, classpath_resources = [ "//tools:logback.xml", ], diff --git a/tools/container-init.sh b/tools/container-init.sh new file mode 100755 index 000000000..f7a816b9c --- /dev/null +++ b/tools/container-init.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +## 1: copy dotfiles sources +echo ""; echo "Copying dotfiles..."; +mkdir -p ~/dotfiles; +cp -frv ./* ./.* ~/dotfiles/; + +## 2: run `rcup` +echo ""; echo "--- Running 'rcup'..."; +rcup; + +## 3: copy in any secrets +echo ""; echo "--- Setting up credentials..."; + +if [[ -z "${GOOGLE_CREDENTIALS}" ]]; then + echo "No Google credentials detected."; +else + echo "- Installing Google credentials..."; + mkdir -p "$HOME/.config/gcloud"; + echo "${GOOGLE_CREDENTIALS}" > "$HOME/.config/gcloud/application_default_credentials.json"; +fi + +if [[ -z "${BUILDBUDDY_CERT}" ]]; then + echo "No Buildbuddy certificate detected."; +else + echo "- Installing Buildbuddy certificate..."; + echo "${BUILDBUDDY_CERT}" > "$HOME/buildbuddy-cert.pem"; +fi + +if [[ -z "${BUILDBUDDY_KEY}" ]]; then + echo "No Buildbuddy key detected."; +else + echo "- Installing Buildbuddy key..."; + echo "${BUILDBUDDY_KEY}" > "$HOME/buildbuddy-key.pem"; +fi + +if [[ -z "${SSHKEY}" ]]; then + echo "No SSH key detected."; +else + echo "- Installing SSH key..."; + mkdir -p $HOME/.ssh; + echo "${SSHKEY}" > "$HOME/.ssh/id_rsa"; + chmod 600 $HOME/.ssh/id_rsa; + chown dev:engineering $HOME/.ssh/id_rsa; +fi + diff --git a/yarn.lock b/yarn.lock index bc6dfe40f..a54a75a2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3591,6 +3591,13 @@ tmp@0.0.33, tmp@0.0.x: dependencies: os-tmpdir "~1.0.2" +tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + to-array@0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" From dcd09205a41c36a65aa24a9d42d98ecb47b7159e Mon Sep 17 00:00:00 2001 From: codefactor-io Date: Wed, 16 Mar 2022 01:16:06 +0000 Subject: [PATCH 2/2] [CodeFactor] Apply fixes to commit 4d92d06 [ci skip] [skip ci] --- docs/java/jquery/jquery-ui.css | 2 +- docs/java/jquery/jquery-ui.structure.css | 2 +- docs/java/stylesheet.css | 50 ++++++++++++------------ 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/java/jquery/jquery-ui.css b/docs/java/jquery/jquery-ui.css index c4487b41c..c46726b62 100644 --- a/docs/java/jquery/jquery-ui.css +++ b/docs/java/jquery/jquery-ui.css @@ -121,7 +121,7 @@ height: 0; font-size: 0; line-height: 0; - border-width: 1px 0 0 0; + border-width: 1px 0 0; } .ui-menu .ui-state-focus, .ui-menu .ui-state-active { diff --git a/docs/java/jquery/jquery-ui.structure.css b/docs/java/jquery/jquery-ui.structure.css index d8c81c2b9..8eeb10bbf 100644 --- a/docs/java/jquery/jquery-ui.structure.css +++ b/docs/java/jquery/jquery-ui.structure.css @@ -125,7 +125,7 @@ height: 0; font-size: 0; line-height: 0; - border-width: 1px 0 0 0; + border-width: 1px 0 0; } .ui-menu .ui-state-focus, .ui-menu .ui-state-active { diff --git a/docs/java/stylesheet.css b/docs/java/stylesheet.css index fa246765c..d5f30c584 100644 --- a/docs/java/stylesheet.css +++ b/docs/java/stylesheet.css @@ -106,12 +106,12 @@ sup { */ .clear { clear:both; - height:0px; + height:0; overflow:hidden; } .aboutLanguage { float:right; - padding:0px 21px; + padding:0 21px; font-size:11px; z-index:200; margin-top:-9px; @@ -204,7 +204,7 @@ ul.navList li{ } ul.navListSearch { float:right; - margin:0 0 0 0; + margin:0; padding:0; } ul.navListSearch li { @@ -248,7 +248,7 @@ ul.subNavList li { .header, .footer { clear:both; margin:0 20px; - padding:5px 0 0 0; + padding:5px 0 0; } .indexNav { position:relative; @@ -273,14 +273,14 @@ ul.subNavList li { margin:10px 0; } .subTitle { - margin:5px 0 0 0; + margin:5px 0 0; } .header ul { - margin:0 0 15px 0; + margin:0 0 15px; padding:0; } .footer ul { - margin:20px 0 5px 0; + margin:20px 0 5px; } .header ul li, .footer ul li { list-style:none; @@ -306,7 +306,7 @@ ul.blockList ul.blockList li.blockList h3 { margin:15px 0; } ul.blockList li.blockList h2 { - padding:0px 0 20px 0; + padding:0 0 20px; } /* * Styles for page layout containers. @@ -324,7 +324,7 @@ ul.blockList li.blockList h2 { } .indexContainer h2 { font-size:13px; - padding:0 0 3px 0; + padding:0 0 3px; } .indexContainer ul { margin:0; @@ -337,11 +337,11 @@ ul.blockList li.blockList h2 { .contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt { font-size:12px; font-weight:bold; - margin:10px 0 0 0; + margin:10px 0 0; color:#4E4E4E; } .contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd { - margin:5px 0 10px 0px; + margin:5px 0 10px; font-size:14px; font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; } @@ -380,7 +380,7 @@ ul.inheritance li ul.inheritance { padding-top:1px; } ul.blockList, ul.blockListLast { - margin:10px 0 10px 0; + margin:10px 0; padding:0; } ul.blockList li.blockList, ul.blockListLast li.blockList { @@ -389,7 +389,7 @@ ul.blockList li.blockList, ul.blockListLast li.blockList { line-height:1.4; } ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList { - padding:0px 20px 5px 10px; + padding:0 20px 5px 10px; border:1px solid #ededed; background-color:#f8f8f8; } @@ -425,7 +425,7 @@ table tr td dl, table tr td dl dt, table tr td dl dd { border-bottom:1px solid #EEE; } .overviewSummary, .memberSummary, .requiresSummary, .packagesSummary, .providesSummary, .usesSummary { - padding:0px; + padding:0; } .overviewSummary caption, .memberSummary caption, .typeSummary caption, .useSummary caption, .constantsSummary caption, .deprecatedSummary caption, @@ -437,10 +437,10 @@ table tr td dl, table tr td dl dt, table tr td dl dd { font-weight:bold; clear:none; overflow:hidden; - padding:0px; + padding:0; padding-top:10px; padding-left:1px; - margin:0px; + margin:0; white-space:pre; } .overviewSummary caption a:link, .memberSummary caption a:link, .typeSummary caption a:link, @@ -508,9 +508,9 @@ table tr td dl, table tr td dl dt, table tr td dl dd { .packagesSummary caption span.tableTab, .packagesSummary caption span.activeTableTab, .overviewSummary caption span.tableTab, .overviewSummary caption span.activeTableTab, .typeSummary caption span.tableTab, .typeSummary caption span.activeTableTab { - padding-top:0px; - padding-left:0px; - padding-right:0px; + padding-top:0; + padding-left:0; + padding-right:0; background-image:none; float:none; display:inline; @@ -549,13 +549,13 @@ table tr td dl, table tr td dl dt, table tr td dl dd { .useSummary td, .constantsSummary td, .deprecatedSummary td, .requiresSummary td, .packagesSummary td, .providesSummary td, .usesSummary td { text-align:left; - padding:0px 0px 12px 10px; + padding:0 0 12px 10px; } th.colFirst, th.colSecond, th.colLast, th.colConstructorName, th.colDeprecatedItemName, .useSummary th, .constantsSummary th, .packagesSummary th, td.colFirst, td.colSecond, td.colLast, .useSummary td, .constantsSummary td { vertical-align:top; - padding-right:0px; + padding-right:0; padding-top:8px; padding-bottom:3px; } @@ -633,7 +633,7 @@ div.block { font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; } td.colLast div { - padding-top:0px; + padding-top:0; } td.colLast a { padding-bottom:3px; @@ -652,7 +652,7 @@ h1.hidden { } .block { display:block; - margin:3px 10px 2px 0px; + margin:3px 10px 2px 0; color:#474747; } .deprecatedLabel, .descfrmTypeLabel, .implementationLabel, .memberNameLabel, .memberNameLink, @@ -680,7 +680,7 @@ div.block div.block span.interfaceName { font-style:normal; } div.contentContainer ul.blockList li.blockList h2 { - padding-bottom:0px; + padding-bottom:0; } /* * Styles for IFRAME. @@ -797,7 +797,7 @@ ul.ui-autocomplete li { position:relative; left:-4px; top:-4px; - font-size:0px; + font-size:0; } .watermark { color:#545454;