[jackson-datatype-guava] 01/05: Imported Upstream version 2.4.2
Tim Potter
tpot-guest at moszumanska.debian.org
Mon Nov 3 23:25:19 UTC 2014
This is an automated email from the git hooks/post-receive script.
tpot-guest pushed a commit to branch master
in repository jackson-datatype-guava.
commit 3dd3f5da291745f5b4ac78fe63df0b7e890afd87
Author: Tim Potter <tpot at hp.com>
Date: Fri Oct 24 11:56:14 2014 +1100
Imported Upstream version 2.4.2
---
.gitignore | 22 ++
DEV/contributor-agreement.pdf | Bin 0 -> 45328 bytes
README.md | 42 ++++
pom.xml | 92 ++++++++
release-notes/VERSION | 91 ++++++++
.../jackson/datatype/guava/GuavaDeserializers.java | 237 ++++++++++++++++++++
.../jackson/datatype/guava/GuavaModule.java | 39 ++++
.../jackson/datatype/guava/GuavaSerializers.java | 52 +++++
.../jackson/datatype/guava/GuavaTypeModifier.java | 64 ++++++
.../jackson/datatype/guava/PackageVersion.java.in | 20 ++
.../guava/deser/GuavaCollectionDeserializer.java | 129 +++++++++++
.../GuavaImmutableCollectionDeserializer.java | 76 +++++++
.../guava/deser/GuavaImmutableMapDeserializer.java | 57 +++++
.../datatype/guava/deser/GuavaMapDeserializer.java | 135 ++++++++++++
.../guava/deser/GuavaMultisetDeserializer.java | 70 ++++++
.../guava/deser/GuavaOptionalDeserializer.java | 126 +++++++++++
.../guava/deser/HashMultisetDeserializer.java | 31 +++
.../guava/deser/HostAndPortDeserializer.java | 42 ++++
.../guava/deser/ImmutableBiMapDeserializer.java | 26 +++
.../guava/deser/ImmutableListDeserializer.java | 37 ++++
.../guava/deser/ImmutableMapDeserializer.java | 31 +++
.../guava/deser/ImmutableMultisetDeserializer.java | 27 +++
.../guava/deser/ImmutableSetDeserializer.java | 30 +++
.../deser/ImmutableSortedMapDeserializer.java | 36 ++++
.../deser/ImmutableSortedSetDeserializer.java | 38 ++++
.../deser/LinkedHashMultisetDeserializer.java | 26 +++
.../datatype/guava/deser/RangeDeserializer.java | 181 ++++++++++++++++
.../guava/deser/TreeMultisetDeserializer.java | 29 +++
.../deser/multimap/GuavaMultimapDeserializer.java | 183 ++++++++++++++++
.../list/ArrayListMultimapDeserializer.java | 43 ++++
.../list/LinkedListMultimapDeserializer.java | 43 ++++
.../multimap/set/HashMultimapDeserializer.java | 43 ++++
.../set/LinkedHashMultimapDeserializer.java | 43 ++++
.../datatype/guava/deser/util/RangeFactory.java | 181 ++++++++++++++++
.../guava/ser/GuavaBeanSerializerModifier.java | 26 +++
.../guava/ser/GuavaOptionalBeanPropertyWriter.java | 26 +++
.../guava/ser/GuavaOptionalSerializer.java | 55 +++++
.../datatype/guava/ser/MultimapSerializer.java | 177 +++++++++++++++
.../datatype/guava/ser/RangeSerializer.java | 112 ++++++++++
src/main/resources/META-INF/LICENSE | 8 +
.../services/com.fasterxml.jackson.databind.Module | 1 +
.../jackson/datatype/guava/FluentIterableTest.java | 45 ++++
.../jackson/datatype/guava/HostAndPortTest.java | 41 ++++
.../jackson/datatype/guava/IterablesTest.java | 16 ++
.../jackson/datatype/guava/ModuleTestBase.java | 40 ++++
.../jackson/datatype/guava/TestImmutables.java | 240 +++++++++++++++++++++
.../jackson/datatype/guava/TestMultimaps.java | 205 ++++++++++++++++++
.../jackson/datatype/guava/TestMultisets.java | 110 ++++++++++
.../jackson/datatype/guava/TestOptional.java | 176 +++++++++++++++
.../guava/TestOptionalWithPolymorphic.java | 106 +++++++++
.../jackson/datatype/guava/TestRange.java | 78 +++++++
.../jackson/datatype/guava/TestVersions.java | 38 ++++
52 files changed, 3822 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..66c4cfe
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,22 @@
+# use glob syntax.
+syntax: glob
+*.class
+*~
+*.bak
+*.off
+*.old
+.DS_Store
+
+# building
+target
+
+# Eclipse
+.classpath
+.project
+.settings
+
+# IDEA
+*.iml
+*.ipr
+*.iws
+.idea/
diff --git a/DEV/contributor-agreement.pdf b/DEV/contributor-agreement.pdf
new file mode 100644
index 0000000..fe36036
Binary files /dev/null and b/DEV/contributor-agreement.pdf differ
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..009b175
--- /dev/null
+++ b/README.md
@@ -0,0 +1,42 @@
+Project to build [Jackson](http://jackson.codehaus.org) module (jar)
+to support JSON serialization and deserialization of
+[Guava](http://code.google.com/p/guava-libraries/) collection types.
+
+[](https://fasterxml.ci.cloudbees.com/job/jackson-datatype-guava-master/)
+
+## Status
+
+As of version 2.3, module is production ready. Not all datatypes of Guava are support due to sheer
+size of the library; new support is added based on contributions.
+
+## Usage
+
+### Maven dependency
+
+To use module on Maven-based projects, use following dependency:
+
+```xml
+<dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-guava</artifactId>
+ <version>2.4.0</version>
+</dependency>
+```
+
+(or whatever version is most up-to-date at the moment)
+
+### Registering module
+
+Like all standard Jackson modules (libraries that implement Module interface), registration is done as follows:
+
+```java
+ObjectMapper mapper = new ObjectMapper();
+mapper.registerModule(new GuavaModule());
+```
+
+after which functionality is available for all normal Jackson operations.
+
+## More
+
+See [Wiki](jackson-datatype-guava/wiki) for more information (javadocs, downloads).
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..a7593bf
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,92 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.fasterxml.jackson</groupId>
+ <artifactId>jackson-parent</artifactId>
+ <version>2.4</version>
+ </parent>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-guava</artifactId>
+ <name>Jackson-datatype-Guava</name>
+ <version>2.4.2</version>
+ <packaging>bundle</packaging>
+ <description>Add-on datatype-support module for Jackson (http://jackson.codehaus.org) that handles
+Guava (http://code.google.com/p/guava-libraries/) types (currently mostly just collection ones)
+ </description>
+ <url>http://wiki.fasterxml.com/JacksonModuleGuava</url>
+ <scm>
+ <connection>scm:git:git at github.com:FasterXML/jackson-datatype-guava.git</connection>
+ <developerConnection>scm:git:git at github.com:FasterXML/jackson-datatype-guava.git</developerConnection>
+ <url>http://github.com/FasterXML/jackson-datatype-guava</url>
+ <tag>jackson-datatype-guava-2.4.2</tag>
+ </scm>
+
+ <contributors>
+ <contributor>
+ <name>Steven Schlansker</name>
+ <email>steven at nesscomputing.com</email>
+ </contributor>
+ </contributors>
+
+ <properties>
+ <version.jackson>2.4.2</version.jackson>
+
+ <!-- Generate PackageVersion.java into this directory. -->
+ <packageVersion.dir>com/fasterxml/jackson/datatype/guava</packageVersion.dir>
+ <packageVersion.package>${project.groupId}.guava</packageVersion.package>
+ <osgi.export>${project.groupId}.guava;version=${project.version},
+${project.groupId}.guava.*;version=${project.version}
+ </osgi.export>
+ <osgi.import>
+com.google.common.collect,
+com.google.common.base,
+com.google.common.cache,
+com.google.common.net,
+com.fasterxml.jackson.core,
+com.fasterxml.jackson.core.util,
+com.fasterxml.jackson.databind,
+com.fasterxml.jackson.databind.deser,
+com.fasterxml.jackson.databind.deser.std,
+com.fasterxml.jackson.databind.introspect,
+com.fasterxml.jackson.databind.jsonFormatVisitors,
+com.fasterxml.jackson.databind.jsontype,
+com.fasterxml.jackson.databind.ser,
+com.fasterxml.jackson.databind.ser.std,
+com.fasterxml.jackson.databind.type
+ </osgi.import>
+ </properties>
+
+ <dependencies>
+ <!-- Extends Jackson; supports Guava datatypes, so: -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>${version.jackson}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ <version>${version.jackson}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>15.0</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.google.code.maven-replacer-plugin</groupId>
+ <artifactId>replacer</artifactId>
+ <executions>
+ <execution>
+ <id>process-packageVersion</id>
+ <phase>generate-sources</phase>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/release-notes/VERSION b/release-notes/VERSION
new file mode 100644
index 0000000..19d7e6a
--- /dev/null
+++ b/release-notes/VERSION
@@ -0,0 +1,91 @@
+Project: jackson-datatype-guava
+Version: 2.4.2 (15-Aug-2014)
+
+#46: Can not serialize guava Iterables
+ (reported by chisui at github)
+
+------------------------------------------------------------------------
+=== History: ===
+------------------------------------------------------------------------
+
+2.4.1 (17-Jun-2014)
+
+No changes since 2.4.0.
+
+2.4.0 (03-Jun-2014)
+
+#43: Add support for `HostAndPort`
+
+2.3.3 (14-Apr-2014)
+
+#37: `Optional` not correctly deserialized from JSON null, if inside a Collection
+ (reported by JYang-Addepar at github)
+#41: `Multimap` serializer does not honor @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ (reported by Olve S-H)
+
+2.3.2 (01-Mar-2014)
+
+#36: Improve Range deserializer to work with older Guava versions (10-)
+ (contribtued by ispringer at github)
+
+2.3.1 (28-Dec-2013)
+
+#33: Add support for `Range` values
+ (contribute by ispringer at github)
+#34: Use Optional type parameter if present to create JSON schema
+ (contributed by cponomaryov at github)
+
+2.3.0 (14-Nov-2013)
+
+#29: Empty ImmutableMap not deserialized correctly, when type info included
+ (reported by pbergn at github)
+- Add support for `DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY` for
+ `ImmutableSet` and `MultiSet`
+
+2.2.3 (25-Aug-2013)
+2.2.2 (27-May-2013)
+2.2.1 (03-May-2013)
+
+No functional changes.
+
+2.2.0 (23-Apr-2013)
+
+New minor version, no functional changes.
+
+2.1.2 (08-Dec-2012)
+
+No functional changes.
+
+2.1.1 (13-Nov-2012)
+
+* More improvements to handling of Optional values
+
+2.1.0 (08-Oct-2012)
+
+* [Issue#9]: Handling of Optional values, wrt NON_NULL inclusion
+
+2.0.6 (30-Sep-2012)
+
+ No functional changes, just dependency updates.
+
+2.0.4, 2.0.5: not released
+
+2.0.3 (14-Jun-2012)
+
+* Issue-6: An NPE in MultimapDeserializer
+
+2.0.2 (16-May-2012)
+
+Fixes:
+
+* Issue-3: (Tree)MultiMaps not serialized properly
+
+Improvements:
+
+* Add for a wider set of Collections
+ (contributed by Pascal G)
+
+
+2.0.0 (25-Mar-2012)
+
+The official 2.0 release...
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java b/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java
new file mode 100644
index 0000000..71bd9b0
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java
@@ -0,0 +1,237 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.Deserializers;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.fasterxml.jackson.databind.type.MapLikeType;
+import com.fasterxml.jackson.databind.type.MapType;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.fasterxml.jackson.datatype.guava.deser.*;
+import com.fasterxml.jackson.datatype.guava.deser.multimap.list.ArrayListMultimapDeserializer;
+import com.fasterxml.jackson.datatype.guava.deser.multimap.list.LinkedListMultimapDeserializer;
+import com.fasterxml.jackson.datatype.guava.deser.multimap.set.HashMultimapDeserializer;
+import com.fasterxml.jackson.datatype.guava.deser.multimap.set.LinkedHashMultimapDeserializer;
+import com.google.common.base.Optional;
+import com.google.common.collect.*;
+import com.google.common.net.HostAndPort;
+
+/**
+ * Custom deserializers module offers.
+ */
+public class GuavaDeserializers
+ extends Deserializers.Base
+{
+ /**
+ * We have plenty of collection types to support...
+ */
+ @Override
+ public JsonDeserializer<?> findCollectionDeserializer(CollectionType type,
+ DeserializationConfig config, BeanDescription beanDesc,
+ TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer)
+ throws JsonMappingException
+ {
+ Class<?> raw = type.getRawClass();
+
+ // ImmutableXxx types?
+ if (ImmutableCollection.class.isAssignableFrom(raw)) {
+ if (ImmutableList.class.isAssignableFrom(raw)) {
+ return new ImmutableListDeserializer(type,
+ elementTypeDeserializer, elementDeserializer);
+ }
+ if (ImmutableMultiset.class.isAssignableFrom(raw)) {
+ // 15-May-2012, pgelinas: There is no ImmutableSortedMultiset
+ // available yet
+ return new ImmutableMultisetDeserializer(type, elementTypeDeserializer, elementDeserializer);
+ }
+ if (ImmutableSet.class.isAssignableFrom(raw)) {
+ // sorted one?
+ if (ImmutableSortedSet.class.isAssignableFrom(raw)) {
+ /* 28-Nov-2010, tatu: With some more work would be able to use other things
+ * than natural ordering; but that'll have to do for now...
+ */
+ Class<?> elemType = type.getContentType().getRawClass();
+ if (!Comparable.class.isAssignableFrom(elemType)) {
+ throw new IllegalArgumentException("Can not handle ImmutableSortedSet with elements that are not Comparable<?> ("
+ +raw.getName()+")");
+ }
+ return new ImmutableSortedSetDeserializer(type,
+ elementTypeDeserializer, elementDeserializer);
+ }
+ // nah, just regular one
+ return new ImmutableSetDeserializer(type,
+ elementTypeDeserializer, elementDeserializer);
+ }
+ // TODO: make configurable (for now just default blindly to a list)
+ return new ImmutableListDeserializer(type, elementTypeDeserializer, elementDeserializer);
+ }
+
+ // Multi-xxx collections?
+ if (Multiset.class.isAssignableFrom(raw)) {
+ // Quite a few variations...
+ if (LinkedHashMultiset.class.isAssignableFrom(raw)) {
+ return new LinkedHashMultisetDeserializer(type, elementTypeDeserializer, elementDeserializer);
+ }
+ if (HashMultiset.class.isAssignableFrom(raw)) {
+ return new HashMultisetDeserializer(type, elementTypeDeserializer, elementDeserializer);
+ }
+ if (EnumMultiset.class.isAssignableFrom(raw)) {
+ // !!! TODO
+ }
+ if (TreeMultiset.class.isAssignableFrom(raw)) {
+ return new TreeMultisetDeserializer(type, elementTypeDeserializer, elementDeserializer);
+ }
+
+ // TODO: make configurable (for now just default blindly)
+ return new HashMultisetDeserializer(type, elementTypeDeserializer, elementDeserializer);
+ }
+
+ return null;
+ }
+
+ /**
+ * A few Map types to support.
+ */
+ @Override
+ public JsonDeserializer<?> findMapDeserializer(MapType type,
+ DeserializationConfig config, BeanDescription beanDesc,
+ KeyDeserializer keyDeserializer,
+ TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer)
+ throws JsonMappingException
+ {
+ Class<?> raw = type.getRawClass();
+
+ // ImmutableXxxMap types?
+ if (ImmutableMap.class.isAssignableFrom(raw)) {
+ if (ImmutableSortedMap.class.isAssignableFrom(raw)) {
+ return new ImmutableSortedMapDeserializer(type, keyDeserializer, elementTypeDeserializer,
+ elementDeserializer);
+ }
+ if (ImmutableBiMap.class.isAssignableFrom(raw)) {
+ return new ImmutableBiMapDeserializer(type, keyDeserializer, elementTypeDeserializer,
+ elementDeserializer);
+ }
+ // Otherwise, plain old ImmutableMap...
+ return new ImmutableMapDeserializer(type, keyDeserializer, elementTypeDeserializer, elementDeserializer);
+ }
+
+ // XxxBiMap types?
+ if (BiMap.class.isAssignableFrom(raw)) {
+ if (EnumBiMap.class.isAssignableFrom(raw)) {
+ // !!! TODO
+ }
+ if (EnumHashBiMap.class.isAssignableFrom(raw)) {
+ // !!! TODO
+ }
+ if (HashBiMap.class.isAssignableFrom(raw)) {
+ // !!! TODO
+ }
+ // !!! TODO default
+ }
+
+
+ return null;
+ }
+
+ @Override
+ public JsonDeserializer<?> findMapLikeDeserializer(MapLikeType type,
+ DeserializationConfig config, BeanDescription beanDesc,
+ KeyDeserializer keyDeserializer, TypeDeserializer elementTypeDeserializer,
+ JsonDeserializer<?> elementDeserializer)
+ throws JsonMappingException
+ {
+ Class<?> raw = type.getRawClass();
+
+ // ListMultimaps
+ if (ListMultimap.class.isAssignableFrom(raw)) {
+ if (ImmutableListMultimap.class.isAssignableFrom(raw)) {
+ // TODO
+ }
+ if (ArrayListMultimap.class.isAssignableFrom(raw)) {
+ return new ArrayListMultimapDeserializer(type, keyDeserializer,
+ elementTypeDeserializer, elementDeserializer);
+ }
+ if (LinkedListMultimap.class.isAssignableFrom(raw)) {
+ return new LinkedListMultimapDeserializer(type, keyDeserializer,
+ elementTypeDeserializer, elementDeserializer);
+ }
+ if (ForwardingListMultimap.class.isAssignableFrom(raw)) {
+ // TODO
+ }
+
+ // TODO: Remove the default fall-through once all implementations are in place.
+ return new ArrayListMultimapDeserializer(type, keyDeserializer,
+ elementTypeDeserializer, elementDeserializer);
+ }
+
+ // SetMultimaps
+ if (SetMultimap.class.isAssignableFrom(raw)) {
+
+ // SortedSetMultimap
+ if (SortedSetMultimap.class.isAssignableFrom(raw)) {
+ if (TreeMultimap.class.isAssignableFrom(raw)) {
+ // TODO
+ }
+ if (ForwardingSortedSetMultimap.class.isAssignableFrom(raw)) {
+ // TODO
+ }
+ }
+
+ if (ImmutableSetMultimap.class.isAssignableFrom(raw)) {
+ // TODO
+ }
+ if (HashMultimap.class.isAssignableFrom(raw)) {
+ return new HashMultimapDeserializer(type, keyDeserializer, elementTypeDeserializer,
+ elementDeserializer);
+ }
+ if (LinkedHashMultimap.class.isAssignableFrom(raw)) {
+ return new LinkedHashMultimapDeserializer(type, keyDeserializer,
+ elementTypeDeserializer, elementDeserializer);
+ }
+ if (ForwardingSetMultimap.class.isAssignableFrom(raw)) {
+ // TODO
+ }
+
+ // TODO: Remove the default fall-through once all implementations are covered.
+ return new HashMultimapDeserializer(type, keyDeserializer, elementTypeDeserializer,
+ elementDeserializer);
+ }
+
+ // Handle the case where nothing more specific was provided.
+ if (Multimap.class.isAssignableFrom(raw)) {
+ return new LinkedListMultimapDeserializer(type, keyDeserializer,
+ elementTypeDeserializer, elementDeserializer);
+ }
+
+ if (Table.class.isAssignableFrom(raw)) {
+ // !!! TODO
+ }
+
+ return null;
+ }
+
+ @Override
+ public JsonDeserializer<?> findBeanDeserializer(final JavaType type, DeserializationConfig config,
+ BeanDescription beanDesc) throws JsonMappingException
+ {
+ Class<?> raw = type.getRawClass();
+ if (raw == Optional.class){
+ JavaType[] types = config.getTypeFactory().findTypeParameters(type, Optional.class);
+ JavaType refType = (types == null) ? TypeFactory.unknownType() : types[0];
+ JsonDeserializer<?> valueDeser = type.getValueHandler();
+ TypeDeserializer typeDeser = type.getTypeHandler();
+ // [Issue#42]: Polymorphic types need type deserializer
+ if (typeDeser == null) {
+ typeDeser = config.findTypeDeserializer(refType);
+ }
+ return new GuavaOptionalDeserializer(type, refType, typeDeser, valueDeser);
+ }
+ if (raw == Range.class) {
+ return new RangeDeserializer(type);
+ }
+ if (raw == HostAndPort.class) {
+ return HostAndPortDeserializer.std;
+ }
+ return super.findBeanDeserializer(type, config, beanDesc);
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaModule.java b/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaModule.java
new file mode 100644
index 0000000..73a5ff8
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaModule.java
@@ -0,0 +1,39 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import com.fasterxml.jackson.core.Version;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.datatype.guava.ser.GuavaBeanSerializerModifier;
+
+public class GuavaModule extends Module // can't use just SimpleModule, due to generic types
+{
+ private final String NAME = "GuavaModule";
+
+ public GuavaModule() {
+ super();
+ }
+
+ @Override public String getModuleName() { return NAME; }
+ @Override public Version version() { return PackageVersion.VERSION; }
+
+ @Override
+ public void setupModule(SetupContext context)
+ {
+ context.addDeserializers(new GuavaDeserializers());
+ context.addSerializers(new GuavaSerializers());
+ context.addTypeModifier(new GuavaTypeModifier());
+ context.addBeanSerializerModifier(new GuavaBeanSerializerModifier());
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return GuavaModule.class.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ return this == o;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaSerializers.java b/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaSerializers.java
new file mode 100644
index 0000000..4ca1880
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaSerializers.java
@@ -0,0 +1,52 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import com.google.common.base.Optional;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheBuilderSpec;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Range;
+import com.google.common.net.HostAndPort;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import com.fasterxml.jackson.databind.ser.Serializers;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.fasterxml.jackson.databind.type.MapLikeType;
+import com.fasterxml.jackson.datatype.guava.ser.GuavaOptionalSerializer;
+import com.fasterxml.jackson.datatype.guava.ser.MultimapSerializer;
+import com.fasterxml.jackson.datatype.guava.ser.RangeSerializer;
+
+public class GuavaSerializers extends Serializers.Base
+{
+ @Override
+ public JsonSerializer<?> findSerializer(SerializationConfig config, JavaType type, BeanDescription beanDesc)
+ {
+ Class<?> raw = type.getRawClass();
+ if(Optional.class.isAssignableFrom(raw)){
+ return new GuavaOptionalSerializer(type);
+ }
+ if (Range.class.isAssignableFrom(raw)) {
+ return new RangeSerializer(type);
+ }
+ // since 2.4
+ if (HostAndPort.class.isAssignableFrom(raw)) {
+ return ToStringSerializer.instance;
+ }
+ // not sure how useful, but why not?
+ if (CacheBuilderSpec.class.isAssignableFrom(raw) || CacheBuilder.class.isAssignableFrom(raw)) {
+ return ToStringSerializer.instance;
+ }
+ return super.findSerializer(config, type, beanDesc);
+ }
+
+ @Override
+ public JsonSerializer<?> findMapLikeSerializer(SerializationConfig config,
+ MapLikeType type, BeanDescription beanDesc, JsonSerializer<Object> keySerializer,
+ TypeSerializer elementTypeSerializer, JsonSerializer<Object> elementValueSerializer)
+ {
+ if (Multimap.class.isAssignableFrom(type.getRawClass())) {
+ return new MultimapSerializer(config, type, beanDesc, keySerializer,
+ elementTypeSerializer, elementValueSerializer);
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaTypeModifier.java b/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaTypeModifier.java
new file mode 100644
index 0000000..dc017b5
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaTypeModifier.java
@@ -0,0 +1,64 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import java.lang.reflect.Type;
+
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.type.TypeBindings;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.fasterxml.jackson.databind.type.TypeModifier;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Multimap;
+
+public class GuavaTypeModifier extends TypeModifier
+{
+ @Override
+ public JavaType modifyType(JavaType type, Type jdkType, TypeBindings context, TypeFactory typeFactory)
+ {
+ final Class<?> raw = type.getRawClass();
+ if (Multimap.class.isAssignableFrom(raw)) {
+ JavaType keyType = type.containedType(0);
+ JavaType contentType = type.containedType(1);
+
+ if (keyType == null) {
+ keyType = TypeFactory.unknownType();
+ }
+ if (contentType == null) {
+ contentType = TypeFactory.unknownType();
+ }
+ return typeFactory.constructMapLikeType(type.getRawClass(), keyType, contentType);
+ }
+ /* Guava 12 changed the implementation of their {@link FluentIterable} to include a method named "isEmpty." This method
+ * causes Jackson to treat FluentIterables as a Bean instead of an {@link Iterable}. Serialization of FluentIterables by
+ * default result in a string like "{\"empty\":true}." This module modifies the JavaType of FluentIterable to be
+ * the same as Iterable.
+ */
+ /* Hmmh. This won't work too well for deserialization. But I guess it'll
+ * have to do for now...
+ */
+ if (FluentIterable.class.isAssignableFrom(raw)) {
+ JavaType elemType = null;
+ JavaType[] types;
+ try {
+ types = typeFactory.findTypeParameters(type, Iterable.class);
+ if (types != null && types.length > 0) {
+ elemType = types[0];
+ }
+ } catch (IllegalArgumentException e) {
+ /* 07-Aug-2015, tatu: Nasty hack, but until we get 100% functioning
+ * type resolution (from ClassMate project, f.ex.), need to work around
+ * edge cases with aliasing and/or unresolved type variables.
+ * So... here we go:
+ */
+ String msg = e.getMessage();
+ if (msg == null || !msg.contains("Type variable 'T' can not be resolved")) {
+ throw e;
+ }
+ }
+ if (elemType == null) {
+ elemType = TypeFactory.unknownType();
+ }
+ return typeFactory.constructParametricType(Iterable.class, elemType);
+ }
+ return type;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/PackageVersion.java.in b/src/main/java/com/fasterxml/jackson/datatype/guava/PackageVersion.java.in
new file mode 100644
index 0000000..7860aa1
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/PackageVersion.java.in
@@ -0,0 +1,20 @@
+package @package@;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.core.Versioned;
+import com.fasterxml.jackson.core.util.VersionUtil;
+
+/**
+ * Automatically generated from PackageVersion.java.in during
+ * packageVersion-generate execution of maven-replacer-plugin in
+ * pom.xml.
+ */
+public final class PackageVersion implements Versioned {
+ public final static Version VERSION = VersionUtil.parseVersion(
+ "@projectversion@", "@projectgroupid@", "@projectartifactid@");
+
+ @Override
+ public Version version() {
+ return VERSION;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaCollectionDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaCollectionDeserializer.java
new file mode 100644
index 0000000..051022b
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaCollectionDeserializer.java
@@ -0,0 +1,129 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.CollectionType;
+
+public abstract class GuavaCollectionDeserializer<T>
+ extends StdDeserializer<T>
+ implements ContextualDeserializer
+{
+ private static final long serialVersionUID = 1L;
+
+ protected final CollectionType _containerType;
+
+ /**
+ * Deserializer used for values contained in collection being deserialized;
+ * either assigned on constructor, or during resolve().
+ */
+ protected final JsonDeserializer<?> _valueDeserializer;
+
+ /**
+ * If value instances have polymorphic type information, this
+ * is the type deserializer that can deserialize required type
+ * information
+ */
+ protected final TypeDeserializer _typeDeserializerForValue;
+
+ protected GuavaCollectionDeserializer(CollectionType type,
+ TypeDeserializer typeDeser, JsonDeserializer<?> deser)
+ {
+ super(type);
+ _containerType = type;
+ _typeDeserializerForValue = typeDeser;
+ _valueDeserializer = deser;
+ }
+
+ /**
+ * Overridable fluent factory method used for creating contextual
+ * instances.
+ */
+ public abstract GuavaCollectionDeserializer<T> withResolved(
+ TypeDeserializer typeDeser, JsonDeserializer<?> valueDeser);
+
+ /*
+ /**********************************************************
+ /* Validation, post-processing
+ /**********************************************************
+ */
+
+ /**
+ * Method called to finalize setup of this deserializer,
+ * after deserializer itself has been registered. This
+ * is needed to handle recursive and transitive dependencies.
+ */
+ @Override
+ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
+ BeanProperty property) throws JsonMappingException
+ {
+ JsonDeserializer<?> deser = _valueDeserializer;
+ TypeDeserializer typeDeser = _typeDeserializerForValue;
+ if (deser == null) {
+ deser = ctxt.findContextualValueDeserializer(_containerType.getContentType(), property);
+ }
+ if (typeDeser != null) {
+ typeDeser = typeDeser.forProperty(property);
+ }
+ if (deser == _valueDeserializer && typeDeser == _typeDeserializerForValue) {
+ return this;
+ }
+ return withResolved(typeDeser, deser);
+ }
+
+ /*
+ /**********************************************************
+ /* Deserialization interface
+ /**********************************************************
+ */
+
+ /**
+ * Base implementation that does not assume specific type
+ * inclusion mechanism. Sub-classes are expected to override
+ * this method if they are to handle type information.
+ */
+ @Override
+ public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt,
+ TypeDeserializer typeDeserializer)
+ throws IOException, JsonProcessingException
+ {
+ return typeDeserializer.deserializeTypedFromArray(jp, ctxt);
+ }
+
+ @Override
+ public T deserialize(JsonParser jp, DeserializationContext ctxt)
+ throws IOException, JsonProcessingException
+ {
+ // Should usually point to START_ARRAY
+ if (jp.isExpectedStartArrayToken()) {
+ return _deserializeContents(jp, ctxt);
+ }
+ // But may support implicit arrays from single values?
+ if (ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)) {
+ return _deserializeFromSingleValue(jp, ctxt);
+ }
+ throw ctxt.mappingException(_containerType.getRawClass());
+ }
+
+ /*
+ /**********************************************************************
+ /* Abstract methods for impl classes
+ /**********************************************************************
+ */
+
+ protected abstract T _deserializeContents(JsonParser jp, DeserializationContext ctxt)
+ throws IOException, JsonProcessingException;
+
+ /**
+ * Method used to support implicit coercion from a single non-array value
+ * into single-element collection.
+ *
+ * @since 2.3
+ */
+ protected abstract T _deserializeFromSingleValue(JsonParser jp, DeserializationContext ctxt)
+ throws IOException, JsonProcessingException;
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaImmutableCollectionDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaImmutableCollectionDeserializer.java
new file mode 100644
index 0000000..b5de17c
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaImmutableCollectionDeserializer.java
@@ -0,0 +1,76 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.google.common.collect.ImmutableCollection;
+
+abstract class GuavaImmutableCollectionDeserializer<T extends ImmutableCollection<Object>>
+ extends GuavaCollectionDeserializer<T>
+{
+ private static final long serialVersionUID = 1L;
+
+ GuavaImmutableCollectionDeserializer(CollectionType type,
+ TypeDeserializer typeDeser, JsonDeserializer<?> deser) {
+ super(type, typeDeser, deser);
+ }
+
+ protected abstract ImmutableCollection.Builder<Object> createBuilder();
+
+ @Override
+ protected T _deserializeContents(JsonParser jp, DeserializationContext ctxt)
+ throws IOException, JsonProcessingException {
+ JsonDeserializer<?> valueDes = _valueDeserializer;
+ JsonToken t;
+ final TypeDeserializer typeDeser = _typeDeserializerForValue;
+ // No way to pass actual type parameter; but does not matter, just
+ // compiler-time fluff:
+ ImmutableCollection.Builder<Object> builder = createBuilder();
+
+ while ((t = jp.nextToken()) != JsonToken.END_ARRAY) {
+ Object value;
+
+ if (t == JsonToken.VALUE_NULL) {
+ value = null;
+ } else if (typeDeser == null) {
+ value = valueDes.deserialize(jp, ctxt);
+ } else {
+ value = valueDes.deserializeWithType(jp, ctxt, typeDeser);
+ }
+ builder.add(value);
+ }
+ // No class outside of the package will be able to subclass us,
+ // and we provide the proper builder for the subclasses we implement.
+ @SuppressWarnings("unchecked")
+ T collection = (T) builder.build();
+ return collection;
+ }
+
+ @Override
+ protected T _deserializeFromSingleValue(JsonParser jp, DeserializationContext ctxt)
+ throws IOException, JsonProcessingException
+ {
+ JsonDeserializer<?> valueDes = _valueDeserializer;
+ final TypeDeserializer typeDeser = _typeDeserializerForValue;
+ JsonToken t = jp.getCurrentToken();
+
+ Object value;
+
+ if (t == JsonToken.VALUE_NULL) {
+ value = null;
+ } else if (typeDeser == null) {
+ value = valueDes.deserialize(jp, ctxt);
+ } else {
+ value = valueDes.deserializeWithType(jp, ctxt, typeDeser);
+ }
+ @SuppressWarnings("unchecked")
+ T result = (T) createBuilder().add(value).build();
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaImmutableMapDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaImmutableMapDeserializer.java
new file mode 100644
index 0000000..95d4c7b
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaImmutableMapDeserializer.java
@@ -0,0 +1,57 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.KeyDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.MapType;
+import com.google.common.collect.ImmutableMap;
+
+abstract class GuavaImmutableMapDeserializer<T extends ImmutableMap<Object, Object>> extends
+ GuavaMapDeserializer<T> {
+
+ GuavaImmutableMapDeserializer(MapType type, KeyDeserializer keyDeser, TypeDeserializer typeDeser,
+ JsonDeserializer<?> deser) {
+ super(type, keyDeser, typeDeser, deser);
+ }
+
+ protected abstract ImmutableMap.Builder<Object, Object> createBuilder();
+
+ @Override
+ protected T _deserializeEntries(JsonParser jp, DeserializationContext ctxt) throws IOException,
+ JsonProcessingException {
+ final KeyDeserializer keyDes = _keyDeserializer;
+ final JsonDeserializer<?> valueDes = _valueDeserializer;
+ final TypeDeserializer typeDeser = _typeDeserializerForValue;
+
+ ImmutableMap.Builder<Object, Object> builder = createBuilder();
+ for (; jp.getCurrentToken() == JsonToken.FIELD_NAME; jp.nextToken()) {
+ // Must point to field name now
+ String fieldName = jp.getCurrentName();
+ Object key = (keyDes == null) ? fieldName : keyDes.deserializeKey(fieldName, ctxt);
+ // And then the value...
+ JsonToken t = jp.nextToken();
+ // 28-Nov-2010, tatu: Should probably support "ignorable properties" in future...
+ Object value;
+ if (t == JsonToken.VALUE_NULL) {
+ value = null;
+ } else if (typeDeser == null) {
+ value = valueDes.deserialize(jp, ctxt);
+ } else {
+ value = valueDes.deserializeWithType(jp, ctxt, typeDeser);
+ }
+ builder.put(key, value);
+ }
+ // No class outside of the package will be able to subclass us,
+ // and we provide the proper builder for the subclasses we implement.
+ @SuppressWarnings("unchecked")
+ T map = (T) builder.build();
+ return map;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaMapDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaMapDeserializer.java
new file mode 100644
index 0000000..9075756
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaMapDeserializer.java
@@ -0,0 +1,135 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.*;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.MapType;
+
+public abstract class GuavaMapDeserializer<T>
+ extends JsonDeserializer<T>
+ implements ContextualDeserializer
+{
+ protected final MapType _mapType;
+
+ /**
+ * Key deserializer used, if not null. If null, String from JSON
+ * content is used as is.
+ */
+ protected KeyDeserializer _keyDeserializer;
+
+ /**
+ * Value deserializer.
+ */
+ protected JsonDeserializer<?> _valueDeserializer;
+
+ /**
+ * If value instances have polymorphic type information, this
+ * is the type deserializer that can handle it
+ */
+ protected final TypeDeserializer _typeDeserializerForValue;
+
+ /*
+ /**********************************************************
+ /* Life-cycle
+ /**********************************************************
+ */
+
+ protected GuavaMapDeserializer(MapType type, KeyDeserializer keyDeser,
+ TypeDeserializer typeDeser, JsonDeserializer<?> deser)
+ {
+ _mapType = type;
+ _keyDeserializer = keyDeser;
+ _typeDeserializerForValue = typeDeser;
+ _valueDeserializer = deser;
+ }
+
+ /**
+ * Overridable fluent factory method used for creating contextual
+ * instances.
+ */
+ public abstract GuavaMapDeserializer<T> withResolved(KeyDeserializer keyDeser,
+ TypeDeserializer typeDeser, JsonDeserializer<?> valueDeser);
+
+ /*
+ /**********************************************************
+ /* Validation, post-processing
+ /**********************************************************
+ */
+
+ /**
+ * Method called to finalize setup of this deserializer,
+ * after deserializer itself has been registered. This
+ * is needed to handle recursive and transitive dependencies.
+ */
+ @Override
+ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
+ BeanProperty property) throws JsonMappingException
+ {
+ KeyDeserializer keyDeser = _keyDeserializer;
+ JsonDeserializer<?> deser = _valueDeserializer;
+ TypeDeserializer typeDeser = _typeDeserializerForValue;
+ // Do we need any contextualization?
+ if ((keyDeser != null) && (deser != null) && (typeDeser == null)) { // nope
+ return this;
+ }
+ if (keyDeser == null) {
+ keyDeser = ctxt.findKeyDeserializer(_mapType.getKeyType(), property);
+ }
+ if (deser == null) {
+ deser = ctxt.findContextualValueDeserializer(_mapType.getContentType(), property);
+ }
+ if (typeDeser != null) {
+ typeDeser = typeDeser.forProperty(property);
+ }
+ return withResolved(keyDeser, typeDeser, deser);
+ }
+
+ /*
+ /**********************************************************
+ /* Deserialization interface
+ /**********************************************************
+ */
+
+ /**
+ * Base implementation that does not assume specific type
+ * inclusion mechanism. Sub-classes are expected to override
+ * this method if they are to handle type information.
+ */
+ @Override
+ public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt,
+ TypeDeserializer typeDeserializer)
+ throws IOException, JsonProcessingException
+ {
+ // note: call "...FromObject" because expected output structure
+ // for value is JSON Object (regardless of contortions used for type id)
+ return typeDeserializer.deserializeTypedFromObject(jp, ctxt);
+ }
+
+ @Override
+ public T deserialize(JsonParser jp, DeserializationContext ctxt)
+ throws IOException, JsonProcessingException
+ {
+ // Ok: must point to START_OBJECT or FIELD_NAME
+ JsonToken t = jp.getCurrentToken();
+ if (t == JsonToken.START_OBJECT) { // If START_OBJECT, move to next; may also be END_OBJECT
+ t = jp.nextToken();
+ }
+ if (t != JsonToken.FIELD_NAME && t != JsonToken.END_OBJECT) {
+ throw ctxt.mappingException(_mapType.getRawClass());
+ }
+ return _deserializeEntries(jp, ctxt);
+ }
+
+ /*
+ /**********************************************************************
+ /* Abstract methods for impl classes
+ /**********************************************************************
+ */
+
+ protected abstract T _deserializeEntries(JsonParser jp, DeserializationContext ctxt)
+ throws IOException, JsonProcessingException;
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaMultisetDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaMultisetDeserializer.java
new file mode 100644
index 0000000..df34075
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaMultisetDeserializer.java
@@ -0,0 +1,70 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.google.common.collect.Multiset;
+
+abstract class GuavaMultisetDeserializer<T extends Multiset<Object>>
+ extends GuavaCollectionDeserializer<T>
+{
+ private static final long serialVersionUID = 1L;
+
+ GuavaMultisetDeserializer(CollectionType type, TypeDeserializer typeDeser, JsonDeserializer<?> deser) {
+ super(type, typeDeser, deser);
+ }
+
+ protected abstract T createMultiset();
+
+ @Override
+ protected T _deserializeContents(JsonParser jp, DeserializationContext ctxt) throws IOException,
+ JsonProcessingException {
+ JsonDeserializer<?> valueDes = _valueDeserializer;
+ JsonToken t;
+ final TypeDeserializer typeDeser = _typeDeserializerForValue;
+ T set = createMultiset();
+
+ while ((t = jp.nextToken()) != JsonToken.END_ARRAY) {
+ Object value;
+
+ if (t == JsonToken.VALUE_NULL) {
+ value = null;
+ } else if (typeDeser == null) {
+ value = valueDes.deserialize(jp, ctxt);
+ } else {
+ value = valueDes.deserializeWithType(jp, ctxt, typeDeser);
+ }
+ set.add(value);
+ }
+ return set;
+ }
+
+ @Override
+ protected T _deserializeFromSingleValue(JsonParser jp, DeserializationContext ctxt)
+ throws IOException, JsonProcessingException
+ {
+ JsonDeserializer<?> valueDes = _valueDeserializer;
+ final TypeDeserializer typeDeser = _typeDeserializerForValue;
+ JsonToken t = jp.getCurrentToken();
+
+ Object value;
+
+ if (t == JsonToken.VALUE_NULL) {
+ value = null;
+ } else if (typeDeser == null) {
+ value = valueDes.deserialize(jp, ctxt);
+ } else {
+ value = valueDes.deserializeWithType(jp, ctxt, typeDeser);
+ }
+ T result = createMultiset();
+ result.add(value);
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaOptionalDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaOptionalDeserializer.java
new file mode 100644
index 0000000..5462b11
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaOptionalDeserializer.java
@@ -0,0 +1,126 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.google.common.base.Optional;
+
+public class GuavaOptionalDeserializer
+ extends StdDeserializer<Optional<?>>
+ implements ContextualDeserializer
+{
+ private static final long serialVersionUID = 1L;
+
+ protected final JavaType _fullType;
+
+ protected final JavaType _referenceType;
+
+ protected final JsonDeserializer<?> _valueDeserializer;
+
+ protected final TypeDeserializer _valueTypeDeserializer;
+
+ public GuavaOptionalDeserializer(JavaType fullType, JavaType refType,
+ TypeDeserializer typeDeser, JsonDeserializer<?> valueDeser)
+ {
+ super(fullType);
+ _fullType = fullType;
+ _referenceType = refType;
+ _valueTypeDeserializer = typeDeser;
+ _valueDeserializer = valueDeser;
+ }
+
+ @Override
+ public JavaType getValueType() { return _fullType; }
+
+ @Override
+ public Optional<?> getNullValue() { return Optional.absent(); }
+
+ /**
+ * Overridable fluent factory method used for creating contextual
+ * instances.
+ */
+ protected GuavaOptionalDeserializer withResolved(
+ TypeDeserializer typeDeser, JsonDeserializer<?> valueDeser)
+ {
+ return new GuavaOptionalDeserializer(_fullType, _referenceType,
+ typeDeser, valueDeser);
+ }
+
+ /*
+ /**********************************************************
+ /* Validation, post-processing
+ /**********************************************************
+ */
+
+ /**
+ * Method called to finalize setup of this deserializer,
+ * after deserializer itself has been registered. This
+ * is needed to handle recursive and transitive dependencies.
+ */
+ @Override
+ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
+ BeanProperty property) throws JsonMappingException
+ {
+ JsonDeserializer<?> deser = _valueDeserializer;
+ TypeDeserializer typeDeser = _valueTypeDeserializer;
+
+ if (deser == null) {
+ deser = ctxt.findContextualValueDeserializer(_referenceType, property);
+ } else { // otherwise directly assigned, probably not contextual yet:
+ deser = ctxt.handleSecondaryContextualization(deser, property);
+ }
+ if (typeDeser != null) {
+ typeDeser = typeDeser.forProperty(property);
+ }
+ if (deser == _valueDeserializer && typeDeser == _valueTypeDeserializer) {
+ return this;
+ }
+ return withResolved(typeDeser, deser);
+ }
+
+ @Override
+ public Optional<?> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
+ JsonProcessingException
+ {
+ Object refd;
+
+ if (_valueTypeDeserializer == null) {
+ refd = _valueDeserializer.deserialize(jp, ctxt);
+ } else {
+ refd = _valueDeserializer.deserializeWithType(jp, ctxt, _valueTypeDeserializer);
+ }
+ return Optional.of(refd);
+ }
+
+ /* NOTE: usually should not need this method... but for some reason, it is needed here.
+ */
+ @Override
+ public Optional<?> deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
+ throws IOException, JsonProcessingException
+ {
+ final JsonToken t = jp.getCurrentToken();
+ if (t == JsonToken.VALUE_NULL) {
+ return getNullValue();
+ }
+ // 03-Nov-2013, tatu: This gets rather tricky with "natural" types
+ // (String, Integer, Boolean), which do NOT include type information.
+ // These might actually be handled ok except that nominal type here
+ // is `Optional`, so special handling is not invoked; instead, need
+ // to do a work-around here.
+ if (t != null && t.isScalarValue()) {
+ return deserialize(jp, ctxt);
+ }
+ // with type deserializer to use here? Looks like we get passed same one?
+ return Optional.of(typeDeserializer.deserializeTypedFromAny(jp, ctxt));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/HashMultisetDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/HashMultisetDeserializer.java
new file mode 100644
index 0000000..212c6b1
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/HashMultisetDeserializer.java
@@ -0,0 +1,31 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.google.common.collect.HashMultiset;
+
+public class HashMultisetDeserializer
+ extends GuavaMultisetDeserializer<HashMultiset<Object>>
+{
+ private static final long serialVersionUID = 1L;
+
+ public HashMultisetDeserializer(CollectionType type,
+ TypeDeserializer typeDeser, JsonDeserializer<?> deser)
+ {
+ super(type, typeDeser, deser);
+ }
+
+ @Override
+ public HashMultisetDeserializer withResolved(TypeDeserializer typeDeser,
+ JsonDeserializer<?> valueDeser) {
+ return new HashMultisetDeserializer(_containerType,
+ typeDeser, valueDeser);
+ }
+
+ @Override
+ protected HashMultiset<Object> createMultiset() {
+ return HashMultiset.<Object> create();
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/HostAndPortDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/HostAndPortDeserializer.java
new file mode 100644
index 0000000..09aa262
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/HostAndPortDeserializer.java
@@ -0,0 +1,42 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.*;
+
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+
+import com.google.common.net.HostAndPort;
+
+public class HostAndPortDeserializer extends StdDeserializer<HostAndPort>
+{
+ private static final long serialVersionUID = 1L;
+
+ public final static HostAndPortDeserializer std = new HostAndPortDeserializer();
+
+ public HostAndPortDeserializer() { super(HostAndPort.class); }
+
+ @Override
+ public HostAndPort deserialize(JsonParser jp, DeserializationContext ctxt)
+ throws IOException
+ {
+ JsonToken t = jp.getCurrentToken();
+ if (t == JsonToken.START_OBJECT) { // old style
+ JsonNode root = jp.readValueAsTree();
+ String host = root.path("hostText").asText();
+ JsonNode n = root.get("port");
+ if (n == null) {
+ return HostAndPort.fromString(host);
+ }
+ return HostAndPort.fromParts(host, n.asInt());
+ }
+ if (t == JsonToken.VALUE_STRING) {
+ return HostAndPort.fromString(jp.getText().trim());
+ }
+ // could also support arrays?
+ throw ctxt.wrongTokenException(jp, JsonToken.VALUE_STRING, "(or JSON Object)");
+ }
+
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableBiMapDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableBiMapDeserializer.java
new file mode 100644
index 0000000..c6a7f51
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableBiMapDeserializer.java
@@ -0,0 +1,26 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.KeyDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.MapType;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableMap.Builder;
+
+public class ImmutableBiMapDeserializer extends GuavaImmutableMapDeserializer<ImmutableBiMap<Object, Object>> {
+ public ImmutableBiMapDeserializer(MapType type, KeyDeserializer keyDeser, TypeDeserializer typeDeser,
+ JsonDeserializer<?> deser) {
+ super(type, keyDeser, typeDeser, deser);
+ }
+
+ @Override
+ protected Builder<Object, Object> createBuilder() {
+ return ImmutableBiMap.builder();
+ }
+
+ @Override
+ public GuavaMapDeserializer<ImmutableBiMap<Object, Object>> withResolved(KeyDeserializer keyDeser,
+ TypeDeserializer typeDeser, JsonDeserializer<?> valueDeser) {
+ return new ImmutableBiMapDeserializer(_mapType, keyDeser, typeDeser, valueDeser);
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableListDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableListDeserializer.java
new file mode 100644
index 0000000..51a3e5b
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableListDeserializer.java
@@ -0,0 +1,37 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import com.google.common.collect.ImmutableList;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.CollectionType;
+
+public class ImmutableListDeserializer extends
+ GuavaImmutableCollectionDeserializer<ImmutableList<Object>>
+{
+ private static final long serialVersionUID = 1L;
+
+ public ImmutableListDeserializer(CollectionType type,
+ TypeDeserializer typeDeser, JsonDeserializer<?> deser) {
+ super(type, typeDeser, deser);
+ }
+
+ @Override
+ public ImmutableListDeserializer withResolved(TypeDeserializer typeDeser,
+ JsonDeserializer<?> valueDeser) {
+ return new ImmutableListDeserializer(_containerType, typeDeser,
+ valueDeser);
+ }
+
+ /*
+ /**********************************************************
+ /* Deserialization
+ /**********************************************************
+ */
+
+ @Override
+ protected ImmutableList.Builder<Object> createBuilder() {
+ ImmutableList.Builder<Object> builder = ImmutableList.builder();
+ return builder;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableMapDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableMapDeserializer.java
new file mode 100644
index 0000000..6421b49
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableMapDeserializer.java
@@ -0,0 +1,31 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.KeyDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.MapType;
+import com.google.common.collect.ImmutableMap;
+
+public class ImmutableMapDeserializer
+ extends GuavaImmutableMapDeserializer<ImmutableMap<Object, Object>>
+{
+ public ImmutableMapDeserializer(MapType type, KeyDeserializer keyDeser,
+ TypeDeserializer typeDeser, JsonDeserializer<?> deser)
+ {
+ super(type, keyDeser, typeDeser, deser);
+ }
+
+ @Override
+ public ImmutableMapDeserializer withResolved(KeyDeserializer keyDeser,
+ TypeDeserializer typeDeser, JsonDeserializer<?> valueDeser) {
+ return new ImmutableMapDeserializer(_mapType, keyDeser,
+ typeDeser, valueDeser);
+ }
+
+ @Override
+ protected ImmutableMap.Builder<Object, Object> createBuilder() {
+ return ImmutableMap.builder();
+ }
+
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableMultisetDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableMultisetDeserializer.java
new file mode 100644
index 0000000..c488ed8
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableMultisetDeserializer.java
@@ -0,0 +1,27 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.google.common.collect.ImmutableCollection.Builder;
+import com.google.common.collect.ImmutableMultiset;
+
+public class ImmutableMultisetDeserializer extends GuavaImmutableCollectionDeserializer<ImmutableMultiset<Object>>
+{
+ private static final long serialVersionUID = 1L;
+
+ public ImmutableMultisetDeserializer(CollectionType type, TypeDeserializer typeDeser, JsonDeserializer<?> deser) {
+ super(type, typeDeser, deser);
+ }
+
+ @Override
+ protected Builder<Object> createBuilder() {
+ return ImmutableMultiset.builder();
+ }
+
+ @Override
+ public GuavaCollectionDeserializer<ImmutableMultiset<Object>> withResolved(TypeDeserializer typeDeser,
+ JsonDeserializer<?> valueDeser) {
+ return new ImmutableMultisetDeserializer(_containerType, typeDeser, valueDeser);
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSetDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSetDeserializer.java
new file mode 100644
index 0000000..132200d
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSetDeserializer.java
@@ -0,0 +1,30 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.google.common.collect.ImmutableCollection.Builder;
+import com.google.common.collect.ImmutableSet;
+
+public class ImmutableSetDeserializer extends GuavaImmutableCollectionDeserializer<ImmutableSet<Object>>
+{
+ private static final long serialVersionUID = 1L;
+
+ public ImmutableSetDeserializer(CollectionType type,
+ TypeDeserializer typeDeser, JsonDeserializer<?> deser)
+ {
+ super(type, typeDeser, deser);
+ }
+
+ @Override
+ public ImmutableSetDeserializer withResolved(TypeDeserializer typeDeser,
+ JsonDeserializer<?> valueDeser) {
+ return new ImmutableSetDeserializer(_containerType,
+ typeDeser, valueDeser);
+ }
+
+ @Override
+ protected Builder<Object> createBuilder() {
+ return ImmutableSet.builder();
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSortedMapDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSortedMapDeserializer.java
new file mode 100644
index 0000000..179e1de
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSortedMapDeserializer.java
@@ -0,0 +1,36 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.KeyDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.MapType;
+import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.ImmutableSortedMap;
+
+public class ImmutableSortedMapDeserializer extends GuavaImmutableMapDeserializer<ImmutableSortedMap<Object, Object>> {
+
+ public ImmutableSortedMapDeserializer(MapType type, KeyDeserializer keyDeser, TypeDeserializer typeDeser,
+ JsonDeserializer<?> deser) {
+ super(type, keyDeser, typeDeser, deser);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected Builder<Object, Object> createBuilder() {
+ /*
+ * Not quite sure what to do with sorting/ordering; may require better
+ * support either via annotations, or via custom serialization (bean
+ * style that includes ordering aspects)
+ */
+ @SuppressWarnings("rawtypes")
+ ImmutableSortedMap.Builder<?, Object> naturalOrder = ImmutableSortedMap.<Comparable, Object>naturalOrder();
+ ImmutableSortedMap.Builder<Object, Object> builder = (ImmutableSortedMap.Builder<Object, Object>) naturalOrder;
+ return builder;
+ }
+
+ @Override
+ public GuavaMapDeserializer<ImmutableSortedMap<Object, Object>> withResolved(KeyDeserializer keyDeser,
+ TypeDeserializer typeDeser, JsonDeserializer<?> valueDeser) {
+ return new ImmutableSortedMapDeserializer(_mapType, keyDeser, typeDeser, valueDeser);
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSortedSetDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSortedSetDeserializer.java
new file mode 100644
index 0000000..ff46bb4
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSortedSetDeserializer.java
@@ -0,0 +1,38 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.google.common.collect.ImmutableCollection.Builder;
+import com.google.common.collect.ImmutableSortedSet;
+
+public class ImmutableSortedSetDeserializer extends GuavaImmutableCollectionDeserializer<ImmutableSortedSet<Object>>
+{
+ private static final long serialVersionUID = 1L;
+
+ public ImmutableSortedSetDeserializer(CollectionType type,
+ TypeDeserializer typeDeser, JsonDeserializer<?> deser)
+ {
+ super(type, typeDeser, deser);
+ }
+
+ @Override
+ public ImmutableSortedSetDeserializer withResolved(TypeDeserializer typeDeser,
+ JsonDeserializer<?> valueDeser) {
+ return new ImmutableSortedSetDeserializer(_containerType,
+ typeDeser, valueDeser);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected Builder<Object> createBuilder() {
+ /* Not quite sure what to do with sorting/ordering; may require better support either
+ * via annotations, or via custom serialization (bean style that includes ordering
+ * aspects)
+ */
+ @SuppressWarnings("rawtypes")
+ ImmutableSortedSet.Builder<?> builderComp = ImmutableSortedSet.<Comparable> naturalOrder();
+ ImmutableSortedSet.Builder<Object> builder = (ImmutableSortedSet.Builder<Object>) builderComp;
+ return builder;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/LinkedHashMultisetDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/LinkedHashMultisetDeserializer.java
new file mode 100644
index 0000000..7306ca6
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/LinkedHashMultisetDeserializer.java
@@ -0,0 +1,26 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.google.common.collect.LinkedHashMultiset;
+
+public class LinkedHashMultisetDeserializer extends GuavaMultisetDeserializer<LinkedHashMultiset<Object>>
+{
+ private static final long serialVersionUID = 1L;
+
+ public LinkedHashMultisetDeserializer(CollectionType type, TypeDeserializer typeDeser, JsonDeserializer<?> deser) {
+ super(type, typeDeser, deser);
+ }
+
+ @Override
+ protected LinkedHashMultiset<Object> createMultiset() {
+ return LinkedHashMultiset.create();
+ }
+
+ @Override
+ public GuavaCollectionDeserializer<LinkedHashMultiset<Object>> withResolved(TypeDeserializer typeDeser,
+ JsonDeserializer<?> valueDeser) {
+ return new LinkedHashMultisetDeserializer(_containerType, typeDeser, valueDeser);
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/RangeDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/RangeDeserializer.java
new file mode 100644
index 0000000..7f8a74d
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/RangeDeserializer.java
@@ -0,0 +1,181 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.datatype.guava.deser.util.RangeFactory;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.BoundType;
+import com.google.common.collect.Range;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
+/**
+ * Jackson deserializer for a Guava {@link Range}.
+ *<p>
+ * TODO: I think it would make sense to reimplement this deserializer to
+ * use Delegating Deserializer, using a POJO as an intermediate form (properties
+ * could be of type {@link java.lang.Object})
+ * This would also also simplify the implementation a bit.
+ */
+public class RangeDeserializer
+ extends StdDeserializer<Range<?>>
+ implements ContextualDeserializer
+{
+ private static final long serialVersionUID = 1L;
+
+ protected final JavaType _rangeType;
+
+ protected final JsonDeserializer<Object> _endpointDeserializer;
+
+ /*
+ /**********************************************************
+ /* Life-cycle
+ /**********************************************************
+ */
+
+ public RangeDeserializer(JavaType rangeType) {
+ this(rangeType, null);
+ }
+
+ @SuppressWarnings("unchecked")
+ public RangeDeserializer(JavaType rangeType, JsonDeserializer<?> endpointDeser)
+ {
+ super(rangeType);
+ _rangeType = rangeType;
+ _endpointDeserializer = (JsonDeserializer<Object>) endpointDeser;
+ }
+
+ @Override
+ public JavaType getValueType() { return _rangeType; }
+
+ @Override
+ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
+ BeanProperty property) throws JsonMappingException
+ {
+ if (_endpointDeserializer == null) {
+ JavaType endpointType = _rangeType.containedType(0);
+ if (endpointType == null) { // should this ever occur?
+ endpointType = TypeFactory.unknownType();
+ }
+ JsonDeserializer<Object> deser = ctxt.findContextualValueDeserializer(endpointType, property);
+ return new RangeDeserializer(_rangeType, deser);
+ }
+ return this;
+ }
+
+ /*
+ /**********************************************************
+ /* Actual deserialization
+ /**********************************************************
+ */
+
+ @Override
+ public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt,
+ TypeDeserializer typeDeserializer)
+ throws IOException, JsonProcessingException
+ {
+ return typeDeserializer.deserializeTypedFromObject(jp, ctxt);
+ }
+
+ @Override
+ public Range<?> deserialize(JsonParser parser, DeserializationContext context)
+ throws IOException, JsonProcessingException
+ {
+ // NOTE: either START_OBJECT _or_ FIELD_NAME fine; latter for polymorphic cases
+ JsonToken t = parser.getCurrentToken();
+ if (t == JsonToken.START_OBJECT) {
+ t = parser.nextToken();
+ }
+
+ Comparable<?> lowerEndpoint = null;
+ Comparable<?> upperEndpoint = null;
+ BoundType lowerBoundType = null;
+ BoundType upperBoundType = null;
+
+ for (; t != JsonToken.END_OBJECT; t = parser.nextToken()) {
+ expect(parser, JsonToken.FIELD_NAME, t);
+ String fieldName = parser.getCurrentName();
+ try {
+ if (fieldName.equals("lowerEndpoint")) {
+ Preconditions.checkState(lowerEndpoint == null, "'lowerEndpoint' field included multiple times.");
+ parser.nextToken();
+ lowerEndpoint = deserializeEndpoint(parser, context);
+ } else if (fieldName.equals("upperEndpoint")) {
+ Preconditions.checkState(upperEndpoint == null, "'upperEndpoint' field included multiple times.");
+ parser.nextToken();
+ upperEndpoint = deserializeEndpoint(parser, context);
+ } else if (fieldName.equals("lowerBoundType")) {
+ Preconditions.checkState(lowerBoundType == null, "'lowerBoundType' field included multiple times.");
+ parser.nextToken();
+ lowerBoundType = deserializeBoundType(parser);
+ } else if (fieldName.equals("upperBoundType")) {
+ Preconditions.checkState(upperBoundType == null, "'upperBoundType' field included multiple times.");
+ parser.nextToken();
+ upperBoundType = deserializeBoundType(parser);
+ } else {
+ throw context.mappingException("Unexpected Range field: " + fieldName);
+ }
+ } catch (IllegalStateException e) {
+ throw new JsonMappingException(e.getMessage());
+ }
+ }
+
+ try {
+ if ((lowerEndpoint != null) && (upperEndpoint != null)) {
+ Preconditions.checkState(lowerEndpoint.getClass() == upperEndpoint.getClass(),
+ "Endpoint types are not the same - 'lowerEndpoint' deserialized to [%s], and 'upperEndpoint' deserialized to [%s].",
+ lowerEndpoint.getClass().getName(),
+ upperEndpoint.getClass().getName());
+ Preconditions.checkState(lowerBoundType != null, "'lowerEndpoint' field found, but not 'lowerBoundType'");
+ Preconditions.checkState(upperBoundType != null, "'upperEndpoint' field found, but not 'upperBoundType'");
+ return RangeFactory.range(lowerEndpoint, lowerBoundType, upperEndpoint, upperBoundType);
+ }
+ if (lowerEndpoint != null) {
+ Preconditions.checkState(lowerBoundType != null, "'lowerEndpoint' field found, but not 'lowerBoundType'");
+ return RangeFactory.downTo(lowerEndpoint, lowerBoundType);
+ }
+ if (upperEndpoint != null) {
+ Preconditions.checkState(upperBoundType != null, "'upperEndpoint' field found, but not 'upperBoundType'");
+ return RangeFactory.upTo(upperEndpoint, upperBoundType);
+ }
+ return RangeFactory.all();
+ } catch (IllegalStateException e) {
+ throw new JsonMappingException(e.getMessage());
+ }
+ }
+
+ private BoundType deserializeBoundType(JsonParser parser) throws IOException
+ {
+ expect(parser, JsonToken.VALUE_STRING, parser.getCurrentToken());
+ String name = parser.getText();
+ try {
+ return BoundType.valueOf(name);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalStateException("[" + name + "] is not a valid BoundType name.");
+ }
+ }
+
+ private Comparable<?> deserializeEndpoint(JsonParser parser, DeserializationContext context) throws IOException
+ {
+ Object obj = _endpointDeserializer.deserialize(parser, context);
+ if (!(obj instanceof Comparable)) {
+ throw context.mappingException(String.format(
+ "Field [%s] deserialized to [%s], which does not implement Comparable.",
+ parser.getCurrentName(), obj.getClass().getName()));
+ }
+ return (Comparable<?>) obj;
+ }
+
+ private void expect(JsonParser jp, JsonToken expected, JsonToken actual) throws JsonMappingException
+ {
+ if (actual != expected) {
+ throw new JsonMappingException("Expecting " + expected + ", found " + actual, jp.getCurrentLocation());
+ }
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/TreeMultisetDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/TreeMultisetDeserializer.java
new file mode 100644
index 0000000..ff0ab47
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/TreeMultisetDeserializer.java
@@ -0,0 +1,29 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.google.common.collect.TreeMultiset;
+
+public class TreeMultisetDeserializer extends GuavaMultisetDeserializer<TreeMultiset<Object>>
+{
+ private static final long serialVersionUID = 1L;
+
+ public TreeMultisetDeserializer(CollectionType type, TypeDeserializer typeDeser, JsonDeserializer<?> deser) {
+ super(type, typeDeser, deser);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected TreeMultiset<Object> createMultiset() {
+ @SuppressWarnings("rawtypes")
+ TreeMultiset<?> naturalOrder = TreeMultiset.<Comparable> create();
+ return (TreeMultiset<Object>) naturalOrder;
+ }
+
+ @Override
+ public GuavaCollectionDeserializer<TreeMultiset<Object>> withResolved(TypeDeserializer typeDeser,
+ JsonDeserializer<?> valueDeser) {
+ return new TreeMultisetDeserializer(_containerType, typeDeser, valueDeser);
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/GuavaMultimapDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/GuavaMultimapDeserializer.java
new file mode 100644
index 0000000..2e422d7
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/GuavaMultimapDeserializer.java
@@ -0,0 +1,183 @@
+package com.fasterxml.jackson.datatype.guava.deser.multimap;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.KeyDeserializer;
+import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.MapLikeType;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Multimap;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.List;
+
+/**
+ * @author mvolkhart
+ */
+public abstract class GuavaMultimapDeserializer<T extends Multimap<Object,
+ Object>> extends JsonDeserializer<T> implements ContextualDeserializer {
+
+ private static final List<String> METHOD_NAMES = ImmutableList.of("copyOf", "create");
+ private final MapLikeType type;
+ private final KeyDeserializer keyDeserializer;
+ private final TypeDeserializer elementTypeDeserializer;
+ private final JsonDeserializer<?> elementDeserializer;
+ /**
+ * Since we have to use a method to transform from a known multi-map type into actual one, we'll
+ * resolve method just once, use it. Note that if this is set to null, we can just construct a
+ * {@link com.google.common.collect.LinkedListMultimap} instance and be done with it.
+ */
+ private final Method creatorMethod;
+
+ public GuavaMultimapDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
+ TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer) {
+ this(type, keyDeserializer, elementTypeDeserializer, elementDeserializer,
+ findTransformer(type.getRawClass()));
+ }
+
+ public GuavaMultimapDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
+ TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer,
+ Method creatorMethod) {
+ this.type = type;
+ this.keyDeserializer = keyDeserializer;
+ this.elementTypeDeserializer = elementTypeDeserializer;
+ this.elementDeserializer = elementDeserializer;
+ this.creatorMethod = creatorMethod;
+ }
+
+ private static Method findTransformer(Class<?> rawType) {
+ // Very first thing: if it's a "standard multi-map type", can avoid copying
+ if (rawType == LinkedListMultimap.class || rawType == ListMultimap.class || rawType ==
+ Multimap.class) {
+ return null;
+ }
+
+ // First, check type itself for matching methods
+ for (String methodName : METHOD_NAMES) {
+ try {
+ Method m = rawType.getMethod(methodName, Multimap.class);
+ if (m != null) {
+ return m;
+ }
+ } catch (NoSuchMethodException e) {
+ }
+ // pass SecurityExceptions as-is:
+ // } catch (SecurityException e) { }
+ }
+
+ // If not working, possibly super types too (should we?)
+ for (String methodName : METHOD_NAMES) {
+ try {
+ Method m = rawType.getMethod(methodName, Multimap.class);
+ if (m != null) {
+ return m;
+ }
+ } catch (NoSuchMethodException e) {
+ }
+ // pass SecurityExceptions as-is:
+ // } catch (SecurityException e) { }
+ }
+
+ return null;
+ }
+
+ protected abstract T createMultimap();
+
+ /**
+ * We need to use this method to properly handle possible contextual variants of key and value
+ * deserializers, as well as type deserializers.
+ */
+ @Override
+ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
+ BeanProperty property) throws JsonMappingException {
+ KeyDeserializer kd = keyDeserializer;
+ if (kd == null) {
+ kd = ctxt.findKeyDeserializer(type.getKeyType(), property);
+ }
+ JsonDeserializer<?> ed = elementDeserializer;
+ if (ed == null) {
+ ed = ctxt.findContextualValueDeserializer(type.getContentType(), property);
+ }
+ // Type deserializer is slightly different; must be passed, but needs to become contextual:
+ TypeDeserializer etd = elementTypeDeserializer;
+ if (etd != null && property != null) {
+ etd = etd.forProperty(property);
+ }
+ return (_createContextual(type, kd, etd, ed, creatorMethod));
+ }
+
+ protected abstract JsonDeserializer<?> _createContextual(MapLikeType t,
+ KeyDeserializer kd, TypeDeserializer typeDeserializer,
+ JsonDeserializer<?> ed, Method method);
+
+ @Override
+ public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
+ JsonProcessingException {
+
+ T multimap = createMultimap();
+
+ expect(jp, JsonToken.START_OBJECT);
+
+ while (jp.nextToken() != JsonToken.END_OBJECT) {
+ final Object key;
+ if (keyDeserializer != null) {
+ key = keyDeserializer.deserializeKey(jp.getCurrentName(), ctxt);
+ } else {
+ key = jp.getCurrentName();
+ }
+
+ jp.nextToken();
+ expect(jp, JsonToken.START_ARRAY);
+
+ while (jp.nextToken() != JsonToken.END_ARRAY) {
+ final Object value;
+ if (jp.getCurrentToken() == JsonToken.VALUE_NULL) {
+ value = null;
+ } else if (elementTypeDeserializer != null) {
+ value = elementDeserializer.deserializeWithType(jp, ctxt,
+ elementTypeDeserializer);
+ } else {
+ value = elementDeserializer.deserialize(jp, ctxt);
+ }
+ multimap.put(key, value);
+ }
+ }
+ if (creatorMethod == null) {
+ return multimap;
+ }
+ try {
+ @SuppressWarnings("unchecked") T map = (T) creatorMethod.invoke(null, multimap);
+ return map;
+ } catch (InvocationTargetException e) {
+ throw new JsonMappingException("Could not map to " + type, _peel(e));
+ } catch (IllegalArgumentException e) {
+ throw new JsonMappingException("Could not map to " + type, _peel(e));
+ } catch (IllegalAccessException e) {
+ throw new JsonMappingException("Could not map to " + type, _peel(e));
+ }
+ }
+
+ private void expect(JsonParser jp, JsonToken token) throws IOException {
+ if (jp.getCurrentToken() != token) {
+ throw new JsonMappingException("Expecting " + token + ", found " + jp.getCurrentToken(),
+ jp.getCurrentLocation());
+ }
+ }
+
+ private Throwable _peel(Throwable t) {
+ while (t.getCause() != null) {
+ t = t.getCause();
+ }
+ return t;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/list/ArrayListMultimapDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/list/ArrayListMultimapDeserializer.java
new file mode 100644
index 0000000..cdf673b
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/list/ArrayListMultimapDeserializer.java
@@ -0,0 +1,43 @@
+package com.fasterxml.jackson.datatype.guava.deser.multimap.list;
+
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.KeyDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.MapLikeType;
+import com.fasterxml.jackson.datatype.guava.deser.multimap.GuavaMultimapDeserializer;
+import com.google.common.collect.ArrayListMultimap;
+
+import java.lang.reflect.Method;
+
+/**
+ * Provides deserialization for the Guava ArrayListMultimap class.
+ *
+ * @author mvolkhart
+ */
+public class ArrayListMultimapDeserializer extends GuavaMultimapDeserializer<ArrayListMultimap<Object,
+ Object>> {
+
+ public ArrayListMultimapDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
+ TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer) {
+ super(type, keyDeserializer, elementTypeDeserializer, elementDeserializer);
+ }
+
+ public ArrayListMultimapDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
+ TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer,
+ Method creatorMethod) {
+ super(type, keyDeserializer, elementTypeDeserializer, elementDeserializer, creatorMethod);
+ }
+
+ @Override
+ protected ArrayListMultimap<Object, Object> createMultimap() {
+ return ArrayListMultimap.create();
+ }
+
+ @Override
+ protected JsonDeserializer<?> _createContextual(MapLikeType type,
+ KeyDeserializer keyDeserializer, TypeDeserializer typeDeserializer,
+ JsonDeserializer<?> elementDeserializer, Method method) {
+ return new ArrayListMultimapDeserializer(type, keyDeserializer, typeDeserializer,
+ elementDeserializer, method);
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/list/LinkedListMultimapDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/list/LinkedListMultimapDeserializer.java
new file mode 100644
index 0000000..263f6fe
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/list/LinkedListMultimapDeserializer.java
@@ -0,0 +1,43 @@
+package com.fasterxml.jackson.datatype.guava.deser.multimap.list;
+
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.KeyDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.MapLikeType;
+import com.fasterxml.jackson.datatype.guava.deser.multimap.GuavaMultimapDeserializer;
+import com.google.common.collect.LinkedListMultimap;
+
+import java.lang.reflect.Method;
+
+/**
+ * Provides deserialization for the Guava LinkedListMultimap class.
+ *
+ * @author mvolkhart
+ */
+public class LinkedListMultimapDeserializer
+ extends GuavaMultimapDeserializer<LinkedListMultimap<Object,Object>>
+{
+ public LinkedListMultimapDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
+ TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer) {
+ super(type, keyDeserializer, elementTypeDeserializer, elementDeserializer);
+ }
+
+ public LinkedListMultimapDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
+ TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer,
+ Method creatorMethod) {
+ super(type, keyDeserializer, elementTypeDeserializer, elementDeserializer, creatorMethod);
+ }
+
+ @Override
+ protected LinkedListMultimap<Object, Object> createMultimap() {
+ return LinkedListMultimap.create();
+ }
+
+ @Override
+ protected JsonDeserializer<?> _createContextual(MapLikeType type,
+ KeyDeserializer keyDeserializer, TypeDeserializer typeDeserializer,
+ JsonDeserializer<?> elementDeserializer, Method method) {
+ return new LinkedListMultimapDeserializer(type, keyDeserializer, typeDeserializer,
+ elementDeserializer, method);
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/set/HashMultimapDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/set/HashMultimapDeserializer.java
new file mode 100644
index 0000000..7569321
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/set/HashMultimapDeserializer.java
@@ -0,0 +1,43 @@
+package com.fasterxml.jackson.datatype.guava.deser.multimap.set;
+
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.KeyDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.MapLikeType;
+import com.fasterxml.jackson.datatype.guava.deser.multimap.GuavaMultimapDeserializer;
+import com.google.common.collect.HashMultimap;
+
+import java.lang.reflect.Method;
+
+/**
+ * Provides deserialization for the Guava HashMultimap class.
+ *
+ * @author mvolkhart
+ */
+public class HashMultimapDeserializer extends GuavaMultimapDeserializer<HashMultimap<Object,
+ Object>> {
+
+ public HashMultimapDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
+ TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer) {
+ super(type, keyDeserializer, elementTypeDeserializer, elementDeserializer);
+ }
+
+ public HashMultimapDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
+ TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer,
+ Method creatorMethod) {
+ super(type, keyDeserializer, elementTypeDeserializer, elementDeserializer, creatorMethod);
+ }
+
+ @Override
+ protected HashMultimap<Object, Object> createMultimap() {
+ return HashMultimap.create();
+ }
+
+ @Override
+ protected JsonDeserializer<?> _createContextual(MapLikeType type,
+ KeyDeserializer keyDeserializer, TypeDeserializer typeDeserializer,
+ JsonDeserializer<?> elementDeserializer, Method method) {
+ return new HashMultimapDeserializer(type, keyDeserializer, typeDeserializer,
+ elementDeserializer, method);
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/set/LinkedHashMultimapDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/set/LinkedHashMultimapDeserializer.java
new file mode 100644
index 0000000..fc442ac
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/set/LinkedHashMultimapDeserializer.java
@@ -0,0 +1,43 @@
+package com.fasterxml.jackson.datatype.guava.deser.multimap.set;
+
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.KeyDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.MapLikeType;
+import com.fasterxml.jackson.datatype.guava.deser.multimap.GuavaMultimapDeserializer;
+import com.google.common.collect.LinkedHashMultimap;
+
+import java.lang.reflect.Method;
+
+/**
+ * Provides deserialization for the Guava LinkedHashMultimap class.
+ *
+ * @author mvolkhart
+ */
+public class LinkedHashMultimapDeserializer extends
+ GuavaMultimapDeserializer<LinkedHashMultimap<Object, Object>> {
+
+ public LinkedHashMultimapDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
+ TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer) {
+ super(type, keyDeserializer, elementTypeDeserializer, elementDeserializer);
+ }
+
+ public LinkedHashMultimapDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
+ TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer,
+ Method creatorMethod) {
+ super(type, keyDeserializer, elementTypeDeserializer, elementDeserializer, creatorMethod);
+ }
+
+ @Override
+ protected LinkedHashMultimap<Object, Object> createMultimap() {
+ return LinkedHashMultimap.create();
+ }
+
+ @Override
+ protected JsonDeserializer<?> _createContextual(MapLikeType type,
+ KeyDeserializer keyDeserializer, TypeDeserializer typeDeserializer,
+ JsonDeserializer<?> elementDeserializer, Method method) {
+ return new LinkedHashMultimapDeserializer(type, keyDeserializer, typeDeserializer,
+ elementDeserializer, method);
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/util/RangeFactory.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/util/RangeFactory.java
new file mode 100644
index 0000000..0d7c54d
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/util/RangeFactory.java
@@ -0,0 +1,181 @@
+
+package com.fasterxml.jackson.datatype.guava.deser.util;
+
+import com.google.common.collect.BoundType;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Range;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.Callable;
+
+/**
+ * A factory for creating Guava {@link Range}s that is compatible with Guava 10 and later.
+ *
+ * If Guava 10, 11, 12, or 13 is being used, the factory methods in the com.google.common.collect.Ranges class (a beta
+ * class that was removed in Guava 14) are used to reflectively instantiate Ranges. If Guava 14 or later is being used,
+ * the factory methods in the Range class itself (added in Guava 14) are used to instantiate Ranges.
+ */
+public class RangeFactory
+{
+ private static final String LEGACY_RANGES_CLASS_NAME = "com.google.common.collect.Ranges";
+
+ private static final String LEGACY_RANGE_METHOD_NAME = "range";
+ private static final String LEGACY_DOWN_TO_METHOD_NAME = "downTo";
+ private static final String LEGACY_UP_TO_METHOD_NAME = "upTo";
+ private static final String LEGACY_ALL_METHOD_NAME = "all";
+
+ private static Method legacyRangeMethod;
+ private static Method legacyDownToMethod;
+ private static Method legacyUpToMethod;
+ private static Method legacyAllMethod;
+
+ static
+ {
+ initLegacyRangeFactoryMethods();
+ }
+
+ private static void initLegacyRangeFactoryMethods()
+ {
+ try {
+ Class<?> rangesClass = Class.forName(LEGACY_RANGES_CLASS_NAME);
+ legacyRangeMethod = findMethod(rangesClass, LEGACY_RANGE_METHOD_NAME, Comparable.class, BoundType.class,
+ Comparable.class, BoundType.class);
+ legacyDownToMethod = findMethod(rangesClass, LEGACY_DOWN_TO_METHOD_NAME, Comparable.class, BoundType.class);
+ legacyUpToMethod = findMethod(rangesClass, LEGACY_UP_TO_METHOD_NAME, Comparable.class, BoundType.class);
+ legacyAllMethod = findMethod(rangesClass, LEGACY_ALL_METHOD_NAME);
+ } catch (ClassNotFoundException e) {
+ // ignore
+ }
+ }
+
+ // returns null if the method is not found
+ private static Method findMethod(Class<?> clazz, String methodName, Class<?> ... paramTypes)
+ {
+ try {
+ return clazz.getMethod(methodName, paramTypes);
+ } catch (NoSuchMethodException e) {
+ return null;
+ }
+ }
+
+ public static <C extends Comparable<?>> Range<C> open(C lowerEndpoint, C upperEndpoint)
+ {
+ return range(lowerEndpoint, BoundType.OPEN, upperEndpoint, BoundType.OPEN);
+ }
+
+ public static <C extends Comparable<?>> Range<C> openClosed(C lowerEndpoint, C upperEndpoint)
+ {
+ return range(lowerEndpoint, BoundType.OPEN, upperEndpoint, BoundType.CLOSED);
+ }
+
+ public static <C extends Comparable<?>> Range<C> closedOpen(C lowerEndpoint, C upperEndpoint)
+ {
+ return range(lowerEndpoint, BoundType.CLOSED, upperEndpoint, BoundType.OPEN);
+ }
+
+ public static <C extends Comparable<?>> Range<C> closed(C lowerEndpoint, C upperEndpoint)
+ {
+ return range(lowerEndpoint, BoundType.CLOSED, upperEndpoint, BoundType.CLOSED);
+ }
+
+ public static <C extends Comparable<?>> Range<C> range(final C lowerEndpoint, final BoundType lowerBoundType,
+ final C upperEndpoint, final BoundType upperBoundType)
+ {
+ return createRange(new Callable<Range<C>>() {
+ @Override
+ public Range<C> call() throws Exception {
+ return Range.range(lowerEndpoint, lowerBoundType, upperEndpoint, upperBoundType);
+ }
+ }, legacyRangeMethod, lowerEndpoint, lowerBoundType, upperEndpoint, upperBoundType);
+ }
+
+ public static <C extends Comparable<?>> Range<C> greaterThan(C lowerEndpoint)
+ {
+ return downTo(lowerEndpoint, BoundType.OPEN);
+ }
+
+ public static <C extends Comparable<?>> Range<C> atLeast(C lowerEndpoint)
+ {
+ return downTo(lowerEndpoint, BoundType.CLOSED);
+ }
+
+ public static <C extends Comparable<?>> Range<C> downTo(final C lowerEndpoint, final BoundType lowerBoundType)
+ {
+ return createRange(new Callable<Range<C>>() {
+ @Override
+ public Range<C> call() throws Exception {
+ return Range.downTo(lowerEndpoint, lowerBoundType);
+ }
+ }, legacyDownToMethod, lowerEndpoint, lowerBoundType);
+ }
+
+ public static <C extends Comparable<?>> Range<C> lessThan(C upperEndpoint)
+ {
+ return upTo(upperEndpoint, BoundType.OPEN);
+ }
+
+ public static <C extends Comparable<?>> Range<C> atMost(C upperEndpoint)
+ {
+ return upTo(upperEndpoint, BoundType.CLOSED);
+ }
+
+ public static <C extends Comparable<?>> Range<C> upTo(final C upperEndpoint, final BoundType upperBoundType)
+ {
+ return createRange(new Callable<Range<C>>() {
+ @Override
+ public Range<C> call() throws Exception {
+ return Range.upTo(upperEndpoint, upperBoundType);
+ }
+ }, legacyUpToMethod, upperEndpoint, upperBoundType);
+ }
+
+ public static <C extends Comparable<?>> Range<C> all()
+ {
+ return createRange(new Callable<Range<C>>() {
+ @Override
+ public Range<C> call() throws Exception {
+ return Range.all();
+ }
+ }, legacyAllMethod);
+ }
+
+ public static <C extends Comparable<?>> Range<C> singleton(final C value)
+ {
+ return createRange(new Callable<Range<C>>() {
+ @Override
+ public Range<C> call() throws Exception {
+ return Range.singleton(value);
+ }
+ }, legacyRangeMethod, value, BoundType.CLOSED, value, BoundType.CLOSED);
+ }
+
+ private static <C extends Comparable<?>> Range<C> createRange(Callable<Range<C>> rangeCallable, Method legacyRangeFactoryMethod, Object ... params)
+ {
+ try {
+ return rangeCallable.call();
+ } catch (NoSuchMethodError noSuchMethodError) {
+ if (legacyRangeFactoryMethod != null) {
+ return invokeLegacyRangeFactoryMethod(legacyRangeFactoryMethod, params);
+ } else {
+ throw noSuchMethodError;
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <C extends Comparable<?>> Range<C> invokeLegacyRangeFactoryMethod(Method method, Object... params)
+ {
+ try {
+ //noinspection unchecked
+ return (Range<C>) method.invoke(null, params);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to invoke legacy Range factory method [" + method.getName() +
+ "] with params " + Lists.newArrayList(params) + ".", e);
+ }
+ }
+
+ // prevent instantiation
+ private RangeFactory() { }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaBeanSerializerModifier.java b/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaBeanSerializerModifier.java
new file mode 100644
index 0000000..1083ff5
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaBeanSerializerModifier.java
@@ -0,0 +1,26 @@
+package com.fasterxml.jackson.datatype.guava.ser;
+
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.SerializationConfig;
+import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
+import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
+import com.google.common.base.Optional;
+
+import java.util.List;
+
+public class GuavaBeanSerializerModifier extends BeanSerializerModifier {
+
+ @Override
+ public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
+ BeanDescription beanDesc,
+ List<BeanPropertyWriter> beanProperties)
+ {
+ for (int i = 0; i < beanProperties.size(); ++i) {
+ final BeanPropertyWriter writer = beanProperties.get(i);
+ if (Optional.class.isAssignableFrom(writer.getPropertyType())) {
+ beanProperties.set(i, new GuavaOptionalBeanPropertyWriter(writer));
+ }
+ }
+ return beanProperties;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaOptionalBeanPropertyWriter.java b/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaOptionalBeanPropertyWriter.java
new file mode 100644
index 0000000..396a274
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaOptionalBeanPropertyWriter.java
@@ -0,0 +1,26 @@
+package com.fasterxml.jackson.datatype.guava.ser;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
+import com.google.common.base.Optional;
+
+public class GuavaOptionalBeanPropertyWriter extends BeanPropertyWriter {
+
+ protected GuavaOptionalBeanPropertyWriter(BeanPropertyWriter base) {
+ super(base);
+ }
+
+ @Override
+ public void serializeAsField(Object bean, JsonGenerator jgen, SerializerProvider prov) throws Exception
+ {
+ if (_nullSerializer == null) {
+ Object value = get(bean);
+ if (value == null || Optional.absent().equals(value)) {
+ return;
+ }
+ }
+ super.serializeAsField(bean, jgen, prov);
+ }
+
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaOptionalSerializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaOptionalSerializer.java
new file mode 100644
index 0000000..5fa2846
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaOptionalSerializer.java
@@ -0,0 +1,55 @@
+package com.fasterxml.jackson.datatype.guava.ser;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import com.google.common.base.Optional;
+
+public final class GuavaOptionalSerializer extends StdSerializer<Optional<?>>
+{
+ public GuavaOptionalSerializer(JavaType type) {
+ super(type);
+ }
+
+ // implemented since 2.3
+ @Override
+ public boolean isEmpty(Optional<?> value) {
+ return (value == null) || !value.isPresent();
+ }
+
+ @Override
+ public void serialize(Optional<?> value, JsonGenerator jgen, SerializerProvider provider)
+ throws IOException, JsonGenerationException {
+ if(value.isPresent()){
+ provider.defaultSerializeValue(value.get(), jgen);
+ } else{
+ provider.defaultSerializeNull(jgen);
+ }
+ }
+
+ @Override
+ public void serializeWithType(Optional<?> value,
+ JsonGenerator jgen,
+ SerializerProvider provider,
+ TypeSerializer typeSer) throws IOException, JsonProcessingException {
+ serialize(value, jgen, provider);
+ }
+
+ @Override
+ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException {
+ JavaType typeParameter = typeHint.containedType(0);
+ if (typeParameter != null) {
+ visitor.getProvider().findValueSerializer(typeParameter, null).acceptJsonFormatVisitor(visitor, typeParameter);
+ return;
+ }
+ super.acceptJsonFormatVisitor(visitor, typeHint);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/ser/MultimapSerializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/ser/MultimapSerializer.java
new file mode 100644
index 0000000..eef48cd
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/ser/MultimapSerializer.java
@@ -0,0 +1,177 @@
+package com.fasterxml.jackson.datatype.guava.ser;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map.Entry;
+
+import com.fasterxml.jackson.core.*;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import com.fasterxml.jackson.databind.ser.ContainerSerializer;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+import com.fasterxml.jackson.databind.type.MapLikeType;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+
+public class MultimapSerializer
+ extends ContainerSerializer<Multimap<?, ?>>
+ implements ContextualSerializer
+{
+ private final MapLikeType _type;
+ private final BeanProperty _property;
+ private final JsonSerializer<Object> _keySerializer;
+ private final TypeSerializer _valueTypeSerializer;
+ private final JsonSerializer<Object> _valueSerializer;
+
+ public MultimapSerializer(SerializationConfig config,
+ MapLikeType type,
+ BeanDescription beanDesc,
+ JsonSerializer<Object> keySerializer,
+ TypeSerializer valueTypeSerializer,
+ JsonSerializer<Object> valueSerializer)
+ {
+ super(type.getRawClass(), false);
+ _type = type;
+ _property = null;
+ _keySerializer = keySerializer;
+ _valueTypeSerializer = valueTypeSerializer;
+ _valueSerializer = valueSerializer;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected MultimapSerializer(MultimapSerializer src, BeanProperty property,
+ JsonSerializer<?> keySerializer,
+ TypeSerializer valueTypeSerializer, JsonSerializer<?> valueSerializer)
+ {
+ super(src);
+ _type = src._type;
+ _property = property;
+ _keySerializer = (JsonSerializer<Object>) keySerializer;
+ _valueTypeSerializer = valueTypeSerializer;
+ _valueSerializer = (JsonSerializer<Object>) valueSerializer;
+ }
+
+ protected MultimapSerializer withResolved(BeanProperty property,
+ JsonSerializer<?> keySer,
+ TypeSerializer vts, JsonSerializer<?> valueSer)
+ {
+ return new MultimapSerializer(this, property, keySer, vts, valueSer);
+ }
+
+ @Override
+ protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer typeSer) {
+ return new MultimapSerializer(this, _property, _keySerializer,
+ typeSer, _valueSerializer);
+ }
+
+ /*
+ /**********************************************************
+ /* Post-processing (contextualization)
+ /**********************************************************
+ */
+
+ @Override
+ public JsonSerializer<?> createContextual(SerializerProvider provider,
+ BeanProperty property) throws JsonMappingException
+ {
+ JsonSerializer<?> valueSer = _valueSerializer;
+ if (valueSer == null) { // if type is final, can actually resolve:
+ JavaType valueType = _type.getContentType();
+ if (valueType.isFinal()) {
+ valueSer = provider.findValueSerializer(valueType, property);
+ }
+ } else if (valueSer instanceof ContextualSerializer) {
+ valueSer = ((ContextualSerializer) valueSer).createContextual(provider, property);
+ }
+ JsonSerializer<?> keySer = _keySerializer;
+ if (keySer == null) {
+ keySer = provider.findKeySerializer(_type.getKeyType(), property);
+ } else if (keySer instanceof ContextualSerializer) {
+ keySer = ((ContextualSerializer) keySer).createContextual(provider, property);
+ }
+ // finally, TypeSerializers may need contextualization as well
+ TypeSerializer typeSer = _valueTypeSerializer;
+ if (typeSer != null) {
+ typeSer = typeSer.forProperty(property);
+ }
+ return withResolved(property, keySer, typeSer, valueSer);
+ }
+
+ /*
+ /**********************************************************
+ /* Accessors for ContainerSerializer
+ /**********************************************************
+ */
+
+ @Override
+ public JsonSerializer<?> getContentSerializer() {
+ return _valueSerializer;
+ }
+
+ @Override
+ public JavaType getContentType() {
+ return _type.getContentType();
+ }
+
+ @Override
+ public boolean hasSingleElement(Multimap<?,?> map) {
+ return map.size() == 1;
+ }
+
+ @Override
+ public boolean isEmpty(Multimap<?,?> map) {
+ return map.isEmpty();
+ }
+
+ /*
+ /**********************************************************
+ /* Post-processing (contextualization)
+ /**********************************************************
+ */
+
+ @Override
+ public void serialize(Multimap<?, ?> value, JsonGenerator jgen, SerializerProvider provider)
+ throws IOException, JsonProcessingException
+ {
+ jgen.writeStartObject();
+ if (!value.isEmpty()) {
+ serializeFields(value, jgen, provider);
+ }
+ jgen.writeEndObject();
+ }
+
+ @Override
+ public void serializeWithType(Multimap<?,?> value, JsonGenerator jgen, SerializerProvider provider,
+ TypeSerializer typeSer)
+ throws IOException, JsonGenerationException
+ {
+ typeSer.writeTypePrefixForObject(value, jgen);
+ serializeFields(value, jgen, provider);
+ typeSer.writeTypeSuffixForObject(value, jgen);
+ }
+
+ private final void serializeFields(Multimap<?, ?> value, JsonGenerator jgen, SerializerProvider provider)
+ throws IOException, JsonProcessingException
+ {
+ for (Entry<?, ? extends Collection<?>> e : value.asMap().entrySet()) {
+ if (_keySerializer != null) {
+ _keySerializer.serialize(e.getKey(), jgen, provider);
+ } else {
+ provider.findKeySerializer(provider.constructType(String.class), _property)
+ .serialize(e.getKey(), jgen, provider);
+ }
+ if (_valueSerializer != null) {
+ // note: value is a List, but generic type is for contents... so:
+ jgen.writeStartArray();
+ for (Object vv : e.getValue()) {
+ _valueSerializer.serialize(vv, jgen, provider);
+ }
+ jgen.writeEndArray();
+ } else {
+ provider.defaultSerializeValue(Lists.newArrayList(e.getValue()), jgen);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/ser/RangeSerializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/ser/RangeSerializer.java
new file mode 100644
index 0000000..097ea3f
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/datatype/guava/ser/RangeSerializer.java
@@ -0,0 +1,112 @@
+package com.fasterxml.jackson.datatype.guava.ser;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+import com.google.common.collect.Range;
+
+/**
+ * Jackson serializer for a Guava {@link Range}.
+ */
+public class RangeSerializer extends StdSerializer<Range<?>>
+ implements ContextualSerializer
+{
+ protected final JavaType _rangeType;
+
+ protected final JsonSerializer<Object> _endpointSerializer;
+
+ /*
+ /**********************************************************
+ /* Life-cycle
+ /**********************************************************
+ */
+
+ public RangeSerializer(JavaType type) { this(type, null); }
+
+ @SuppressWarnings("unchecked")
+ public RangeSerializer(JavaType type, JsonSerializer<?> endpointSer)
+ {
+ super(type);
+ _rangeType = type;
+ _endpointSerializer = (JsonSerializer<Object>) endpointSer;
+ }
+
+ // TODO: can this be implemented with better semantics? Base class only
+ // checks for null
+ @Override
+ public boolean isEmpty(Range<?> value) {
+ return super.isEmpty(value);
+ }
+
+ @Override
+ public JsonSerializer<?> createContextual(SerializerProvider prov,
+ BeanProperty property) throws JsonMappingException
+ {
+ if (_endpointSerializer == null) {
+ JavaType endpointType = _rangeType.containedType(0);
+ // let's not consider "untyped" (java.lang.Object) to be meaningful here...
+ if (endpointType != null && !endpointType.hasRawClass(Object.class)) {
+ JsonSerializer<?> ser = prov.findValueSerializer(endpointType, property);
+ return new RangeSerializer(_rangeType, ser);
+ }
+ }
+ return this;
+ }
+
+ /*
+ /**********************************************************
+ /* Serialization methods
+ /**********************************************************
+ */
+
+ @Override
+ public void serialize(Range<?> value, JsonGenerator jgen, SerializerProvider provider)
+ throws IOException, JsonGenerationException
+ {
+ jgen.writeStartObject();
+ _writeContents(value, jgen, provider);
+ jgen.writeEndObject();
+
+ }
+
+ @Override
+ public void serializeWithType(Range<?> value, JsonGenerator jgen, SerializerProvider provider,
+ TypeSerializer typeSer)
+ throws IOException, JsonProcessingException
+ {
+ // Will be serialized as a JSON Object, so:
+ typeSer.writeTypePrefixForObject(value, jgen);
+ _writeContents(value, jgen, provider);
+ typeSer.writeTypeSuffixForObject(value, jgen);
+ }
+
+ private void _writeContents(Range<?> value, JsonGenerator jgen, SerializerProvider provider)
+ throws IOException
+ {
+ if (value.hasLowerBound()) {
+ if (_endpointSerializer != null) {
+ jgen.writeFieldName("lowerEndpoint");
+ _endpointSerializer.serialize(value.lowerEndpoint(), jgen, provider);
+ } else {
+ provider.defaultSerializeField("lowerEndpoint", value.lowerEndpoint(), jgen);
+ }
+ provider.defaultSerializeField("lowerBoundType", value.lowerBoundType(), jgen);
+ }
+ if (value.hasUpperBound()) {
+ if (_endpointSerializer != null) {
+ jgen.writeFieldName("upperEndpoint");
+ _endpointSerializer.serialize(value.upperEndpoint(), jgen, provider);
+ } else {
+ provider.defaultSerializeField("upperEndpoint", value.upperEndpoint(), jgen);
+ }
+ provider.defaultSerializeField("upperBoundType", value.upperBoundType(), jgen);
+ }
+ }
+}
diff --git a/src/main/resources/META-INF/LICENSE b/src/main/resources/META-INF/LICENSE
new file mode 100644
index 0000000..f5f45d2
--- /dev/null
+++ b/src/main/resources/META-INF/LICENSE
@@ -0,0 +1,8 @@
+This copy of Jackson JSON processor streaming parser/generator is licensed under the
+Apache (Software) License, version 2.0 ("the License").
+See the License for details about distribution rights, and the
+specific rights regarding derivate works.
+
+You may obtain a copy of the License at:
+
+http://www.apache.org/licenses/LICENSE-2.0
diff --git a/src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module b/src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module
new file mode 100644
index 0000000..047e1f8
--- /dev/null
+++ b/src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module
@@ -0,0 +1 @@
+com.fasterxml.jackson.datatype.guava.GuavaModule
diff --git a/src/test/java/com/fasterxml/jackson/datatype/guava/FluentIterableTest.java b/src/test/java/com/fasterxml/jackson/datatype/guava/FluentIterableTest.java
new file mode 100644
index 0000000..493a424
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/datatype/guava/FluentIterableTest.java
@@ -0,0 +1,45 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import java.util.Iterator;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Sets;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Unit tests to verify serialization of {@link FluentIterable}s.
+ */
+public class FluentIterableTest extends ModuleTestBase
+{
+ private final ObjectMapper MAPPER = mapperWithModule();
+
+ FluentIterable<Integer> createFluentIterable() {
+ return new FluentIterable<Integer>() {
+ private final Iterable<Integer> _iterable = Sets.newHashSet(1, 2, 3);
+ @Override
+ public Iterator<Integer> iterator() {
+ return _iterable.iterator();
+ }
+ };
+ }
+
+ /**
+ * This test is present so that we know if either Jackson's handling of FluentIterable
+ * or Guava's implementation of FluentIterable changes.
+ * @throws Exception
+ */
+ public void testSerializationWithoutModule() throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ Iterable<Integer> fluentIterable = createFluentIterable();
+ String json = mapper.writeValueAsString(fluentIterable);
+ assertEquals("{\"empty\":false}", json);
+ }
+
+ public void testSerialization() throws Exception {
+ Iterable<Integer> fluentIterable = createFluentIterable();
+ String json = MAPPER.writeValueAsString(fluentIterable);
+ assertEquals("[1,2,3]", json);
+ }
+
+}
diff --git a/src/test/java/com/fasterxml/jackson/datatype/guava/HostAndPortTest.java b/src/test/java/com/fasterxml/jackson/datatype/guava/HostAndPortTest.java
new file mode 100644
index 0000000..d562363
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/datatype/guava/HostAndPortTest.java
@@ -0,0 +1,41 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.net.HostAndPort;
+
+public class HostAndPortTest extends ModuleTestBase
+{
+ private final ObjectMapper MAPPER = mapperWithModule();
+
+ public void testSerialization() throws Exception
+ {
+ HostAndPort input = HostAndPort.fromParts("localhost", 80);
+ String json = MAPPER.writeValueAsString(input);
+ assertEquals("\"localhost:80\"", json);
+ }
+
+ public void testDeserialization() throws Exception
+ {
+ // Actually, let's support both old style and new style
+
+ // old:
+ HostAndPort result = MAPPER.readValue(aposToQuotes("{'hostText':'localhost','port':9090}"),
+ HostAndPort.class);
+ assertEquals("localhost", result.getHostText());
+ assertEquals(9090, result.getPort());
+
+ // and new:
+ result = MAPPER.readValue(quote("localhost:7070"), HostAndPort.class);
+ assertEquals("localhost", result.getHostText());
+ assertEquals(7070, result.getPort());
+
+ // and ... error
+ try {
+ MAPPER.readValue("false", HostAndPort.class);
+ fail("Should not deserialize from boolean");
+ } catch (JsonProcessingException e) {
+ verifyException(e, "Unexpected token");
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/datatype/guava/IterablesTest.java b/src/test/java/com/fasterxml/jackson/datatype/guava/IterablesTest.java
new file mode 100644
index 0000000..d51ce87
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/datatype/guava/IterablesTest.java
@@ -0,0 +1,16 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.Iterables;
+
+public class IterablesTest extends ModuleTestBase
+{
+ private final ObjectMapper MAPPER = mapperWithModule();
+
+ public void testIterablesSerialization() throws Exception
+ {
+ String json = MAPPER.writeValueAsString(Iterables.limit(Iterables.cycle(1,2,3), 3));
+ assertNotNull(json);
+ assertEquals("[1,2,3]", json);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/datatype/guava/ModuleTestBase.java b/src/test/java/com/fasterxml/jackson/datatype/guava/ModuleTestBase.java
new file mode 100644
index 0000000..b9179c1
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/datatype/guava/ModuleTestBase.java
@@ -0,0 +1,40 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import java.util.Arrays;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import com.fasterxml.jackson.datatype.guava.GuavaModule;
+
+public abstract class ModuleTestBase extends junit.framework.TestCase
+{
+ protected ModuleTestBase() { }
+
+ protected ObjectMapper mapperWithModule()
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.registerModule(new GuavaModule());
+ return mapper;
+ }
+
+ protected String aposToQuotes(String json) {
+ return json.replace("'", "\"");
+ }
+
+ public String quote(String str) {
+ return '"'+str+'"';
+ }
+
+ protected void verifyException(Throwable e, String... matches)
+ {
+ String msg = e.getMessage();
+ String lmsg = (msg == null) ? "" : msg.toLowerCase();
+ for (String match : matches) {
+ String lmatch = match.toLowerCase();
+ if (lmsg.indexOf(lmatch) >= 0) {
+ return;
+ }
+ }
+ fail("Expected an exception with one of substrings ("+Arrays.asList(matches)+"): got one with message \""+msg+"\"");
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/datatype/guava/TestImmutables.java b/src/test/java/com/fasterxml/jackson/datatype/guava/TestImmutables.java
new file mode 100644
index 0000000..857ca55
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/datatype/guava/TestImmutables.java
@@ -0,0 +1,240 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import java.util.Iterator;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.*;
+
+/**
+ * Unit tests for verifying that various immutable types
+ * (like {@link ImmutableList}, {@link ImmutableMap} and {@link ImmutableSet})
+ * work as expected.
+ *
+ * @author tsaloranta
+ */
+public class TestImmutables extends ModuleTestBase
+{
+ private final ObjectMapper MAPPER = mapperWithModule();
+
+ static class Holder {
+ @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
+ public Object value;
+
+ public Holder() { }
+ public Holder(Object v) {
+ value = v;
+ }
+ }
+
+ /*
+ /**********************************************************************
+ /* Unit tests for verifying handling in absence of module registration
+ /**********************************************************************
+ */
+
+ /**
+ * Immutable types can actually be serialized as regular collections, without
+ * problems.
+ */
+ public void testWithoutSerializers() throws Exception
+ {
+ ImmutableList<Integer> list = ImmutableList.<Integer>builder()
+ .add(1).add(2).add(3).build();
+ assertEquals("[1,2,3]", MAPPER.writeValueAsString(list));
+
+ ImmutableSet<String> set = ImmutableSet.<String>builder()
+ .add("abc").add("def").build();
+ assertEquals("[\"abc\",\"def\"]", MAPPER.writeValueAsString(set));
+
+ ImmutableMap<String,Integer> map = ImmutableMap.<String,Integer>builder()
+ .put("a", 1).put("b", 2).build();
+ assertEquals("{\"a\":1,\"b\":2}", MAPPER.writeValueAsString(map));
+ }
+
+ /**
+ * Deserialization will fail, however.
+ */
+ public void testWithoutDeserializers() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ mapper.readValue("[1,2,3]",
+ new TypeReference<ImmutableList<Integer>>() { });
+ fail("Expected failure for missing deserializer");
+ } catch (JsonMappingException e) {
+ verifyException(e, "can not find a deserializer");
+ }
+
+ try {
+ mapper.readValue("[1,2,3]", new TypeReference<ImmutableSet<Integer>>() { });
+ fail("Expected failure for missing deserializer");
+ } catch (JsonMappingException e) {
+ verifyException(e, "can not find a deserializer");
+ }
+
+ try {
+ mapper.readValue("[1,2,3]", new TypeReference<ImmutableSortedSet<Integer>>() { });
+ fail("Expected failure for missing deserializer");
+ } catch (JsonMappingException e) {
+ verifyException(e, "can not find a deserializer");
+ }
+
+ try {
+ mapper.readValue("{\"a\":true,\"b\":false}", new TypeReference<ImmutableMap<Integer,Boolean>>() { });
+ fail("Expected failure for missing deserializer");
+ } catch (JsonMappingException e) {
+ verifyException(e, "can not find a deserializer");
+ }
+ }
+
+ /*
+ /**********************************************************************
+ /* Unit tests for actual registered module
+ /**********************************************************************
+ */
+
+ public void testImmutableList() throws Exception
+ {
+ ImmutableList<Integer> list = MAPPER.readValue("[1,2,3]", new TypeReference<ImmutableList<Integer>>() { });
+ assertEquals(3, list.size());
+ assertEquals(Integer.valueOf(1), list.get(0));
+ assertEquals(Integer.valueOf(2), list.get(1));
+ assertEquals(Integer.valueOf(3), list.get(2));
+ }
+
+ public void testImmutableSet() throws Exception
+ {
+ ImmutableSet<Integer> set = MAPPER.readValue("[3,7,8]",
+ new TypeReference<ImmutableSet<Integer>>() { });
+ assertEquals(3, set.size());
+ Iterator<Integer> it = set.iterator();
+ assertEquals(Integer.valueOf(3), it.next());
+ assertEquals(Integer.valueOf(7), it.next());
+ assertEquals(Integer.valueOf(8), it.next());
+
+ set = MAPPER.readValue("[ ]",
+ new TypeReference<ImmutableSet<Integer>>() { });
+ assertEquals(0, set.size());
+ }
+
+ public void testImmutableSetFromSingle() throws Exception
+ {
+ ObjectMapper mapper = mapperWithModule()
+ .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
+ ImmutableSet<String> set = mapper.readValue("\"abc\"",
+ new TypeReference<ImmutableSet<String>>() { });
+ assertEquals(1, set.size());
+ assertTrue(set.contains("abc"));
+ }
+
+ public void testTypedImmutableset() throws Exception
+ {
+ ImmutableSet<Integer> set;
+ Holder h;
+ String json;
+ Holder result;
+
+ // First, with one entry
+ set = new ImmutableSet.Builder<Integer>()
+ .add(1).build();
+ h = new Holder(set);
+ json = MAPPER.writeValueAsString(h);
+
+ // so far so good. and back?
+ result = MAPPER.readValue(json, Holder.class);
+ assertNotNull(result.value);
+ if (!(result.value instanceof ImmutableSet<?>)) {
+ fail("Expected ImmutableSet, got "+result.value.getClass());
+ }
+ assertEquals(1, ((ImmutableSet<?>) result.value).size());
+ // and then an empty version:
+ set = new ImmutableSet.Builder<Integer>().build();
+ h = new Holder(set);
+ json = MAPPER.writeValueAsString(h);
+ result = MAPPER.readValue(json, Holder.class);
+ assertNotNull(result.value);
+ if (!(result.value instanceof ImmutableSet<?>)) {
+ fail("Expected ImmutableSet, got "+result.value.getClass());
+ }
+ assertEquals(0, ((ImmutableSet<?>) result.value).size());
+ }
+
+ public void testImmutableSortedSet() throws Exception
+ {
+ ImmutableSortedSet<Integer> set = MAPPER.readValue("[5,1,2]", new TypeReference<ImmutableSortedSet<Integer>>() { });
+ assertEquals(3, set.size());
+ Iterator<Integer> it = set.iterator();
+ assertEquals(Integer.valueOf(1), it.next());
+ assertEquals(Integer.valueOf(2), it.next());
+ assertEquals(Integer.valueOf(5), it.next());
+ }
+
+ public void testImmutableMap() throws Exception
+ {
+ final JavaType type = MAPPER.getTypeFactory().constructType(new TypeReference<ImmutableMap<Integer,Boolean>>() { });
+ ImmutableMap<Integer,Boolean> map = MAPPER.readValue("{\"12\":true,\"4\":false}", type);
+ assertEquals(2, map.size());
+ assertEquals(Boolean.TRUE, map.get(Integer.valueOf(12)));
+ assertEquals(Boolean.FALSE, map.get(Integer.valueOf(4)));
+
+ map = MAPPER.readValue("{}", type);
+ assertNotNull(map);
+ assertEquals(0, map.size());
+ }
+
+ public void testTypedImmutableMap() throws Exception
+ {
+ ImmutableMap<String,Integer> map;
+ Holder h;
+ String json;
+ Holder result;
+
+ // First, with one entry
+ map = new ImmutableMap.Builder<String,Integer>()
+ .put("a", 1).build();
+ h = new Holder(map);
+ json = MAPPER.writeValueAsString(h);
+
+ // so far so good. and back?
+ result = MAPPER.readValue(json, Holder.class);
+ assertNotNull(result.value);
+ if (!(result.value instanceof ImmutableMap<?,?>)) {
+ fail("Expected ImmutableMap, got "+result.value.getClass());
+ }
+ assertEquals(1, ((ImmutableMap<?,?>) result.value).size());
+ // and then an empty version:
+ map = new ImmutableMap.Builder<String,Integer>().build();
+ h = new Holder(map);
+ json = MAPPER.writeValueAsString(h);
+ result = MAPPER.readValue(json, Holder.class);
+ assertNotNull(result.value);
+ if (!(result.value instanceof ImmutableMap<?,?>)) {
+ fail("Expected ImmutableMap, got "+result.value.getClass());
+ }
+ assertEquals(0, ((ImmutableMap<?,?>) result.value).size());
+ }
+
+ public void testImmutableSortedMap() throws Exception
+ {
+ ImmutableSortedMap<Integer,Boolean> map = MAPPER.readValue("{\"12\":true,\"4\":false}", new TypeReference<ImmutableSortedMap<Integer,Boolean>>() { });
+ assertEquals(2, map.size());
+ assertEquals(Boolean.TRUE, map.get(Integer.valueOf(12)));
+ assertEquals(Boolean.FALSE, map.get(Integer.valueOf(4)));
+ }
+
+ public void testImmutableBiMap() throws Exception
+ {
+ ImmutableBiMap<Integer,Boolean> map = MAPPER.readValue("{\"12\":true,\"4\":false}", new TypeReference<ImmutableBiMap<Integer,Boolean>>() { });
+ assertEquals(2, map.size());
+ assertEquals(Boolean.TRUE, map.get(12));
+ assertEquals(Boolean.FALSE, map.get(4));
+ assertEquals(map.get(12), Boolean.TRUE);
+ assertEquals(map.get(4), Boolean.FALSE);
+ }
+
+}
diff --git a/src/test/java/com/fasterxml/jackson/datatype/guava/TestMultimaps.java b/src/test/java/com/fasterxml/jackson/datatype/guava/TestMultimaps.java
new file mode 100644
index 0000000..b6b9bc1
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/datatype/guava/TestMultimaps.java
@@ -0,0 +1,205 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import com.google.common.collect.*;
+
+import java.io.IOException;
+import java.util.Map;
+
+import static com.google.common.collect.TreeMultimap.create;
+
+/**
+ * Unit tests to verify handling of various {@link Multimap}s.
+ *
+ * @author steven at nesscomputing.com
+ */
+public class TestMultimaps extends ModuleTestBase
+{
+ // Test for issue #13 on github, provided by stevenschlansker
+ public static enum MyEnum {
+ YAY,
+ BOO
+ }
+
+ // [Issue#41]
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ static class MultiMapWrapper {
+ @JsonProperty
+ Multimap<String, String> map = ArrayListMultimap.create();
+ }
+
+ private static final String StringStringMultimap =
+ "{\"first\":[\"abc\",\"abc\",\"foo\"]," + "\"second\":[\"bar\"]}";
+
+ private final ObjectMapper MAPPER = mapperWithModule();
+
+ public void testMultimap() throws Exception
+ {
+ _testMultimap(TreeMultimap.create(), true,
+ "{\"false\":[false],\"maybe\":[false,true],\"true\":[true]}");
+ _testMultimap(LinkedListMultimap.create(), false,
+ "{\"true\":[true],\"false\":[false],\"maybe\":[true,false]}");
+ _testMultimap(LinkedHashMultimap.create(), false, null);
+ }
+
+ private void _testMultimap(Multimap<?,?> map0, boolean fullyOrdered, String EXPECTED) throws Exception
+ {
+ @SuppressWarnings("unchecked")
+ Multimap<String,Boolean> map = (Multimap<String,Boolean>) map0;
+ map.put("true", Boolean.TRUE);
+ map.put("false", Boolean.FALSE);
+ map.put("maybe", Boolean.TRUE);
+ map.put("maybe", Boolean.FALSE);
+
+ // Test that typed writes work
+ if (EXPECTED != null) {
+ String json = MAPPER.writerWithType(new TypeReference<Multimap<String, Boolean>>() {}).writeValueAsString(map);
+ assertEquals(EXPECTED, json);
+ }
+
+ // And untyped too
+ String serializedForm = MAPPER.writeValueAsString(map);
+
+ if (EXPECTED != null) {
+ assertEquals(EXPECTED, serializedForm);
+ }
+
+ // these seem to be order-sensitive as well, so only use for ordered-maps
+ if (fullyOrdered) {
+ assertEquals(map, MAPPER.<Multimap<String, Boolean>>readValue(serializedForm, new TypeReference<TreeMultimap<String, Boolean>>() {}));
+ assertEquals(map, create(MAPPER.<Multimap<String, Boolean>>readValue(serializedForm, new TypeReference<Multimap<String, Boolean>>() {})));
+ assertEquals(map, create(MAPPER.<Multimap<String, Boolean>>readValue(serializedForm, new TypeReference<HashMultimap<String, Boolean>>() {})));
+ assertEquals(map, create(MAPPER.<Multimap<String, Boolean>>readValue(serializedForm, new TypeReference<ImmutableMultimap<String, Boolean>>() {})));
+ }
+ }
+
+ public void testMultimapIssue3() throws Exception
+ {
+ Multimap<String, String> m1 = TreeMultimap.create();
+ m1.put("foo", "bar");
+ m1.put("foo", "baz");
+ m1.put("qux", "quux");
+ ObjectMapper o = MAPPER;
+
+ String t1 = o.writerWithType(new TypeReference<TreeMultimap<String, String>>(){}).writeValueAsString(m1);
+ Map<?,?> javaMap = o.readValue(t1, Map.class);
+ assertEquals(2, javaMap.size());
+
+ String t2 = o.writerWithType(new TypeReference<Multimap<String, String>>(){}).writeValueAsString(m1);
+ javaMap = o.readValue(t2, Map.class);
+ assertEquals(2, javaMap.size());
+
+ TreeMultimap<String, String> m2 = TreeMultimap.create();
+ m2.put("foo", "bar");
+ m2.put("foo", "baz");
+ m2.put("qux", "quux");
+
+ String t3 = o.writerWithType(new TypeReference<TreeMultimap<String, String>>(){}).writeValueAsString(m2);
+ javaMap = o.readValue(t3, Map.class);
+ assertEquals(2, javaMap.size());
+
+ String t4 = o.writerWithType(new TypeReference<Multimap<String, String>>(){}).writeValueAsString(m2);
+ javaMap = o.readValue(t4, Map.class);
+ assertEquals(2, javaMap.size());
+ }
+
+ public void testEnumKey() throws Exception
+ {
+ final TypeReference<TreeMultimap<MyEnum, Integer>> type = new TypeReference<TreeMultimap<MyEnum, Integer>>() {};
+ final Multimap<MyEnum, Integer> map = TreeMultimap.create();
+
+ map.put(MyEnum.YAY, 5);
+ map.put(MyEnum.BOO, 2);
+
+ final String serializedForm = MAPPER.writerWithType(type).writeValueAsString(map);
+
+ assertEquals(serializedForm, MAPPER.writeValueAsString(map));
+ assertEquals(map, MAPPER.readValue(serializedForm, type));
+ }
+
+ // [Issue#41]
+ public void testEmptyMapExclusion() throws Exception
+ {
+ String json = MAPPER.writeValueAsString(new MultiMapWrapper());
+ assertEquals("{}", json);
+ }
+
+ /*
+ /**********************************************************************
+ /* Unit tests for set-based multimaps
+ /**********************************************************************
+ */
+ public void testTreeMultimap() {
+
+ }
+
+ public void testForwardingSortedSetMultimap() {
+
+ }
+
+ public void testImmutableSetMultimap() {
+ // TODO look at others
+ }
+
+ public void testHashMultimap() throws IOException {
+ SetMultimap<String, String> map =
+ setBasedHelper(new TypeReference<HashMultimap<String, String>>() {
+ });
+ assertTrue(map instanceof HashMultimap);
+ }
+
+ public void testLinkedHashMultimap() throws IOException {
+ SetMultimap<String, String> map =
+ setBasedHelper(new TypeReference<LinkedHashMultimap<String, String>>() {
+ });
+ assertTrue(map instanceof LinkedHashMultimap);
+ }
+
+ public void testForwardingSetMultimap() {
+
+ }
+
+ private SetMultimap<String, String> setBasedHelper(TypeReference<?> type)
+ throws IOException
+ {
+ SetMultimap<String, String> map = MAPPER.readValue(StringStringMultimap, type);
+ assertEquals(3, map.size());
+ assertTrue(map.containsEntry("first", "abc"));
+ assertTrue(map.containsEntry("first", "foo"));
+ assertTrue(map.containsEntry("second", "bar"));
+ return map;
+ }
+
+ /*
+ /**********************************************************************
+ /* Unit tests for list-based multimaps
+ /**********************************************************************
+ */
+
+ public void testArrayListMultimap() throws IOException {
+ ListMultimap<String, String> map =
+ listBasedHelper(new TypeReference<ArrayListMultimap<String, String>>() {
+ });
+ assertTrue(map instanceof ArrayListMultimap);
+ }
+
+ public void testLinkedListMultimap() throws IOException {
+ ListMultimap<String, String> map =
+ listBasedHelper(new TypeReference<LinkedListMultimap<String, String>>() {
+ });
+ assertTrue(map instanceof LinkedListMultimap);
+ }
+
+ private ListMultimap<String, String> listBasedHelper(TypeReference<?> type) throws IOException {
+ ListMultimap<String, String> map = MAPPER.readValue(StringStringMultimap, type);
+ assertEquals(4, map.size());
+ assertTrue(map.remove("first", "abc"));
+ assertTrue(map.containsEntry("first", "abc"));
+ assertTrue(map.containsEntry("first", "foo"));
+ assertTrue(map.containsEntry("second", "bar"));
+ return map;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/fasterxml/jackson/datatype/guava/TestMultisets.java b/src/test/java/com/fasterxml/jackson/datatype/guava/TestMultisets.java
new file mode 100644
index 0000000..07c23c5
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/datatype/guava/TestMultisets.java
@@ -0,0 +1,110 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import com.fasterxml.jackson.databind.*;
+
+import com.google.common.collect.*;
+
+/**
+ * Unit tests to verify handling of various {@link Multiset}s.
+ *
+ * @author tsaloranta
+ */
+public class TestMultisets extends ModuleTestBase
+{
+
+ /*
+ /**********************************************************************
+ /* Unit tests for verifying handling in absence of module registration
+ /**********************************************************************
+ */
+
+ /**
+ * Multi-sets can actually be serialized as regular collections, without
+ * problems.
+ */
+ public void testWithoutSerializers() throws Exception
+ {
+
+ ObjectMapper mapper = new ObjectMapper();
+ Multiset<String> set = LinkedHashMultiset.create();
+ // hash-based multi-sets actually keeps 'same' instances together, otherwise insertion-ordered:
+ set.add("abc");
+ set.add("foo");
+ set.add("abc");
+ String json = mapper.writeValueAsString(set);
+ assertEquals("[\"abc\",\"abc\",\"foo\"]", json);
+ }
+
+ public void testWithoutDeserializers() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ /*Multiset<String> set =*/ mapper.readValue("[\"abc\",\"abc\",\"foo\"]",
+ new TypeReference<Multiset<String>>() { });
+ fail("Should have failed");
+ } catch (JsonMappingException e) {
+ verifyException(e, "can not find a deserializer");
+ }
+ }
+
+ /*
+ /**********************************************************************
+ /* Unit tests for actual registered module
+ /**********************************************************************
+ */
+
+ final ObjectMapper MAPPER = mapperWithModule();
+
+ public void testDefault() throws Exception
+ {
+ Multiset<String> set = MAPPER.readValue("[\"abc\",\"abc\",\"foo\"]", new TypeReference<Multiset<String>>() { });
+ assertEquals(3, set.size());
+ assertEquals(1, set.count("foo"));
+ assertEquals(2, set.count("abc"));
+ assertEquals(0, set.count("bar"));
+ }
+
+ public void testLinkedHashMultiset() throws Exception {
+ LinkedHashMultiset<String> set = MAPPER.readValue("[\"abc\",\"abc\",\"foo\"]", new TypeReference<LinkedHashMultiset<String>>() { });
+ assertEquals(3, set.size());
+ assertEquals(1, set.count("foo"));
+ assertEquals(2, set.count("abc"));
+ assertEquals(0, set.count("bar"));
+ }
+
+ public void testHashMultiset() throws Exception {
+ HashMultiset<String> set = MAPPER.readValue("[\"abc\",\"abc\",\"foo\"]", new TypeReference<HashMultiset<String>>() { });
+ assertEquals(3, set.size());
+ assertEquals(1, set.count("foo"));
+ assertEquals(2, set.count("abc"));
+ assertEquals(0, set.count("bar"));
+ }
+
+ public void testTreeMultiset() throws Exception {
+ TreeMultiset<String> set = MAPPER.readValue("[\"abc\",\"abc\",\"foo\"]", new TypeReference<TreeMultiset<String>>() { });
+ assertEquals(3, set.size());
+ assertEquals(1, set.count("foo"));
+ assertEquals(2, set.count("abc"));
+ assertEquals(0, set.count("bar"));
+ }
+
+ public void testImmutableMultiset() throws Exception {
+ ImmutableMultiset<String> set = MAPPER.readValue("[\"abc\",\"abc\",\"foo\"]", new TypeReference<ImmutableMultiset<String>>() { });
+ assertEquals(3, set.size());
+ assertEquals(1, set.count("foo"));
+ assertEquals(2, set.count("abc"));
+ assertEquals(0, set.count("bar"));
+ }
+
+ public void testFromSingle() throws Exception
+ {
+ ObjectMapper mapper = mapperWithModule()
+ .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
+ Multiset<String> set = mapper.readValue("\"abc\"",
+ new TypeReference<Multiset<String>>() { });
+ assertEquals(1, set.size());
+ assertTrue(set.contains("abc"));
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/datatype/guava/TestOptional.java b/src/test/java/com/fasterxml/jackson/datatype/guava/TestOptional.java
new file mode 100644
index 0000000..fab74d0
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/datatype/guava/TestOptional.java
@@ -0,0 +1,176 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import com.google.common.base.Optional;
+
+public class TestOptional extends ModuleTestBase
+{
+ private final ObjectMapper MAPPER = mapperWithModule();
+
+ @JsonAutoDetect(fieldVisibility=Visibility.ANY)
+ public static final class OptionalData{
+ private Optional<String> myString;
+ }
+
+ @JsonAutoDetect(fieldVisibility=Visibility.ANY)
+ public static final class OptionalGenericData<T>{
+ private Optional<T> myData;
+ }
+
+ @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class)
+ public static class Unit
+ {
+// @JsonIdentityReference(alwaysAsId=true)
+ public Optional<Unit> baseUnit;
+
+ public Unit() { }
+ public Unit(Optional<Unit> u) { baseUnit = u; }
+
+ public void link(Unit u) {
+ baseUnit = Optional.of(u);
+ }
+ }
+
+ /*
+ /**********************************************************************
+ /* Test methods
+ /**********************************************************************
+ */
+
+ public void testDeserAbsent() throws Exception {
+ Optional<?> value = MAPPER.readValue("null", new TypeReference<Optional<String>>() {});
+ assertFalse(value.isPresent());
+ }
+
+ public void testDeserSimpleString() throws Exception{
+ Optional<?> value = MAPPER.readValue("\"simpleString\"", new TypeReference<Optional<String>>() {});
+ assertTrue(value.isPresent());
+ assertEquals("simpleString", value.get());
+ }
+
+ public void testDeserInsideObject() throws Exception {
+ OptionalData data = MAPPER.readValue("{\"myString\":\"simpleString\"}", OptionalData.class);
+ assertTrue(data.myString.isPresent());
+ assertEquals("simpleString", data.myString.get());
+ }
+
+ public void testDeserComplexObject() throws Exception {
+ TypeReference<Optional<OptionalData>> type = new TypeReference<Optional<OptionalData>>() {};
+ Optional<OptionalData> data = MAPPER.readValue("{\"myString\":\"simpleString\"}", type);
+ assertTrue(data.isPresent());
+ assertTrue(data.get().myString.isPresent());
+ assertEquals("simpleString", data.get().myString.get());
+ }
+
+ public void testDeserGeneric() throws Exception {
+ TypeReference<Optional<OptionalGenericData<String>>> type = new TypeReference<Optional<OptionalGenericData<String>>>() {};
+ Optional<OptionalGenericData<String>> data = MAPPER.readValue("{\"myData\":\"simpleString\"}", type);
+ assertTrue(data.isPresent());
+ assertTrue(data.get().myData.isPresent());
+ assertEquals("simpleString", data.get().myData.get());
+ }
+
+ public void testSerAbsent() throws Exception {
+ String value = MAPPER.writeValueAsString(Optional.absent());
+ assertEquals("null", value);
+ }
+
+ public void testSerSimpleString() throws Exception {
+ String value = MAPPER.writeValueAsString(Optional.of("simpleString"));
+ assertEquals("\"simpleString\"", value);
+ }
+
+ public void testSerInsideObject() throws Exception {
+ OptionalData data = new OptionalData();
+ data.myString = Optional.of("simpleString");
+ String value = MAPPER.writeValueAsString(data);
+ assertEquals("{\"myString\":\"simpleString\"}", value);
+ }
+
+ public void testSerComplexObject() throws Exception {
+ OptionalData data = new OptionalData();
+ data.myString = Optional.of("simpleString");
+ String value = MAPPER.writeValueAsString(Optional.of(data));
+ assertEquals("{\"myString\":\"simpleString\"}", value);
+ }
+
+ public void testSerGeneric() throws Exception {
+ OptionalGenericData<String> data = new OptionalGenericData<String>();
+ data.myData = Optional.of("simpleString");
+ String value = MAPPER.writeValueAsString(Optional.of(data));
+ assertEquals("{\"myData\":\"simpleString\"}", value);
+ }
+
+ public void testSerNonNull() throws Exception {
+ OptionalData data = new OptionalData();
+ data.myString = Optional.absent();
+ String value = mapperWithModule().setSerializationInclusion(JsonInclude.Include.NON_NULL).writeValueAsString(data);
+ assertEquals("{}", value);
+ }
+
+ public void testSerOptNull() throws Exception {
+ OptionalData data = new OptionalData();
+ data.myString = null;
+ String value = mapperWithModule().setSerializationInclusion(JsonInclude.Include.NON_NULL).writeValueAsString(data);
+ assertEquals("{}", value);
+ }
+
+ public void testWithTypingEnabled() throws Exception
+ {
+ final ObjectMapper objectMapper = mapperWithModule();
+ // ENABLE TYPING
+ objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
+
+ final OptionalData myData = new OptionalData();
+ myData.myString = Optional.fromNullable("abc");
+
+ final String json = objectMapper.writeValueAsString(myData);
+
+ final OptionalData deserializedMyData = objectMapper.readValue(json, OptionalData.class);
+ assertEquals(myData.myString, deserializedMyData.myString);
+ }
+
+ // [Issue#17]
+ public void testObjectId() throws Exception
+ {
+ final Unit input = new Unit();
+ input.link(input);
+ String json = MAPPER.writeValueAsString(input);
+ Unit result = MAPPER.readValue(json, Unit.class);
+ assertNotNull(result);
+ assertNotNull(result.baseUnit);
+ assertTrue(result.baseUnit.isPresent());
+ Unit base = result.baseUnit.get();
+ assertSame(result, base);
+ }
+
+ // [Issue#37]
+ public void testOptionalCollection() throws Exception {
+ ObjectMapper mapper = new ObjectMapper().registerModule(new GuavaModule());
+
+ TypeReference<List<Optional<String>>> typeReference =
+ new TypeReference<List<Optional<String>>>() {};
+
+ List<Optional<String>> list = new ArrayList<Optional<String>>();
+ list.add(Optional.of("2014-1-22"));
+ list.add(Optional.<String>absent());
+ list.add(Optional.of("2014-1-23"));
+
+ String str = mapper.writeValueAsString(list);
+ assertEquals("[\"2014-1-22\",null,\"2014-1-23\"]", str);
+
+ List<Optional<String>> result = mapper.readValue(str, typeReference);
+ assertEquals(list.size(), result.size());
+ for (int i = 0; i < list.size(); ++i) {
+ assertEquals("Entry #"+i, list.get(i), result.get(i));
+ }
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/datatype/guava/TestOptionalWithPolymorphic.java b/src/test/java/com/fasterxml/jackson/datatype/guava/TestOptionalWithPolymorphic.java
new file mode 100644
index 0000000..782bbc7
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/datatype/guava/TestOptionalWithPolymorphic.java
@@ -0,0 +1,106 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.databind.*;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+
+public class TestOptionalWithPolymorphic extends ModuleTestBase
+{
+ static class ContainerA {
+ @JsonProperty private Optional<String> name = Optional.absent();
+ @JsonProperty private Optional<Strategy> strategy = Optional.absent();
+ }
+
+ static class ContainerB {
+ @JsonProperty private Optional<String> name = Optional.absent();
+ @JsonProperty private Strategy strategy = null;
+ }
+
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
+ @JsonSubTypes({
+ @JsonSubTypes.Type(name = "Foo", value = Foo.class),
+ @JsonSubTypes.Type(name = "Bar", value = Bar.class),
+ @JsonSubTypes.Type(name = "Baz", value = Baz.class)
+ })
+ interface Strategy {
+ }
+
+ static class Foo implements Strategy {
+ @JsonProperty private final int foo;
+ @JsonCreator Foo(@JsonProperty("foo") int foo) {
+ this.foo = foo;
+ }
+ }
+
+ static class Bar implements Strategy {
+ @JsonProperty private final boolean bar;
+ @JsonCreator Bar(@JsonProperty("bar") boolean bar) {
+ this.bar = bar;
+ }
+ }
+
+ static class Baz implements Strategy {
+ @JsonProperty private final String baz;
+ @JsonCreator Baz(@JsonProperty("baz") String baz) {
+ this.baz = baz;
+ }
+ }
+
+ /*
+ /**********************************************************************
+ /* Test methods
+ /**********************************************************************
+ */
+
+ final ObjectMapper MAPPER = mapperWithModule();
+
+ public void testOptionalMapsFoo() throws Exception {
+
+ ImmutableMap<String, Object> foo = ImmutableMap.<String, Object>builder()
+ .put("name", "foo strategy")
+ .put("strategy", ImmutableMap.builder()
+ .put("type", "Foo")
+ .put("foo", 42)
+ .build())
+ .build();
+ _test(MAPPER, foo);
+ }
+
+ public void testOptionalMapsBar() throws Exception {
+
+ ImmutableMap<String, Object> bar = ImmutableMap.<String, Object>builder()
+ .put("name", "bar strategy")
+ .put("strategy", ImmutableMap.builder()
+ .put("type", "Bar")
+ .put("bar", true)
+ .build())
+ .build();
+ _test(MAPPER, bar);
+ }
+
+ public void testOptionalMapsBaz() throws Exception {
+ ImmutableMap<String, Object> baz = ImmutableMap.<String, Object>builder()
+ .put("name", "baz strategy")
+ .put("strategy", ImmutableMap.builder()
+ .put("type", "Baz")
+ .put("baz", "hello world!")
+ .build())
+ .build();
+ _test(MAPPER, baz);
+ }
+
+ private void _test(ObjectMapper m, Map<String, ?> map) throws Exception
+ {
+ String json = m.writeValueAsString(map);
+
+ ContainerA objA = m.readValue(json, ContainerA.class);
+ assertNotNull(objA);
+
+ ContainerB objB = m.readValue(json, ContainerB.class);
+ assertNotNull(objB);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/datatype/guava/TestRange.java b/src/test/java/com/fasterxml/jackson/datatype/guava/TestRange.java
new file mode 100644
index 0000000..3a1a741
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/datatype/guava/TestRange.java
@@ -0,0 +1,78 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.guava.deser.util.RangeFactory;
+import com.google.common.base.Objects;
+import com.google.common.collect.Range;
+
+import java.io.IOException;
+
+/**
+ * Unit tests to verify serialization of Guava {@link Range}s.
+ */
+public class TestRange extends ModuleTestBase {
+
+ private final ObjectMapper MAPPER = mapperWithModule();
+
+ protected static class Untyped
+ {
+ @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
+ public Object range;
+
+ public Untyped() { }
+ public Untyped(Range<?> r) { range = r; }
+ }
+
+ /**
+ * This test is present so that we know if either Jackson's handling of Range
+ * or Guava's implementation of Range changes.
+ * @throws Exception
+ */
+ public void testSerializationWithoutModule() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ Range<Integer> range = RangeFactory.closed(1, 10);
+ String json = mapper.writeValueAsString(range);
+ assertEquals("{\"empty\":false}", json);
+ }
+
+ public void testSerialization() throws Exception
+ {
+ testSerialization(MAPPER, RangeFactory.open(1, 10));
+ testSerialization(MAPPER, RangeFactory.openClosed(1, 10));
+ testSerialization(MAPPER, RangeFactory.closedOpen(1, 10));
+ testSerialization(MAPPER, RangeFactory.closed(1, 10));
+ testSerialization(MAPPER, RangeFactory.atLeast(1));
+ testSerialization(MAPPER, RangeFactory.greaterThan(1));
+ testSerialization(MAPPER, RangeFactory.atMost(10));
+ testSerialization(MAPPER, RangeFactory.lessThan(10));
+ testSerialization(MAPPER, RangeFactory.all());
+ testSerialization(MAPPER, RangeFactory.singleton(1));
+ }
+
+ public void testDeserialization() throws Exception
+ {
+ String json = MAPPER.writeValueAsString(RangeFactory.open(1, 10));
+ @SuppressWarnings("unchecked")
+ Range<Integer> r = (Range<Integer>) MAPPER.readValue(json, Range.class);
+ assertNotNull(r);
+ assertEquals(Integer.valueOf(1), r.lowerEndpoint());
+ assertEquals(Integer.valueOf(10), r.upperEndpoint());
+ }
+
+ private void testSerialization(ObjectMapper objectMapper, Range<?> range) throws IOException
+ {
+ String json = objectMapper.writeValueAsString(range);
+ Range<?> rangeClone = objectMapper.readValue(json, Range.class);
+ assert Objects.equal(rangeClone, range);
+ }
+
+ public void testUntyped() throws Exception
+ {
+ String json = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(new Untyped(RangeFactory.open(1, 10)));
+ Untyped out = MAPPER.readValue(json, Untyped.class);
+ assertNotNull(out);
+ assertEquals(Range.class, out.range.getClass());
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/datatype/guava/TestVersions.java b/src/test/java/com/fasterxml/jackson/datatype/guava/TestVersions.java
new file mode 100644
index 0000000..a43a4d5
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/datatype/guava/TestVersions.java
@@ -0,0 +1,38 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.core.Versioned;
+import com.fasterxml.jackson.core.util.VersionUtil;
+import com.fasterxml.jackson.datatype.guava.GuavaModule;
+import com.fasterxml.jackson.datatype.guava.PackageVersion;
+
+public class TestVersions extends ModuleTestBase
+{
+ public void testMapperVersions() throws IOException
+ {
+ GuavaModule module = new GuavaModule();
+ assertVersion(module);
+ }
+
+ public void testPackageVersion()
+ {
+ assertEquals(PackageVersion.VERSION,
+ VersionUtil.versionFor(GuavaModule.class));
+ }
+
+ /*
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
+ */
+
+ private void assertVersion(Versioned vers)
+ {
+ final Version v = vers.version();
+ assertFalse("Should find version information (got "+v+")", v.isUknownVersion());
+ assertEquals(PackageVersion.VERSION, v);
+ }
+}
+
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/jackson-datatype-guava.git
More information about the pkg-java-commits
mailing list