From 54edbaac53efd459825e1587a0308878f960da38 Mon Sep 17 00:00:00 2001 From: iProdigy Date: Sun, 24 Sep 2023 11:59:33 -0700 Subject: [PATCH 01/19] feat: add jackson module --- jackson/build.gradle.kts | 11 + .../jackson/databind/cfg/CacheProvider.java | 221 ++++++++++++++++++ .../jackson/XanthicJacksonCacheAdapter.java | 72 ++++++ .../jackson/XanthicJacksonCacheProvider.java | 72 ++++++ .../jackson/util/SerializableConsumer.java | 7 + settings.gradle.kts | 2 + 6 files changed, 385 insertions(+) create mode 100644 jackson/build.gradle.kts create mode 100644 jackson/src/main/java/com/fasterxml/jackson/databind/cfg/CacheProvider.java create mode 100644 jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheAdapter.java create mode 100644 jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java create mode 100644 jackson/src/main/java/io/github/xanthic/jackson/util/SerializableConsumer.java diff --git a/jackson/build.gradle.kts b/jackson/build.gradle.kts new file mode 100644 index 00000000..8a426bb9 --- /dev/null +++ b/jackson/build.gradle.kts @@ -0,0 +1,11 @@ +dependencies { + api(project(":cache-core")) + implementation("com.fasterxml.jackson.core:jackson-databind:2.15.2") // TODO: 2.16.0 +} + +publishing.publications.withType { + pom { + name.set("Xanthic - Jackson") + description.set("Xanthic Cache Jackson Adapter") + } +} diff --git a/jackson/src/main/java/com/fasterxml/jackson/databind/cfg/CacheProvider.java b/jackson/src/main/java/com/fasterxml/jackson/databind/cfg/CacheProvider.java new file mode 100644 index 00000000..a617996a --- /dev/null +++ b/jackson/src/main/java/com/fasterxml/jackson/databind/cfg/CacheProvider.java @@ -0,0 +1,221 @@ +/* + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 FasterXML, LLC. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package com.fasterxml.jackson.databind.cfg; + +import com.fasterxml.jackson.databind.DeserializationConfig; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.util.LookupCache; +import com.fasterxml.jackson.databind.util.TypeKey; + +import java.io.Serializable; + +// TODO: remove +public interface CacheProvider extends Serializable { + LookupCache> forDeserializerCache(DeserializationConfig config); + LookupCache> forSerializerCache(SerializationConfig config); + LookupCache forTypeFactory(); +} diff --git a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheAdapter.java b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheAdapter.java new file mode 100644 index 00000000..d96e906c --- /dev/null +++ b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheAdapter.java @@ -0,0 +1,72 @@ +package io.github.xanthic.jackson; + +import com.fasterxml.jackson.databind.util.LookupCache; +import io.github.xanthic.cache.api.Cache; +import io.github.xanthic.cache.core.CacheApi; +import io.github.xanthic.cache.core.CacheApiSpec; +import lombok.RequiredArgsConstructor; +import lombok.Value; +import org.jetbrains.annotations.NotNull; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +@Value +@RequiredArgsConstructor +public class XanthicJacksonCacheAdapter implements LookupCache { + + /** + * The Xanthic cache to use as a Jackson {@link LookupCache}. + */ + Cache cache; + + /** + * The specification associated with the constructed cache. + */ + Consumer> spec; + + /** + * Creates a Jackson {@link LookupCache} by wrapping a Xanthic cache with this adapter. + * + * @param spec the cache specification (note: specifying {@link CacheApiSpec#maxSize(Long)} is recommended) + */ + public XanthicJacksonCacheAdapter(@NotNull Consumer> spec) { + this(CacheApi.create(spec), spec); + } + + @Override + public int size() { + return (int) cache.size(); + } + + @Override + @SuppressWarnings("unchecked") + public V get(Object key) { + return cache.get((K) key); + } + + @Override + public V put(K key, V value) { + return cache.put(key, value); + } + + @Override + public V putIfAbsent(K key, V value) { + return cache.putIfAbsent(key, value); + } + + @Override + public void clear() { + cache.clear(); + } + + // TODO: @Override + public void contents(BiConsumer consumer) { + cache.forEach(consumer); + } + + // TODO: @Override + public XanthicJacksonCacheAdapter emptyCopy() { + return new XanthicJacksonCacheAdapter<>(spec); + } +} diff --git a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java new file mode 100644 index 00000000..06fe5a5c --- /dev/null +++ b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java @@ -0,0 +1,72 @@ +package io.github.xanthic.jackson; + +import com.fasterxml.jackson.databind.DeserializationConfig; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.cfg.CacheProvider; +import com.fasterxml.jackson.databind.ser.SerializerCache; +import com.fasterxml.jackson.databind.util.LookupCache; +import com.fasterxml.jackson.databind.util.TypeKey; +import io.github.xanthic.cache.core.CacheApiSpec; +import io.github.xanthic.jackson.util.SerializableConsumer; +import lombok.RequiredArgsConstructor; +import lombok.Value; + +@Value +@RequiredArgsConstructor +public class XanthicJacksonCacheProvider implements CacheProvider { + private static final long serialVersionUID = 1L; + + /** + * Specification for the deserializer cache. + */ + SerializableConsumer>> deserializationSpec; + + /** + * Specification for the serializer cache. + */ + SerializableConsumer>> serializationSpec; + + /** + * Specification for the type factory cache. + */ + SerializableConsumer> typeFactorySpec; + + /** + * Creates a Jackson {@link CacheProvider} backed by Xanthic, using the specified max cache sizes. + * + * @param maxDeserializerCacheSize the maximum size of the deserializer cache + * @param maxSerializerCacheSize the maximum size of the serializer cache + * @param maxTypeFactoryCacheSize the maximum size of the type factory cache + */ + public XanthicJacksonCacheProvider(long maxDeserializerCacheSize, long maxSerializerCacheSize, long maxTypeFactoryCacheSize) { + this.deserializationSpec = spec -> spec.maxSize(maxDeserializerCacheSize); + this.serializationSpec = spec -> spec.maxSize(maxSerializerCacheSize); + this.typeFactorySpec = spec -> spec.maxSize(maxTypeFactoryCacheSize); + } + + /** + * Creates a Jackson {@link CacheProvider} backed by Xanthic, using Jackson's recommended default max cache sizes. + */ + public XanthicJacksonCacheProvider() { + // TODO: this(DeserializerCache.DEFAULT_MAX_CACHE_SIZE, SerializerCache.DEFAULT_MAX_CACHE_SIZE, TypeFactory.DEFAULT_MAX_CACHE_SIZE); + this(2000, SerializerCache.DEFAULT_MAX_CACHED, 200); + } + + @Override + public LookupCache> forDeserializerCache(DeserializationConfig config) { + return new XanthicJacksonCacheAdapter<>(deserializationSpec); + } + + @Override + public LookupCache> forSerializerCache(SerializationConfig config) { + return new XanthicJacksonCacheAdapter<>(serializationSpec); + } + + @Override + public LookupCache forTypeFactory() { + return new XanthicJacksonCacheAdapter<>(typeFactorySpec); + } +} diff --git a/jackson/src/main/java/io/github/xanthic/jackson/util/SerializableConsumer.java b/jackson/src/main/java/io/github/xanthic/jackson/util/SerializableConsumer.java new file mode 100644 index 00000000..e06008dc --- /dev/null +++ b/jackson/src/main/java/io/github/xanthic/jackson/util/SerializableConsumer.java @@ -0,0 +1,7 @@ +package io.github.xanthic.jackson.util; + +import java.io.Serializable; +import java.util.function.Consumer; + +@FunctionalInterface +public interface SerializableConsumer extends Consumer, Serializable {} diff --git a/settings.gradle.kts b/settings.gradle.kts index 33230989..de55c181 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,6 +5,7 @@ include( ":api", ":core", ":kotlin", + ":jackson", ":spring", ":spring-java17", ":provider-androidx", @@ -22,6 +23,7 @@ project(":bom").name = "cache-bom" project(":api").name = "cache-api" project(":core").name = "cache-core" project(":kotlin").name = "cache-kotlin" +project(":jackson").name = "cache-jackson" project(":spring").name = "cache-spring" project(":spring-java17").name = "cache-spring-java17" project(":provider-androidx").name = "cache-provider-androidx" From 99963bad8e744ca591b8197c6a20bd7ffb00c460 Mon Sep 17 00:00:00 2001 From: iProdigy Date: Sun, 24 Sep 2023 18:48:09 -0700 Subject: [PATCH 02/19] docs: describe the utility of each class --- .../xanthic/jackson/XanthicJacksonCacheAdapter.java | 8 ++++++++ .../xanthic/jackson/XanthicJacksonCacheProvider.java | 7 +++++++ .../github/xanthic/jackson/util/SerializableConsumer.java | 6 ++++++ 3 files changed, 21 insertions(+) diff --git a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheAdapter.java b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheAdapter.java index d96e906c..a35b9494 100644 --- a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheAdapter.java +++ b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheAdapter.java @@ -11,6 +11,14 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; +/** + * Wraps a Xanthic {@link Cache} for use as a Jackson {@link LookupCache}. + *

+ * Most users should utilize {@link XanthicJacksonCacheProvider} rather than directly interact with this class. + * + * @param The type of keys that form the cache + * @param The type of values contained in the cache + */ @Value @RequiredArgsConstructor public class XanthicJacksonCacheAdapter implements LookupCache { diff --git a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java index 06fe5a5c..882fee93 100644 --- a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java +++ b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java @@ -14,6 +14,13 @@ import lombok.RequiredArgsConstructor; import lombok.Value; +/** + * Implementation of Jackson's {@link CacheProvider} that yields Xanthic {@link io.github.xanthic.cache.api.Cache} instances, + * which are backed by any cache implementation of your choosing. + *

+ * Example usage: + * {@code ObjectMapper mapper = JsonMapper.builder().cacheProvider(new XanthicJacksonCacheProvider()).build(); } + */ @Value @RequiredArgsConstructor public class XanthicJacksonCacheProvider implements CacheProvider { diff --git a/jackson/src/main/java/io/github/xanthic/jackson/util/SerializableConsumer.java b/jackson/src/main/java/io/github/xanthic/jackson/util/SerializableConsumer.java index e06008dc..468a98de 100644 --- a/jackson/src/main/java/io/github/xanthic/jackson/util/SerializableConsumer.java +++ b/jackson/src/main/java/io/github/xanthic/jackson/util/SerializableConsumer.java @@ -3,5 +3,11 @@ import java.io.Serializable; import java.util.function.Consumer; +/** + * A serializable {@link Consumer} since {@link com.fasterxml.jackson.databind.cfg.CacheProvider} + * must be {@link Serializable}, as it is stored in {@link com.fasterxml.jackson.databind.ObjectMapper}. + * + * @param the type of the input for the consumer + */ @FunctionalInterface public interface SerializableConsumer extends Consumer, Serializable {} From e2ca29f96d4db5f8b7d2dd97ed70a817bc810caf Mon Sep 17 00:00:00 2001 From: iProdigy <8106344+iProdigy@users.noreply.github.com> Date: Fri, 20 Oct 2023 20:44:52 -0700 Subject: [PATCH 03/19] chore: build with 2.16 rc1 Signed-off-by: iProdigy <8106344+iProdigy@users.noreply.github.com> --- jackson/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jackson/build.gradle.kts b/jackson/build.gradle.kts index 8a426bb9..13138f92 100644 --- a/jackson/build.gradle.kts +++ b/jackson/build.gradle.kts @@ -1,6 +1,6 @@ dependencies { api(project(":cache-core")) - implementation("com.fasterxml.jackson.core:jackson-databind:2.15.2") // TODO: 2.16.0 + implementation("com.fasterxml.jackson.core:jackson-databind:2.16.0-rc1") } publishing.publications.withType { From 04f799cbc67d7f060057c489f43e167f240e0bb8 Mon Sep 17 00:00:00 2001 From: iProdigy <8106344+iProdigy@users.noreply.github.com> Date: Fri, 20 Oct 2023 20:45:48 -0700 Subject: [PATCH 04/19] chore: remove duplicate jackson class Signed-off-by: iProdigy <8106344+iProdigy@users.noreply.github.com> --- .../jackson/databind/cfg/CacheProvider.java | 221 ------------------ 1 file changed, 221 deletions(-) delete mode 100644 jackson/src/main/java/com/fasterxml/jackson/databind/cfg/CacheProvider.java diff --git a/jackson/src/main/java/com/fasterxml/jackson/databind/cfg/CacheProvider.java b/jackson/src/main/java/com/fasterxml/jackson/databind/cfg/CacheProvider.java deleted file mode 100644 index a617996a..00000000 --- a/jackson/src/main/java/com/fasterxml/jackson/databind/cfg/CacheProvider.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2023 FasterXML, LLC. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -package com.fasterxml.jackson.databind.cfg; - -import com.fasterxml.jackson.databind.DeserializationConfig; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializationConfig; -import com.fasterxml.jackson.databind.util.LookupCache; -import com.fasterxml.jackson.databind.util.TypeKey; - -import java.io.Serializable; - -// TODO: remove -public interface CacheProvider extends Serializable { - LookupCache> forDeserializerCache(DeserializationConfig config); - LookupCache> forSerializerCache(SerializationConfig config); - LookupCache forTypeFactory(); -} From e99cd69412b7f5ddfd555c3a8fd4c173b683e381 Mon Sep 17 00:00:00 2001 From: iProdigy <8106344+iProdigy@users.noreply.github.com> Date: Fri, 20 Oct 2023 20:47:01 -0700 Subject: [PATCH 05/19] chore: add override annotations in XanthicJacksonCacheAdapter Signed-off-by: iProdigy <8106344+iProdigy@users.noreply.github.com> --- .../io/github/xanthic/jackson/XanthicJacksonCacheAdapter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheAdapter.java b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheAdapter.java index a35b9494..f42c2959 100644 --- a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheAdapter.java +++ b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheAdapter.java @@ -68,12 +68,12 @@ public void clear() { cache.clear(); } - // TODO: @Override + @Override public void contents(BiConsumer consumer) { cache.forEach(consumer); } - // TODO: @Override + @Override public XanthicJacksonCacheAdapter emptyCopy() { return new XanthicJacksonCacheAdapter<>(spec); } From b8b48f899aad9e83c7e96badcee77eeb37cb63e2 Mon Sep 17 00:00:00 2001 From: iProdigy <8106344+iProdigy@users.noreply.github.com> Date: Fri, 20 Oct 2023 20:49:29 -0700 Subject: [PATCH 06/19] chore: use named constants for default cache sizes Signed-off-by: iProdigy <8106344+iProdigy@users.noreply.github.com> --- .../io/github/xanthic/jackson/XanthicJacksonCacheProvider.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java index 882fee93..e88a8d8a 100644 --- a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java +++ b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java @@ -58,8 +58,7 @@ public XanthicJacksonCacheProvider(long maxDeserializerCacheSize, long maxSerial * Creates a Jackson {@link CacheProvider} backed by Xanthic, using Jackson's recommended default max cache sizes. */ public XanthicJacksonCacheProvider() { - // TODO: this(DeserializerCache.DEFAULT_MAX_CACHE_SIZE, SerializerCache.DEFAULT_MAX_CACHE_SIZE, TypeFactory.DEFAULT_MAX_CACHE_SIZE); - this(2000, SerializerCache.DEFAULT_MAX_CACHED, 200); + this(DeserializerCache.DEFAULT_MAX_CACHE_SIZE, SerializerCache.DEFAULT_MAX_CACHE_SIZE, TypeFactory.DEFAULT_MAX_CACHE_SIZE); } @Override From 0d0eea4edebf0e489edc4347147c20f0723fd9ba Mon Sep 17 00:00:00 2001 From: iProdigy <8106344+iProdigy@users.noreply.github.com> Date: Fri, 20 Oct 2023 20:54:39 -0700 Subject: [PATCH 07/19] chore: import TypeFactory Signed-off-by: iProdigy <8106344+iProdigy@users.noreply.github.com> --- .../io/github/xanthic/jackson/XanthicJacksonCacheProvider.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java index e88a8d8a..3a7ee5a1 100644 --- a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java +++ b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.cfg.CacheProvider; import com.fasterxml.jackson.databind.ser.SerializerCache; +import com.fasterxml.jackson.databind.type.TypeFactory; import com.fasterxml.jackson.databind.util.LookupCache; import com.fasterxml.jackson.databind.util.TypeKey; import io.github.xanthic.cache.core.CacheApiSpec; From 4467f21088894cedb6ee4c17f9a165169cf87da6 Mon Sep 17 00:00:00 2001 From: iProdigy <8106344+iProdigy@users.noreply.github.com> Date: Fri, 20 Oct 2023 20:59:42 -0700 Subject: [PATCH 08/19] chore: import DeserializerCache Signed-off-by: iProdigy <8106344+iProdigy@users.noreply.github.com> --- .../io/github/xanthic/jackson/XanthicJacksonCacheProvider.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java index 3a7ee5a1..250e8b9c 100644 --- a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java +++ b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.cfg.CacheProvider; +import com.fasterxml.jackson.databind.deser.DeserializerCache; import com.fasterxml.jackson.databind.ser.SerializerCache; import com.fasterxml.jackson.databind.type.TypeFactory; import com.fasterxml.jackson.databind.util.LookupCache; From 7c9e962fcf58253f26ee64581ee93460dc7de04f Mon Sep 17 00:00:00 2001 From: iProdigy Date: Fri, 20 Oct 2023 21:57:21 -0700 Subject: [PATCH 09/19] chore: declare rich version --- jackson/build.gradle.kts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jackson/build.gradle.kts b/jackson/build.gradle.kts index 13138f92..58c6abc6 100644 --- a/jackson/build.gradle.kts +++ b/jackson/build.gradle.kts @@ -1,6 +1,10 @@ dependencies { api(project(":cache-core")) - implementation("com.fasterxml.jackson.core:jackson-databind:2.16.0-rc1") + implementation("com.fasterxml.jackson.core:jackson-databind") { + version { + require("2.16.0-rc1") // imposes a lower bound on acceptable versions + } + } } publishing.publications.withType { From 33cbe1de842209f6fb462fe4f055cd93e7e15008 Mon Sep 17 00:00:00 2001 From: iProdigy Date: Fri, 20 Oct 2023 22:06:35 -0700 Subject: [PATCH 10/19] feat: add XanthicJacksonCacheProvider.DEFAULT_INSTANCE --- .../xanthic/jackson/XanthicJacksonCacheProvider.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java index 250e8b9c..272d6f4c 100644 --- a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java +++ b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java @@ -21,12 +21,13 @@ * which are backed by any cache implementation of your choosing. *

* Example usage: - * {@code ObjectMapper mapper = JsonMapper.builder().cacheProvider(new XanthicJacksonCacheProvider()).build(); } + * {@code ObjectMapper mapper = JsonMapper.builder().cacheProvider(XanthicJacksonCacheProvider.defaults()).build(); } */ @Value @RequiredArgsConstructor public class XanthicJacksonCacheProvider implements CacheProvider { private static final long serialVersionUID = 1L; + private static final XanthicJacksonCacheProvider DEFAULT_INSTANCE = new XanthicJacksonCacheProvider(); /** * Specification for the deserializer cache. @@ -59,7 +60,7 @@ public XanthicJacksonCacheProvider(long maxDeserializerCacheSize, long maxSerial /** * Creates a Jackson {@link CacheProvider} backed by Xanthic, using Jackson's recommended default max cache sizes. */ - public XanthicJacksonCacheProvider() { + private XanthicJacksonCacheProvider() { this(DeserializerCache.DEFAULT_MAX_CACHE_SIZE, SerializerCache.DEFAULT_MAX_CACHE_SIZE, TypeFactory.DEFAULT_MAX_CACHE_SIZE); } @@ -77,4 +78,11 @@ public LookupCache> forSerializerCache(Serializa public LookupCache forTypeFactory() { return new XanthicJacksonCacheAdapter<>(typeFactorySpec); } + + /** + * @return a Jackson {@link CacheProvider} backed by Xanthic, using Jackson's recommended default max cache sizes. + */ + public static XanthicJacksonCacheProvider defaults() { + return DEFAULT_INSTANCE; + } } From 86d124515d51172355b04384e913d0b8b1316945 Mon Sep 17 00:00:00 2001 From: iProdigy Date: Fri, 20 Oct 2023 22:29:32 -0700 Subject: [PATCH 11/19] chore: add basic tests --- jackson/build.gradle.kts | 1 + .../XanthicJacksonCacheProviderTest.java | 57 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java diff --git a/jackson/build.gradle.kts b/jackson/build.gradle.kts index 58c6abc6..27a414bb 100644 --- a/jackson/build.gradle.kts +++ b/jackson/build.gradle.kts @@ -5,6 +5,7 @@ dependencies { require("2.16.0-rc1") // imposes a lower bound on acceptable versions } } + testImplementation(project(":cache-provider-caffeine")) } publishing.publications.withType { diff --git a/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java b/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java new file mode 100644 index 00000000..a30f6569 --- /dev/null +++ b/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java @@ -0,0 +1,57 @@ +package io.github.xanthic.jackson; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class XanthicJacksonCacheProviderTest { + + @Test + void deserialize() throws JsonProcessingException { + ObjectMapper mapper = JsonMapper.builder() + .cacheProvider(XanthicJacksonCacheProvider.defaults()) + .build(); + Foo foo = mapper.readValue("{\"bar\":\"baz\"}", Foo.class); + assertNotNull(foo); + assertEquals("baz", foo.getBar()); + } + + @Test + void serialize() throws JsonProcessingException { + ObjectMapper mapper = JsonMapper.builder() + .cacheProvider(XanthicJacksonCacheProvider.defaults()) + .build(); + String json = mapper.writeValueAsString(new Foo("baz")); + assertEquals("{\"bar\":\"baz\"}", json); + } + + @Test + void constructType() { + ObjectMapper mapper = JsonMapper.builder() + .cacheProvider(XanthicJacksonCacheProvider.defaults()) + .build(); + JavaType type = mapper.getTypeFactory().constructParametricType(List.class, Integer.class); + assertNotNull(type); + } + + @Data + @Setter(AccessLevel.PRIVATE) + @NoArgsConstructor + @AllArgsConstructor + static class Foo { + private String bar; + } + +} From 55dd5731ee93dbd0afec35255ff862a59a3cd6dd Mon Sep 17 00:00:00 2001 From: iProdigy Date: Fri, 20 Oct 2023 23:09:04 -0700 Subject: [PATCH 12/19] chore(tests): ensure cache was used --- build.gradle.kts | 1 + .../XanthicJacksonCacheProviderTest.java | 32 ++++- .../xanthic/jackson/util/TrackedCache.java | 112 ++++++++++++++++++ .../jackson/util/TrackedCacheProvider.java | 22 ++++ 4 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 jackson/src/test/java/io/github/xanthic/jackson/util/TrackedCache.java create mode 100644 jackson/src/test/java/io/github/xanthic/jackson/util/TrackedCacheProvider.java diff --git a/build.gradle.kts b/build.gradle.kts index 383404af..8046e1ca 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -47,6 +47,7 @@ subprojects { dependencies { // annotations compileOnly("org.jetbrains:annotations:24.0.1") + testCompileOnly("org.jetbrains:annotations:24.0.1") // tests testImplementation(platform("org.junit:junit-bom:5.10.0")) diff --git a/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java b/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java index a30f6569..fbcc8b6b 100644 --- a/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java +++ b/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java @@ -3,7 +3,14 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.cfg.CacheProvider; +import com.fasterxml.jackson.databind.deser.DeserializerCache; import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.ser.SerializerCache; +import com.fasterxml.jackson.databind.type.TypeFactory; +import io.github.xanthic.cache.provider.caffeine.CaffeineProvider; +import io.github.xanthic.jackson.util.TrackedCache; +import io.github.xanthic.jackson.util.TrackedCacheProvider; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Data; @@ -14,36 +21,55 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; class XanthicJacksonCacheProviderTest { @Test void deserialize() throws JsonProcessingException { + TrackedCacheProvider provider = new TrackedCacheProvider(new CaffeineProvider()); ObjectMapper mapper = JsonMapper.builder() - .cacheProvider(XanthicJacksonCacheProvider.defaults()) + .cacheProvider(createCacheProvider(provider)) .build(); + assertFalse(provider.getConstructedCaches().stream().anyMatch(TrackedCache::hasInteraction)); Foo foo = mapper.readValue("{\"bar\":\"baz\"}", Foo.class); assertNotNull(foo); assertEquals("baz", foo.getBar()); + assertTrue(provider.getConstructedCaches().stream().anyMatch(TrackedCache::hasInteraction)); } @Test void serialize() throws JsonProcessingException { + TrackedCacheProvider provider = new TrackedCacheProvider(new CaffeineProvider()); ObjectMapper mapper = JsonMapper.builder() - .cacheProvider(XanthicJacksonCacheProvider.defaults()) + .cacheProvider(createCacheProvider(provider)) .build(); + assertFalse(provider.getConstructedCaches().stream().anyMatch(TrackedCache::hasInteraction)); String json = mapper.writeValueAsString(new Foo("baz")); assertEquals("{\"bar\":\"baz\"}", json); + assertTrue(provider.getConstructedCaches().stream().anyMatch(TrackedCache::hasInteraction)); } @Test void constructType() { + TrackedCacheProvider provider = new TrackedCacheProvider(new CaffeineProvider()); ObjectMapper mapper = JsonMapper.builder() - .cacheProvider(XanthicJacksonCacheProvider.defaults()) + .cacheProvider(createCacheProvider(provider)) .build(); + assertFalse(provider.getConstructedCaches().stream().anyMatch(TrackedCache::hasInteraction)); JavaType type = mapper.getTypeFactory().constructParametricType(List.class, Integer.class); assertNotNull(type); + assertTrue(provider.getConstructedCaches().stream().anyMatch(TrackedCache::hasInteraction)); + } + + private static CacheProvider createCacheProvider(TrackedCacheProvider trackedProvider) { + return new XanthicJacksonCacheProvider( + spec -> spec.provider(trackedProvider).maxSize((long) DeserializerCache.DEFAULT_MAX_CACHE_SIZE), + spec -> spec.provider(trackedProvider).maxSize((long) SerializerCache.DEFAULT_MAX_CACHE_SIZE), + spec -> spec.provider(trackedProvider).maxSize((long) TypeFactory.DEFAULT_MAX_CACHE_SIZE) + ); } @Data diff --git a/jackson/src/test/java/io/github/xanthic/jackson/util/TrackedCache.java b/jackson/src/test/java/io/github/xanthic/jackson/util/TrackedCache.java new file mode 100644 index 00000000..8e61395b --- /dev/null +++ b/jackson/src/test/java/io/github/xanthic/jackson/util/TrackedCache.java @@ -0,0 +1,112 @@ +package io.github.xanthic.jackson.util; + +import io.github.xanthic.cache.api.Cache; +import lombok.Value; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; + +@Value +public class TrackedCache implements Cache { + Cache underlyingCache; + AtomicBoolean interacted = new AtomicBoolean(); + + public boolean hasInteraction() { + return interacted.get(); + } + + @Override + public @Nullable V get(@NotNull K key) { + interacted.set(true); + return underlyingCache.get(key); + } + + @Override + public @Nullable V put(@NotNull K key, @NotNull V value) { + interacted.set(true); + return underlyingCache.put(key, value); + } + + @Override + public @Nullable V remove(@NotNull K key) { + interacted.set(true); + return underlyingCache.remove(key); + } + + @Override + public void clear() { + interacted.set(true); + underlyingCache.clear(); + } + + @Override + public long size() { + interacted.set(true); + return underlyingCache.size(); + } + + @Override + public @Nullable V compute(@NotNull K key, @NotNull BiFunction computeFunc) { + interacted.set(true); + return underlyingCache.compute(key, computeFunc); + } + + @Override + public V computeIfAbsent(@NotNull K key, @NotNull Function computeFunc) { + interacted.set(true); + return underlyingCache.computeIfAbsent(key, computeFunc); + } + + @Override + public @Nullable V computeIfPresent(@NotNull K key, @NotNull BiFunction computeFunc) { + interacted.set(true); + return underlyingCache.computeIfPresent(key, computeFunc); + } + + @Override + public @Nullable V putIfAbsent(@NotNull K key, @NotNull V value) { + interacted.set(true); + return underlyingCache.putIfAbsent(key, value); + } + + @Override + public V merge(@NotNull K key, @NotNull V value, @NotNull BiFunction mergeFunc) { + interacted.set(true); + return underlyingCache.merge(key, value, mergeFunc); + } + + @Override + public boolean replace(@NotNull K key, @NotNull V value) { + interacted.set(true); + return underlyingCache.replace(key, value); + } + + @Override + public boolean replace(@NotNull K key, @NotNull V oldValue, @NotNull V newValue) { + interacted.set(true); + return underlyingCache.replace(key, oldValue, newValue); + } + + @Override + public @NotNull V getOrDefault(@NotNull K key, @NotNull V defaultValue) { + interacted.set(true); + return underlyingCache.getOrDefault(key, defaultValue); + } + + @Override + public void putAll(@NotNull Map map) { + interacted.set(true); + underlyingCache.putAll(map); + } + + @Override + public void forEach(@NotNull BiConsumer action) { + interacted.set(true); + underlyingCache.forEach(action); + } +} diff --git a/jackson/src/test/java/io/github/xanthic/jackson/util/TrackedCacheProvider.java b/jackson/src/test/java/io/github/xanthic/jackson/util/TrackedCacheProvider.java new file mode 100644 index 00000000..fe2fd1b2 --- /dev/null +++ b/jackson/src/test/java/io/github/xanthic/jackson/util/TrackedCacheProvider.java @@ -0,0 +1,22 @@ +package io.github.xanthic.jackson.util; + +import io.github.xanthic.cache.api.Cache; +import io.github.xanthic.cache.api.CacheProvider; +import io.github.xanthic.cache.api.ICacheSpec; +import lombok.Value; + +import java.util.ArrayList; +import java.util.List; + +@Value +public class TrackedCacheProvider implements CacheProvider { + CacheProvider underlyingProvider; + List> constructedCaches = new ArrayList<>(); + + @Override + public Cache build(ICacheSpec spec) { + TrackedCache cache = new TrackedCache<>(underlyingProvider.build(spec)); + constructedCaches.add(cache); + return cache; + } +} From 89ea7366f5cba14aeb977d0bcc554618091f92cc Mon Sep 17 00:00:00 2001 From: iProdigy Date: Fri, 20 Oct 2023 23:15:42 -0700 Subject: [PATCH 13/19] chore(test): ensure independent caches across mappers --- .../XanthicJacksonCacheProviderTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java b/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java index fbcc8b6b..fc3076ae 100644 --- a/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java +++ b/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java @@ -64,6 +64,24 @@ void constructType() { assertTrue(provider.getConstructedCaches().stream().anyMatch(TrackedCache::hasInteraction)); } + @Test + void constructMultiple() throws JsonProcessingException { + TrackedCacheProvider provider = new TrackedCacheProvider(new CaffeineProvider()); + assertEquals(0, provider.getConstructedCaches().size()); + + ObjectMapper m1 = JsonMapper.builder().cacheProvider(createCacheProvider(provider)).build(); + m1.readValue("{\"bar\":\"baz\"}", Foo.class); + m1.writeValueAsString(new Foo("baz")); + m1.getTypeFactory().constructParametricType(List.class, Integer.class); + assertEquals(3, provider.getConstructedCaches().size()); + + ObjectMapper m2 = JsonMapper.builder().cacheProvider(createCacheProvider(provider)).build(); + m2.readValue("{\"bar\":\"baz\"}", Foo.class); + m2.writeValueAsString(new Foo("baz")); + m2.getTypeFactory().constructParametricType(List.class, Integer.class); + assertEquals(6, provider.getConstructedCaches().size()); + } + private static CacheProvider createCacheProvider(TrackedCacheProvider trackedProvider) { return new XanthicJacksonCacheProvider( spec -> spec.provider(trackedProvider).maxSize((long) DeserializerCache.DEFAULT_MAX_CACHE_SIZE), From c18ebce15cbd53464c3c65f6e59fc01009a52240 Mon Sep 17 00:00:00 2001 From: iProdigy Date: Fri, 20 Oct 2023 23:19:31 -0700 Subject: [PATCH 14/19] chore: add test that uses XanthicJacksonCacheProvider.DEFAULT_INSTANCE --- .../jackson/XanthicJacksonCacheProviderTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java b/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java index fc3076ae..520dd154 100644 --- a/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java +++ b/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java @@ -27,6 +27,16 @@ class XanthicJacksonCacheProviderTest { + @Test + void defaults() throws JsonProcessingException { + ObjectMapper mapper = JsonMapper.builder() + .cacheProvider(XanthicJacksonCacheProvider.defaults()) + .build(); + assertNotNull(mapper.readValue("{\"bar\":\"baz\"}", Foo.class)); + assertNotNull(mapper.writeValueAsString(new Foo("baz"))); + assertNotNull(mapper.getTypeFactory().constructParametricType(List.class, Integer.class)); + } + @Test void deserialize() throws JsonProcessingException { TrackedCacheProvider provider = new TrackedCacheProvider(new CaffeineProvider()); From 5b5083d0e243ae7aaf9b6d77c25400d08678b3fa Mon Sep 17 00:00:00 2001 From: iProdigy Date: Sun, 22 Oct 2023 20:38:28 -0700 Subject: [PATCH 15/19] chore: simplify tests --- .../XanthicJacksonCacheProviderTest.java | 24 ++-- .../xanthic/jackson/util/TrackedCache.java | 112 ------------------ .../jackson/util/TrackedCacheProvider.java | 7 +- 3 files changed, 16 insertions(+), 127 deletions(-) delete mode 100644 jackson/src/test/java/io/github/xanthic/jackson/util/TrackedCache.java diff --git a/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java b/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java index 520dd154..cb9d88f1 100644 --- a/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java +++ b/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java @@ -8,8 +8,6 @@ import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.ser.SerializerCache; import com.fasterxml.jackson.databind.type.TypeFactory; -import io.github.xanthic.cache.provider.caffeine.CaffeineProvider; -import io.github.xanthic.jackson.util.TrackedCache; import io.github.xanthic.jackson.util.TrackedCacheProvider; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -39,44 +37,44 @@ void defaults() throws JsonProcessingException { @Test void deserialize() throws JsonProcessingException { - TrackedCacheProvider provider = new TrackedCacheProvider(new CaffeineProvider()); + TrackedCacheProvider provider = new TrackedCacheProvider(); ObjectMapper mapper = JsonMapper.builder() .cacheProvider(createCacheProvider(provider)) .build(); - assertFalse(provider.getConstructedCaches().stream().anyMatch(TrackedCache::hasInteraction)); + assertFalse(provider.getConstructedCaches().stream().anyMatch(c -> c.size() > 0)); Foo foo = mapper.readValue("{\"bar\":\"baz\"}", Foo.class); assertNotNull(foo); assertEquals("baz", foo.getBar()); - assertTrue(provider.getConstructedCaches().stream().anyMatch(TrackedCache::hasInteraction)); + assertTrue(provider.getConstructedCaches().stream().anyMatch(c -> c.size() > 0)); } @Test void serialize() throws JsonProcessingException { - TrackedCacheProvider provider = new TrackedCacheProvider(new CaffeineProvider()); + TrackedCacheProvider provider = new TrackedCacheProvider(); ObjectMapper mapper = JsonMapper.builder() .cacheProvider(createCacheProvider(provider)) .build(); - assertFalse(provider.getConstructedCaches().stream().anyMatch(TrackedCache::hasInteraction)); + assertFalse(provider.getConstructedCaches().stream().anyMatch(c -> c.size() > 0)); String json = mapper.writeValueAsString(new Foo("baz")); assertEquals("{\"bar\":\"baz\"}", json); - assertTrue(provider.getConstructedCaches().stream().anyMatch(TrackedCache::hasInteraction)); + assertTrue(provider.getConstructedCaches().stream().anyMatch(c -> c.size() > 0)); } @Test void constructType() { - TrackedCacheProvider provider = new TrackedCacheProvider(new CaffeineProvider()); + TrackedCacheProvider provider = new TrackedCacheProvider(); ObjectMapper mapper = JsonMapper.builder() .cacheProvider(createCacheProvider(provider)) .build(); - assertFalse(provider.getConstructedCaches().stream().anyMatch(TrackedCache::hasInteraction)); + assertFalse(provider.getConstructedCaches().stream().anyMatch(c -> c.size() > 0)); JavaType type = mapper.getTypeFactory().constructParametricType(List.class, Integer.class); assertNotNull(type); - assertTrue(provider.getConstructedCaches().stream().anyMatch(TrackedCache::hasInteraction)); + assertTrue(provider.getConstructedCaches().stream().anyMatch(c -> c.size() > 0)); } @Test void constructMultiple() throws JsonProcessingException { - TrackedCacheProvider provider = new TrackedCacheProvider(new CaffeineProvider()); + TrackedCacheProvider provider = new TrackedCacheProvider(); assertEquals(0, provider.getConstructedCaches().size()); ObjectMapper m1 = JsonMapper.builder().cacheProvider(createCacheProvider(provider)).build(); @@ -84,12 +82,14 @@ void constructMultiple() throws JsonProcessingException { m1.writeValueAsString(new Foo("baz")); m1.getTypeFactory().constructParametricType(List.class, Integer.class); assertEquals(3, provider.getConstructedCaches().size()); + assertTrue(provider.getConstructedCaches().stream().allMatch(c -> c.size() > 0)); ObjectMapper m2 = JsonMapper.builder().cacheProvider(createCacheProvider(provider)).build(); m2.readValue("{\"bar\":\"baz\"}", Foo.class); m2.writeValueAsString(new Foo("baz")); m2.getTypeFactory().constructParametricType(List.class, Integer.class); assertEquals(6, provider.getConstructedCaches().size()); + assertTrue(provider.getConstructedCaches().stream().allMatch(c -> c.size() > 0)); } private static CacheProvider createCacheProvider(TrackedCacheProvider trackedProvider) { diff --git a/jackson/src/test/java/io/github/xanthic/jackson/util/TrackedCache.java b/jackson/src/test/java/io/github/xanthic/jackson/util/TrackedCache.java deleted file mode 100644 index 8e61395b..00000000 --- a/jackson/src/test/java/io/github/xanthic/jackson/util/TrackedCache.java +++ /dev/null @@ -1,112 +0,0 @@ -package io.github.xanthic.jackson.util; - -import io.github.xanthic.cache.api.Cache; -import lombok.Value; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Function; - -@Value -public class TrackedCache implements Cache { - Cache underlyingCache; - AtomicBoolean interacted = new AtomicBoolean(); - - public boolean hasInteraction() { - return interacted.get(); - } - - @Override - public @Nullable V get(@NotNull K key) { - interacted.set(true); - return underlyingCache.get(key); - } - - @Override - public @Nullable V put(@NotNull K key, @NotNull V value) { - interacted.set(true); - return underlyingCache.put(key, value); - } - - @Override - public @Nullable V remove(@NotNull K key) { - interacted.set(true); - return underlyingCache.remove(key); - } - - @Override - public void clear() { - interacted.set(true); - underlyingCache.clear(); - } - - @Override - public long size() { - interacted.set(true); - return underlyingCache.size(); - } - - @Override - public @Nullable V compute(@NotNull K key, @NotNull BiFunction computeFunc) { - interacted.set(true); - return underlyingCache.compute(key, computeFunc); - } - - @Override - public V computeIfAbsent(@NotNull K key, @NotNull Function computeFunc) { - interacted.set(true); - return underlyingCache.computeIfAbsent(key, computeFunc); - } - - @Override - public @Nullable V computeIfPresent(@NotNull K key, @NotNull BiFunction computeFunc) { - interacted.set(true); - return underlyingCache.computeIfPresent(key, computeFunc); - } - - @Override - public @Nullable V putIfAbsent(@NotNull K key, @NotNull V value) { - interacted.set(true); - return underlyingCache.putIfAbsent(key, value); - } - - @Override - public V merge(@NotNull K key, @NotNull V value, @NotNull BiFunction mergeFunc) { - interacted.set(true); - return underlyingCache.merge(key, value, mergeFunc); - } - - @Override - public boolean replace(@NotNull K key, @NotNull V value) { - interacted.set(true); - return underlyingCache.replace(key, value); - } - - @Override - public boolean replace(@NotNull K key, @NotNull V oldValue, @NotNull V newValue) { - interacted.set(true); - return underlyingCache.replace(key, oldValue, newValue); - } - - @Override - public @NotNull V getOrDefault(@NotNull K key, @NotNull V defaultValue) { - interacted.set(true); - return underlyingCache.getOrDefault(key, defaultValue); - } - - @Override - public void putAll(@NotNull Map map) { - interacted.set(true); - underlyingCache.putAll(map); - } - - @Override - public void forEach(@NotNull BiConsumer action) { - interacted.set(true); - underlyingCache.forEach(action); - } -} diff --git a/jackson/src/test/java/io/github/xanthic/jackson/util/TrackedCacheProvider.java b/jackson/src/test/java/io/github/xanthic/jackson/util/TrackedCacheProvider.java index fe2fd1b2..3950bda3 100644 --- a/jackson/src/test/java/io/github/xanthic/jackson/util/TrackedCacheProvider.java +++ b/jackson/src/test/java/io/github/xanthic/jackson/util/TrackedCacheProvider.java @@ -3,6 +3,7 @@ import io.github.xanthic.cache.api.Cache; import io.github.xanthic.cache.api.CacheProvider; import io.github.xanthic.cache.api.ICacheSpec; +import io.github.xanthic.cache.core.CacheApiSettings; import lombok.Value; import java.util.ArrayList; @@ -10,12 +11,12 @@ @Value public class TrackedCacheProvider implements CacheProvider { - CacheProvider underlyingProvider; - List> constructedCaches = new ArrayList<>(); + CacheProvider underlyingProvider = CacheApiSettings.getInstance().getDefaultCacheProvider(); + List> constructedCaches = new ArrayList<>(); @Override public Cache build(ICacheSpec spec) { - TrackedCache cache = new TrackedCache<>(underlyingProvider.build(spec)); + Cache cache = underlyingProvider.build(spec); constructedCaches.add(cache); return cache; } From b390370ff4c46f56cfe08f16e811c58ea2554172 Mon Sep 17 00:00:00 2001 From: iProdigy Date: Sun, 22 Oct 2023 22:30:39 -0700 Subject: [PATCH 16/19] refactor: remove public spec getters in XanthicJacksonCacheProvider --- .../io/github/xanthic/jackson/XanthicJacksonCacheProvider.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java index 272d6f4c..cbf1a17e 100644 --- a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java +++ b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java @@ -13,6 +13,8 @@ import com.fasterxml.jackson.databind.util.TypeKey; import io.github.xanthic.cache.core.CacheApiSpec; import io.github.xanthic.jackson.util.SerializableConsumer; +import lombok.AccessLevel; +import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Value; @@ -24,6 +26,7 @@ * {@code ObjectMapper mapper = JsonMapper.builder().cacheProvider(XanthicJacksonCacheProvider.defaults()).build(); } */ @Value +@Getter(AccessLevel.PRIVATE) @RequiredArgsConstructor public class XanthicJacksonCacheProvider implements CacheProvider { private static final long serialVersionUID = 1L; From 7f56f2ac106d63455ce7cd46da263f74266e1bcc Mon Sep 17 00:00:00 2001 From: iProdigy <8106344+iProdigy@users.noreply.github.com> Date: Wed, 15 Nov 2023 14:27:14 -0800 Subject: [PATCH 17/19] chore: bump jackson version Signed-off-by: iProdigy <8106344+iProdigy@users.noreply.github.com> --- jackson/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jackson/build.gradle.kts b/jackson/build.gradle.kts index 27a414bb..80aacc3b 100644 --- a/jackson/build.gradle.kts +++ b/jackson/build.gradle.kts @@ -2,7 +2,7 @@ dependencies { api(project(":cache-core")) implementation("com.fasterxml.jackson.core:jackson-databind") { version { - require("2.16.0-rc1") // imposes a lower bound on acceptable versions + require("2.16.0") // imposes a lower bound on acceptable versions } } testImplementation(project(":cache-provider-caffeine")) From b78d9a0695454a1a7b4848ffca27e6e3e891639d Mon Sep 17 00:00:00 2001 From: iProdigy Date: Wed, 15 Nov 2023 15:42:46 -0800 Subject: [PATCH 18/19] refactor: rename default instance getter --- .../github/xanthic/jackson/XanthicJacksonCacheProvider.java | 4 ++-- .../xanthic/jackson/XanthicJacksonCacheProviderTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java index cbf1a17e..297ed3b6 100644 --- a/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java +++ b/jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java @@ -23,7 +23,7 @@ * which are backed by any cache implementation of your choosing. *

* Example usage: - * {@code ObjectMapper mapper = JsonMapper.builder().cacheProvider(XanthicJacksonCacheProvider.defaults()).build(); } + * {@code ObjectMapper mapper = JsonMapper.builder().cacheProvider(XanthicJacksonCacheProvider.defaultInstance()).build(); } */ @Value @Getter(AccessLevel.PRIVATE) @@ -85,7 +85,7 @@ public LookupCache forTypeFactory() { /** * @return a Jackson {@link CacheProvider} backed by Xanthic, using Jackson's recommended default max cache sizes. */ - public static XanthicJacksonCacheProvider defaults() { + public static XanthicJacksonCacheProvider defaultInstance() { return DEFAULT_INSTANCE; } } diff --git a/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java b/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java index cb9d88f1..17013e7a 100644 --- a/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java +++ b/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java @@ -28,7 +28,7 @@ class XanthicJacksonCacheProviderTest { @Test void defaults() throws JsonProcessingException { ObjectMapper mapper = JsonMapper.builder() - .cacheProvider(XanthicJacksonCacheProvider.defaults()) + .cacheProvider(XanthicJacksonCacheProvider.defaultInstance()) .build(); assertNotNull(mapper.readValue("{\"bar\":\"baz\"}", Foo.class)); assertNotNull(mapper.writeValueAsString(new Foo("baz"))); From 9ecc500d3a36fcd08be57b96770ec93039859e34 Mon Sep 17 00:00:00 2001 From: iProdigy Date: Wed, 15 Nov 2023 16:10:39 -0800 Subject: [PATCH 19/19] chore: add java serializable test --- .../XanthicJacksonCacheProviderTest.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java b/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java index 17013e7a..58fcc05e 100644 --- a/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java +++ b/jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java @@ -16,6 +16,11 @@ import lombok.Setter; import org.junit.jupiter.api.Test; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -92,6 +97,22 @@ void constructMultiple() throws JsonProcessingException { assertTrue(provider.getConstructedCaches().stream().allMatch(c -> c.size() > 0)); } + @Test + void serializable() throws IOException, ClassNotFoundException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(XanthicJacksonCacheProvider.defaultInstance()); + } + + XanthicJacksonCacheProvider provider; + try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()))) { + provider = (XanthicJacksonCacheProvider) ois.readObject(); + } + + assertNotNull(provider); + assertNotNull(provider.forTypeFactory()); + } + private static CacheProvider createCacheProvider(TrackedCacheProvider trackedProvider) { return new XanthicJacksonCacheProvider( spec -> spec.provider(trackedProvider).maxSize((long) DeserializerCache.DEFAULT_MAX_CACHE_SIZE),