+- Simplify OSGi settings for build.
+- Guava version now 16 (from 15); does not rely on new features yet
+
+2.6.4 (not yet released)
+
+- Small fix to unwrapped `Optional` serialization, should not fail when trying to
+ serialize empty unwrapped `Optional`.
+
+2.6.3 (12-Oct-2015)
+
+#83: Generic type information for `Optional` wrapped generic type lost in visitor
+ (reported by vzx@github)
+#84: Class not found exception in OSGi (com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanPropertyWriter)
+ (reported by mjameson-se@github)
+- Allow use of `@JsonDeserializer(contentAs=...)` with `Optional`
+
+2.6.2 (15-Sep-2015)
+
+#52: Guava collection types do not allow null values
+ (contributed by Jose T)
+#80: Relax OSGi version constraints for Guava dependency.
+ (requested by Benson M)
+#82: Problem with polymorphic value types for `Optional`, with 2.6
+
+2.6.1 (09-Aug-2015)
+
+No changes since 2.6.0
+
+2.6.0 (19-Jul-2015)
+
+#64: `@JsonUnwrapped` annotation is ignored when a field is an Optional
+ (reported by Alexey K, akobiakov@github)
+#66: Add `GuavaModule.configureAbsentsAsNulls(boolean)` to change whether
+ `Optional.absent()` is to be handled same as Java null during serialization
+ (default: true) or not.
+#67: Support deserializing ImmutableSetMultimaps
+ (contributed by Michael H, michaelhixson@github)
+#69: Add support for `JsonInclude.Include.NON_ABSENT`, to compensate for #66
+#70: Change OSGi manifest entries to import guava 15.0 or greater
+ (reported by sprynter@github)
+#74: Multimap serializer ignores _valueTypeSerializer
+ (reported by soldierkam@github)
+
+2.5.4 (09-Jun-2015)
+
+- Add override for `MultimapSerializer.isEmpty(SerializerProvider, Multimap,?>)`
+ for forward compatibility (needed for databind 2.6)
+
+2.5.3 (24-Apr-2015)
+
+#65: Add deserialization support for SortedMultiset and ImmutableSortedMultiset
+ (contributed by Stephan S, Stephan202@github)
+
+2.5.2 (29-Mar-2015)
+
+#62: Add `com.google.common.hash` to OSGi import list
+ (reported by lukewink@github)
+
+2.5.1 (06-Fev-2015)
+
+No changes since 2.5.0
+
+2.5.0 (01-Jan-2015)
+
+#56: Add support `HashCode`
+ (contributed by Stephan S, Stephan202@github)
+
+2.4.6 (23-Apr-2015)
+
+#61: NPE serializing `Multimap`s with null values
+ (reported by sixinli@github)
+
+2.4.5 (13-Jan-2015)
+
+#58: `FluentIterable` serialization doesn't work for Bean properties
+ (fixed by Christopher C)
+
+2.4.4 (24-Nov-2014)
+
+No changes since 2.4.3
+
+2.4.3 (04-Oct-2014)
+
+#50: Add support for `InternetDomainName`
+ (suggested by sdavids@github)
+- Improved serialization, type handling, schema-access for `Range` and `Optional`.
+
+2.4.2 (15-Aug-2014)
+
+#46: Can not serialize guava Iterables
+ (reported by chisui@github)
+
+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@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@github)
+
+2.3.1 (28-Dec-2013)
+
+#33: Add support for `Range` values
+ (contribute by ispringer@github)
+#34: Use Optional type parameter if present to create JSON schema
+ (contributed by cponomaryov@github)
+
+2.3.0 (14-Nov-2013)
+
+#29: Empty ImmutableMap not deserialized correctly, when type info included
+ (reported by pbergn@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/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java
new file mode 100644
index 00000000..bb492fe9
--- /dev/null
+++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java
@@ -0,0 +1,275 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.*;
+import com.google.common.hash.HashCode;
+import com.google.common.net.HostAndPort;
+import com.google.common.net.InternetDomainName;
+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.ReferenceType;
+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;
+
+/**
+ * Custom deserializers module offers.
+ */
+public class GuavaDeserializers
+ extends Deserializers.Base
+{
+ protected BoundType _defaultBoundType;
+
+ public GuavaDeserializers() {
+ this(null);
+ }
+
+ public GuavaDeserializers(BoundType defaultBoundType) {
+ _defaultBoundType = defaultBoundType;
+ }
+
+ /**
+ * 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)) {
+ // sorted one?
+ if (ImmutableSortedMultiset.class.isAssignableFrom(raw)) {
+ /* See considerations for ImmutableSortedSet below. */
+ requireCollectionOfComparableElements(type, "ImmutableSortedMultiset");
+ return new ImmutableSortedMultisetDeserializer(type,
+ elementTypeDeserializer, elementDeserializer);
+ }
+ // nah, just regular one
+ 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...
+ */
+ requireCollectionOfComparableElements(type, "ImmutableSortedSet");
+ 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)) {
+ if (SortedMultiset.class.isAssignableFrom(raw)) {
+ if (TreeMultiset.class.isAssignableFrom(raw)) {
+ return new TreeMultisetDeserializer(type, elementTypeDeserializer, elementDeserializer);
+ }
+
+ // TODO: make configurable (for now just default blindly)
+ return new TreeMultisetDeserializer(type, elementTypeDeserializer, elementDeserializer);
+ }
+
+ // 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
+ }
+
+ // TODO: make configurable (for now just default blindly)
+ return new HashMultisetDeserializer(type, elementTypeDeserializer, elementDeserializer);
+ }
+
+ return null;
+ }
+
+ private void requireCollectionOfComparableElements(CollectionType actualType, String targetType) {
+ Class> elemType = actualType.getContentType().getRawClass();
+ if (!Comparable.class.isAssignableFrom(elemType)) {
+ throw new IllegalArgumentException("Can not handle " + targetType
+ + " with elements that are not Comparable> (" + elemType.getName() + ")");
+ }
+ }
+
+ /**
+ * 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)) {
+ // [Issue#67]: Preserve order of entries
+ return new LinkedHashMultimapDeserializer(type, keyDeserializer,
+ elementTypeDeserializer, elementDeserializer);
+ }
+ 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;
+ }
+
+ // 21-Oct-2015, tatu: Code much simplified with 2.7 where we should be getting much
+ // of boilerplate handling automatically
+
+ @Override // since 2.7
+ public JsonDeserializer> findReferenceDeserializer(ReferenceType refType,
+ DeserializationConfig config, BeanDescription beanDesc,
+ TypeDeserializer contentTypeDeserializer, JsonDeserializer> contentDeserializer)
+ {
+ if (refType.hasRawClass(Optional.class)) {
+ return new GuavaOptionalDeserializer(refType, contentTypeDeserializer, contentDeserializer);
+ }
+ return null;
+ }
+
+ @Override
+ public JsonDeserializer> findBeanDeserializer(final JavaType type, DeserializationConfig config,
+ BeanDescription beanDesc)
+ {
+ if (type.hasRawClass(Range.class)) {
+ return new RangeDeserializer(_defaultBoundType, type);
+ }
+ if (type.hasRawClass(HostAndPort.class)) {
+ return HostAndPortDeserializer.std;
+ }
+ if (type.hasRawClass(InternetDomainName.class)) {
+ return InternetDomainNameDeserializer.std;
+ }
+ if (type.hasRawClass(HashCode.class)) {
+ return HashCodeDeserializer.std;
+ }
+ return null;
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaModule.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaModule.java
new file mode 100644
index 00000000..b82f3c7f
--- /dev/null
+++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaModule.java
@@ -0,0 +1,110 @@
+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;
+import com.google.common.collect.BoundType;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Basic Jackson {@link Module} that adds support for Guava types.
+ *
+ * Current configurability includes:
+ *
+ * configureAbsentsAsNulls
(default: false
):
+ * Determines whether inclusion strategy of NON_NULL
should additionally consider
+ * Optional.absent()
values (as POJO properties) to be excluded; if true, they will
+ * be excluded, if false, they will be included.
+ * Note that the defaults for other "Optional" types are different; Guava setting is chosen solely
+ * for backwards compatibility.
+ *
+ *
+ */
+public class GuavaModule extends Module // can't use just SimpleModule, due to generic types
+{
+ private final String NAME = "GuavaModule";
+
+ /**
+ * Configuration setting that determines whether `Optional.absent()` is
+ * considered "same as null" for serialization purposes; that is, to be
+ * filtered same as nulls are.
+ * If enabled, absent values are treated like nulls; if disabled, they are not.
+ * In either case, absent values are always considered "empty".
+ *
+ * Default value is `true` for backwards compatibility (2.5 and prior
+ * only had this behavior).
+ *
+ * Note that this setting MUST be changed BEFORE registering the module:
+ * changes after registration will have no effect.
+ */
+ protected boolean _cfgHandleAbsentAsNull = true;
+ protected BoundType _defaultBoundType;
+
+ 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(_defaultBoundType));
+ context.addSerializers(new GuavaSerializers());
+ context.addTypeModifier(new GuavaTypeModifier());
+
+ // 28-Apr-2015, tatu: Allow disabling "treat Optional.absent() like Java nulls"
+ if (_cfgHandleAbsentAsNull) {
+ context.addBeanSerializerModifier(new GuavaBeanSerializerModifier());
+ }
+ }
+
+ /**
+ * Configuration method that may be used to change configuration setting
+ * _cfgHandleAbsentAsNull
: enabling means that `Optional.absent()` values
+ * are handled like Java nulls (wrt filtering on serialization); disabling that
+ * they are only treated as "empty" values, but not like native Java nulls.
+ * Recommended setting for this value is `false`, for compatibility with other
+ * "optional" values (like JDK 8 optionals); but the default is `true` for
+ * backwards compatibility.
+ *
+ * @return This module instance, useful for chaining calls
+ *
+ * @since 2.6
+ */
+ public GuavaModule configureAbsentsAsNulls(boolean state) {
+ _cfgHandleAbsentAsNull = state;
+ return this;
+ }
+
+ /**
+ * Configuration method that may be used to change the {@link BoundType} to be used
+ * when deserializing {@link com.google.common.collect.Range} objects. This configuration
+ * will is used when the object to be deserialized has no bound type attribute.
+ * The default {@link BoundType} is CLOSED.
+ *
+ * @param boundType {@link BoundType}
+ *
+ * @return This module instance, useful for chaining calls
+
+ * @since 2.7
+ */
+ public GuavaModule defaultBoundType(BoundType boundType) {
+ checkNotNull(boundType);
+ _defaultBoundType = boundType;
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ return NAME.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o;
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaSerializers.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaSerializers.java
new file mode 100644
index 00000000..f7becfdc
--- /dev/null
+++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaSerializers.java
@@ -0,0 +1,110 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import java.util.HashSet;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.introspect.Annotated;
+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.databind.type.ReferenceType;
+import com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer;
+import com.fasterxml.jackson.databind.util.ArrayBuilders;
+import com.fasterxml.jackson.databind.util.StdConverter;
+import com.google.common.base.Optional;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheBuilderSpec;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Range;
+import com.google.common.collect.Table;
+import com.google.common.hash.HashCode;
+import com.google.common.net.HostAndPort;
+import com.google.common.net.InternetDomainName;
+import com.fasterxml.jackson.datatype.guava.ser.GuavaOptionalSerializer;
+import com.fasterxml.jackson.datatype.guava.ser.MultimapSerializer;
+import com.fasterxml.jackson.datatype.guava.ser.RangeSerializer;
+import com.fasterxml.jackson.datatype.guava.ser.TableSerializer;
+
+public class GuavaSerializers extends Serializers.Base
+{
+ static class FluentConverter extends StdConverter> {
+ static final FluentConverter instance = new FluentConverter();
+
+ @Override
+ public Iterable> convert(Object value) {
+ return (Iterable>) value;
+ }
+ }
+
+ @Override
+ public JsonSerializer> findReferenceSerializer(SerializationConfig config,
+ ReferenceType refType, BeanDescription beanDesc,
+ TypeSerializer contentTypeSerializer, JsonSerializer contentValueSerializer)
+ {
+ final Class> raw = refType.getRawClass();
+ if (Optional.class.isAssignableFrom(raw)) {
+ return new GuavaOptionalSerializer(refType, contentTypeSerializer, contentValueSerializer);
+ }
+ return null;
+ }
+
+ @Override
+ public JsonSerializer> findSerializer(SerializationConfig config, JavaType type, BeanDescription beanDesc)
+ {
+ Class> raw = type.getRawClass();
+ if (Range.class.isAssignableFrom(raw)) {
+ return new RangeSerializer(_findDeclared(type, Range.class));
+ }
+ if (Table.class.isAssignableFrom(raw)) {
+ return new TableSerializer(_findDeclared(type, Table.class));
+ }
+
+ // since 2.4
+ if (HostAndPort.class.isAssignableFrom(raw)) {
+ return ToStringSerializer.instance;
+ }
+ if (InternetDomainName.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;
+ }
+ if (HashCode.class.isAssignableFrom(raw)) {
+ return ToStringSerializer.instance;
+ }
+ if (FluentIterable.class.isAssignableFrom(raw)) {
+ JavaType iterableType = _findDeclared(type, Iterable.class);
+ return new StdDelegatingSerializer(FluentConverter.instance, iterableType, null);
+ }
+ return super.findSerializer(config, type, beanDesc);
+ }
+
+ @Override
+ public JsonSerializer> findMapLikeSerializer(SerializationConfig config,
+ MapLikeType type, BeanDescription beanDesc, JsonSerializer keySerializer,
+ TypeSerializer elementTypeSerializer, JsonSerializer elementValueSerializer)
+ {
+ if (Multimap.class.isAssignableFrom(type.getRawClass())) {
+ final AnnotationIntrospector intr = config.getAnnotationIntrospector();
+ Object filterId = intr.findFilterId((Annotated)beanDesc.getClassInfo());
+ String[] ignored = intr.findPropertiesToIgnore(beanDesc.getClassInfo(), true);
+ HashSet ignoredEntries = (ignored == null || ignored.length == 0)
+ ? null : ArrayBuilders.arrayToSet(ignored);
+
+ return new MultimapSerializer(type, beanDesc,
+ keySerializer, elementTypeSerializer, elementValueSerializer, ignoredEntries, filterId);
+ }
+ return null;
+ }
+
+ private JavaType _findDeclared(JavaType subtype, Class> target) {
+ JavaType decl = subtype.findSuperType(target);
+ if (decl == null) { // should never happen but
+ throw new IllegalArgumentException("Strange "+target.getName()+" sub-type, "+subtype+", can not find type parameters");
+ }
+ return decl;
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaTypeModifier.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaTypeModifier.java
new file mode 100644
index 00000000..554b2205
--- /dev/null
+++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaTypeModifier.java
@@ -0,0 +1,48 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import java.lang.reflect.Type;
+
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.type.*;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.*;
+
+/**
+ * We need somewhat hacky support for following Guava types:
+ *
+ * FluentIterable: addition of seeming "empty" property should not prevent serialization as
+ * basic `Iterable` (with standard Jackson (de)serializer)
+ *
+ * Multimap: can reuse much/most of standard Map support as long as we make sure it is
+ * recognized as "Map-like" type (similar to how Scala Maps are supported)
+ *
+ * Optional: generic type, simpler, more-efficient to detect parameterization here (although
+ * not strictly mandatory)
+ * Range: same as with Optional, might as well resolve generic type information early on
+ *
+ *
+ *
+ */
+public class GuavaTypeModifier extends TypeModifier
+{
+ @Override
+ public JavaType modifyType(JavaType type, Type jdkType, TypeBindings bindings, TypeFactory typeFactory)
+ {
+ if (type.isReferenceType() || type.isContainerType()) {
+ return type;
+ }
+
+ final Class> raw = type.getRawClass();
+ // First: make Multimaps look more Map-like
+ if (raw == Multimap.class) {
+ return MapLikeType.upgradeFrom(type,
+ type.containedTypeOrUnknown(0),
+ type.containedTypeOrUnknown(1));
+ }
+ if (raw == Optional.class) {
+ return ReferenceType.upgradeFrom(type, type.containedTypeOrUnknown(0));
+ }
+ return type;
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/PackageVersion.java.in b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/PackageVersion.java.in
new file mode 100644
index 00000000..7860aa14
--- /dev/null
+++ b/guava/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/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaCollectionDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaCollectionDeserializer.java
new file mode 100644
index 00000000..051022b2
--- /dev/null
+++ b/guava/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
+ extends StdDeserializer
+ 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 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/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaImmutableCollectionDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaImmutableCollectionDeserializer.java
new file mode 100644
index 00000000..b5de17cd
--- /dev/null
+++ b/guava/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>
+ extends GuavaCollectionDeserializer
+{
+ private static final long serialVersionUID = 1L;
+
+ GuavaImmutableCollectionDeserializer(CollectionType type,
+ TypeDeserializer typeDeser, JsonDeserializer> deser) {
+ super(type, typeDeser, deser);
+ }
+
+ protected abstract ImmutableCollection.Builder 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 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/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaImmutableMapDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaImmutableMapDeserializer.java
new file mode 100644
index 00000000..f47316e1
--- /dev/null
+++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaImmutableMapDeserializer.java
@@ -0,0 +1,79 @@
+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> extends
+ GuavaMapDeserializer {
+
+ GuavaImmutableMapDeserializer(MapType type, KeyDeserializer keyDeser, TypeDeserializer typeDeser,
+ JsonDeserializer> deser) {
+ super(type, keyDeser, typeDeser, deser);
+ }
+
+ protected abstract ImmutableMap.Builder createBuilder();
+
+ @Override
+ protected T _deserializeEntries(JsonParser p, DeserializationContext ctxt)
+ throws IOException, JsonProcessingException
+ {
+ final KeyDeserializer keyDes = _keyDeserializer;
+ final JsonDeserializer> valueDes = _valueDeserializer;
+ final TypeDeserializer typeDeser = _typeDeserializerForValue;
+
+ ImmutableMap.Builder builder = createBuilder();
+ for (; p.getCurrentToken() == JsonToken.FIELD_NAME; p.nextToken()) {
+ // Must point to field name now
+ String fieldName = p.getCurrentName();
+ Object key = (keyDes == null) ? fieldName : keyDes.deserializeKey(fieldName, ctxt);
+ // And then the value...
+ JsonToken t = p.nextToken();
+ // 28-Nov-2010, tatu: Should probably support "ignorable properties" in future...
+ Object value;
+ if (t == JsonToken.VALUE_NULL) {
+ _handleNull(ctxt, key, _valueDeserializer, builder);
+ continue;
+ }
+ if (typeDeser == null) {
+ value = valueDes.deserialize(p, ctxt);
+ } else {
+ value = valueDes.deserializeWithType(p, 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;
+ }
+
+ /**
+ * Overridable helper method called when a JSON null value is encountered.
+ * Since Guava Maps typically do not allow null values, special handling
+ * is needed; default is to simply ignore and skip such values, but alternative
+ * could be to throw an exception.
+ */
+ protected void _handleNull(DeserializationContext ctxt, Object key,
+ JsonDeserializer> valueDeser,
+ ImmutableMap.Builder builder) throws IOException
+ {
+ // 14-Sep-2015, tatu: As per [datatype-guava#52], avoid exception due to null
+ // TODO: allow reporting problem via a feature, in future?
+
+ // Actually, first, see if there's an alternative to Java null
+ Object nvl = valueDeser.getNullValue(ctxt);
+ if (nvl != null) {
+ builder.put(key, nvl);
+ }
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaMapDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaMapDeserializer.java
new file mode 100644
index 00000000..9075756d
--- /dev/null
+++ b/guava/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
+ extends JsonDeserializer
+ 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 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/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaMultisetDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaMultisetDeserializer.java
new file mode 100644
index 00000000..df340752
--- /dev/null
+++ b/guava/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>
+ extends GuavaCollectionDeserializer
+{
+ 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/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaOptionalDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaOptionalDeserializer.java
new file mode 100644
index 00000000..79373e15
--- /dev/null
+++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaOptionalDeserializer.java
@@ -0,0 +1,145 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonParser;
+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.introspect.AnnotatedMember;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
+import com.google.common.base.Optional;
+
+public class GuavaOptionalDeserializer
+ extends StdDeserializer>
+ implements ContextualDeserializer
+{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Full type of `Optional` property.
+ */
+ protected final JavaType _fullType;
+
+ protected final JsonDeserializer> _valueDeserializer;
+
+ protected final TypeDeserializer _valueTypeDeserializer;
+
+ public GuavaOptionalDeserializer(JavaType fullType,
+ TypeDeserializer typeDeser, JsonDeserializer> valueDeser)
+ {
+ super(fullType);
+ _fullType = fullType;
+ _valueTypeDeserializer = typeDeser;
+ _valueDeserializer = valueDeser;
+ }
+
+ @Override
+ public JavaType getValueType() { return _fullType; }
+
+ @Override
+ public Optional> getNullValue(DeserializationContext ctxt) {
+ return Optional.absent();
+ }
+
+ @Override
+ @Deprecated // since 2.6; remove from 2.8
+ public Optional> getNullValue() {
+ return Optional.absent();
+ }
+
+ /**
+ * Overridable fluent factory method used for creating contextual
+ * instances.
+ */
+ protected GuavaOptionalDeserializer withResolved(JavaType fullType,
+ TypeDeserializer typeDeser, JsonDeserializer> valueDeser)
+ {
+ if ((_fullType == fullType)
+ && (valueDeser == _valueDeserializer) && (typeDeser == _valueTypeDeserializer)) {
+ return this;
+ }
+ return new GuavaOptionalDeserializer(_fullType, 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;
+ JavaType fullType = _fullType;
+
+ if (deser == null) {
+ // 08-Oct-2015, tatu: need to allow type override, if any
+ if (property != null) {
+ AnnotationIntrospector intr = ctxt.getAnnotationIntrospector();
+ AnnotatedMember member = property.getMember();
+ if ((intr != null) && (member != null)) {
+ fullType = intr.refineDeserializationType(ctxt.getConfig(), member, fullType);
+ }
+ }
+ JavaType refdType = fullType.getContentType();
+ if (refdType == null) {
+ refdType = TypeFactory.unknownType();
+ }
+ deser = ctxt.findContextualValueDeserializer(refdType, property);
+ } else { // otherwise directly assigned, probably not contextual yet:
+ JavaType refdType = fullType.getContentType();
+ if (refdType == null) {
+ refdType = TypeFactory.unknownType();
+ }
+ deser = ctxt.handleSecondaryContextualization(deser, property, refdType);
+ }
+ if (typeDeser != null) {
+ typeDeser = typeDeser.forProperty(property);
+ }
+ return withResolved(fullType, typeDeser, deser);
+ }
+
+ @Override
+ public Optional> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
+ {
+ Object refd = (_valueTypeDeserializer == null)
+ ? _valueDeserializer.deserialize(p, ctxt)
+ : _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
+ return Optional.fromNullable(refd);
+ }
+
+ @Override
+ public Optional> deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
+ throws IOException
+ {
+ final JsonToken t = p.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.
+ // 22-Oct-2015, tatu: Most likely this is actually wrong, result of incorrewct
+ // serialization (up to 2.6, was omitting necessary type info after all);
+ // but safest to leave in place for now
+ if (t != null && t.isScalarValue()) {
+ return deserialize(p, ctxt);
+ }
+ return (Optional>) typeDeserializer.deserializeTypedFromAny(p, ctxt);
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/HashCodeDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/HashCodeDeserializer.java
new file mode 100644
index 00000000..9410c336
--- /dev/null
+++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/HashCodeDeserializer.java
@@ -0,0 +1,23 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer;
+import com.google.common.hash.HashCode;
+
+public class HashCodeDeserializer extends FromStringDeserializer
+{
+ private static final long serialVersionUID = 1L;
+
+ public final static HashCodeDeserializer std = new HashCodeDeserializer();
+
+ public HashCodeDeserializer() { super(HashCode.class); }
+
+ @Override
+ protected HashCode _deserialize(String value, DeserializationContext ctxt)
+ throws IOException {
+ return HashCode.fromString(value.toLowerCase(Locale.ENGLISH));
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/HashMultisetDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/HashMultisetDeserializer.java
new file mode 100644
index 00000000..212c6b1f
--- /dev/null
+++ b/guava/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>
+{
+ 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 createMultiset() {
+ return HashMultiset. create();
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/HostAndPortDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/HostAndPortDeserializer.java
new file mode 100644
index 00000000..e1d8f777
--- /dev/null
+++ b/guava/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.FromStringDeserializer;
+import com.google.common.net.HostAndPort;
+
+public class HostAndPortDeserializer extends FromStringDeserializer
+{
+ 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
+ {
+ // Need to override this method, which otherwise would work just fine,
+ // since we have legacy JSON Object format to support too:
+ if (jp.getCurrentToken() == 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());
+ }
+ return super.deserialize(jp, ctxt);
+ }
+
+ @Override
+ protected HostAndPort _deserialize(String value, DeserializationContext ctxt)
+ throws IOException {
+ return HostAndPort.fromString(value);
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableBiMapDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableBiMapDeserializer.java
new file mode 100644
index 00000000..c6a7f515
--- /dev/null
+++ b/guava/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> {
+ public ImmutableBiMapDeserializer(MapType type, KeyDeserializer keyDeser, TypeDeserializer typeDeser,
+ JsonDeserializer> deser) {
+ super(type, keyDeser, typeDeser, deser);
+ }
+
+ @Override
+ protected Builder createBuilder() {
+ return ImmutableBiMap.builder();
+ }
+
+ @Override
+ public GuavaMapDeserializer> withResolved(KeyDeserializer keyDeser,
+ TypeDeserializer typeDeser, JsonDeserializer> valueDeser) {
+ return new ImmutableBiMapDeserializer(_mapType, keyDeser, typeDeser, valueDeser);
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableListDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableListDeserializer.java
new file mode 100644
index 00000000..51a3e5b9
--- /dev/null
+++ b/guava/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>
+{
+ 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 createBuilder() {
+ ImmutableList.Builder builder = ImmutableList.builder();
+ return builder;
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableMapDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableMapDeserializer.java
new file mode 100644
index 00000000..6421b492
--- /dev/null
+++ b/guava/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>
+{
+ 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 createBuilder() {
+ return ImmutableMap.builder();
+ }
+
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableMultisetDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableMultisetDeserializer.java
new file mode 100644
index 00000000..c488ed88
--- /dev/null
+++ b/guava/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>
+{
+ private static final long serialVersionUID = 1L;
+
+ public ImmutableMultisetDeserializer(CollectionType type, TypeDeserializer typeDeser, JsonDeserializer> deser) {
+ super(type, typeDeser, deser);
+ }
+
+ @Override
+ protected Builder createBuilder() {
+ return ImmutableMultiset.builder();
+ }
+
+ @Override
+ public GuavaCollectionDeserializer> withResolved(TypeDeserializer typeDeser,
+ JsonDeserializer> valueDeser) {
+ return new ImmutableMultisetDeserializer(_containerType, typeDeser, valueDeser);
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSetDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSetDeserializer.java
new file mode 100644
index 00000000..132200d1
--- /dev/null
+++ b/guava/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>
+{
+ 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 createBuilder() {
+ return ImmutableSet.builder();
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSortedMapDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSortedMapDeserializer.java
new file mode 100644
index 00000000..179e1de6
--- /dev/null
+++ b/guava/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> {
+
+ public ImmutableSortedMapDeserializer(MapType type, KeyDeserializer keyDeser, TypeDeserializer typeDeser,
+ JsonDeserializer> deser) {
+ super(type, keyDeser, typeDeser, deser);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected Builder 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.naturalOrder();
+ ImmutableSortedMap.Builder builder = (ImmutableSortedMap.Builder) naturalOrder;
+ return builder;
+ }
+
+ @Override
+ public GuavaMapDeserializer> withResolved(KeyDeserializer keyDeser,
+ TypeDeserializer typeDeser, JsonDeserializer> valueDeser) {
+ return new ImmutableSortedMapDeserializer(_mapType, keyDeser, typeDeser, valueDeser);
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSortedMultisetDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSortedMultisetDeserializer.java
new file mode 100644
index 00000000..12949186
--- /dev/null
+++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSortedMultisetDeserializer.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.ImmutableSortedMultiset;
+
+public class ImmutableSortedMultisetDeserializer extends GuavaImmutableCollectionDeserializer>
+{
+ private static final long serialVersionUID = 1L;
+
+ public ImmutableSortedMultisetDeserializer(CollectionType type, TypeDeserializer typeDeser, JsonDeserializer> deser) {
+ super(type, typeDeser, deser);
+ }
+
+ @Override
+ protected Builder createBuilder() {
+ /* This is suboptimal. See the considerations in ImmutableSortedSetDeserializer. */
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ Builder builder = (Builder) ImmutableSortedMultiset.naturalOrder();
+ return builder;
+ }
+
+ @Override
+ public GuavaCollectionDeserializer> withResolved(TypeDeserializer typeDeser,
+ JsonDeserializer> valueDeser) {
+ return new ImmutableSortedMultisetDeserializer(_containerType, typeDeser, valueDeser);
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSortedSetDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/ImmutableSortedSetDeserializer.java
new file mode 100644
index 00000000..ff46bb4f
--- /dev/null
+++ b/guava/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>
+{
+ 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 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. naturalOrder();
+ ImmutableSortedSet.Builder builder = (ImmutableSortedSet.Builder) builderComp;
+ return builder;
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/InternetDomainNameDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/InternetDomainNameDeserializer.java
new file mode 100644
index 00000000..f8ed124b
--- /dev/null
+++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/InternetDomainNameDeserializer.java
@@ -0,0 +1,23 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import java.io.IOException;
+
+import com.google.common.net.InternetDomainName;
+
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer;
+
+public class InternetDomainNameDeserializer extends FromStringDeserializer
+{
+ private static final long serialVersionUID = 1L;
+
+ public final static InternetDomainNameDeserializer std = new InternetDomainNameDeserializer();
+
+ public InternetDomainNameDeserializer() { super(InternetDomainName.class); }
+
+ @Override
+ protected InternetDomainName _deserialize(String value, DeserializationContext ctxt)
+ throws IOException {
+ return InternetDomainName.from(value);
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/LinkedHashMultisetDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/LinkedHashMultisetDeserializer.java
new file mode 100644
index 00000000..7306ca6a
--- /dev/null
+++ b/guava/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>
+{
+ private static final long serialVersionUID = 1L;
+
+ public LinkedHashMultisetDeserializer(CollectionType type, TypeDeserializer typeDeser, JsonDeserializer> deser) {
+ super(type, typeDeser, deser);
+ }
+
+ @Override
+ protected LinkedHashMultiset createMultiset() {
+ return LinkedHashMultiset.create();
+ }
+
+ @Override
+ public GuavaCollectionDeserializer> withResolved(TypeDeserializer typeDeser,
+ JsonDeserializer> valueDeser) {
+ return new LinkedHashMultisetDeserializer(_containerType, typeDeser, valueDeser);
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/RangeDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/RangeDeserializer.java
new file mode 100644
index 00000000..e2d3bae5
--- /dev/null
+++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/RangeDeserializer.java
@@ -0,0 +1,197 @@
+package com.fasterxml.jackson.datatype.guava.deser;
+
+import java.io.IOException;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.BoundType;
+import com.google.common.collect.Range;
+
+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.TypeFactory;
+
+import com.fasterxml.jackson.datatype.guava.deser.util.RangeFactory;
+
+/**
+ * Jackson deserializer for a Guava {@link Range}.
+ *
+ * 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>
+ implements ContextualDeserializer
+{
+ private static final long serialVersionUID = 1L;
+
+ protected final JavaType _rangeType;
+
+ protected final JsonDeserializer _endpointDeserializer;
+
+ private BoundType _defaultBoundType;
+
+ /*
+ /**********************************************************
+ /* Life-cycle
+ /**********************************************************
+ */
+
+ /**
+ * @deprecated Since 2.7
+ */
+ @Deprecated // since 2.7
+ public RangeDeserializer(JavaType rangeType) {
+ this(null, rangeType);
+ }
+
+ public RangeDeserializer(BoundType defaultBoundType, JavaType rangeType) {
+ this(rangeType, null);
+ _defaultBoundType = defaultBoundType;
+ }
+
+ @SuppressWarnings("unchecked")
+ public RangeDeserializer(JavaType rangeType, JsonDeserializer> endpointDeser)
+ {
+ super(rangeType);
+ _rangeType = rangeType;
+ _endpointDeserializer = (JsonDeserializer) endpointDeser;
+ }
+
+ @SuppressWarnings("unchecked")
+ public RangeDeserializer(JavaType rangeType, JsonDeserializer> endpointDeser, BoundType defaultBoundType)
+ {
+ super(rangeType);
+ _rangeType = rangeType;
+ _endpointDeserializer = (JsonDeserializer) endpointDeser;
+ _defaultBoundType = defaultBoundType;
+ }
+
+ @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 deser = ctxt.findContextualValueDeserializer(endpointType, property);
+ return new RangeDeserializer(_rangeType, deser, _defaultBoundType);
+ }
+ return this;
+ }
+
+ /*
+ /**********************************************************
+ /* Actual deserialization
+ /**********************************************************
+ */
+
+ @Override
+ public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt,
+ TypeDeserializer typeDeserializer)
+ throws IOException
+ {
+ return typeDeserializer.deserializeTypedFromObject(jp, ctxt);
+ }
+
+ @Override
+ public Range> deserialize(JsonParser parser, DeserializationContext context)
+ throws IOException
+ {
+ // 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 = _defaultBoundType;
+ BoundType upperBoundType = _defaultBoundType;
+
+ for (; t != JsonToken.END_OBJECT; t = parser.nextToken()) {
+ expect(parser, JsonToken.FIELD_NAME, t);
+ String fieldName = parser.getCurrentName();
+ try {
+ if (fieldName.equals("lowerEndpoint")) {
+ parser.nextToken();
+ lowerEndpoint = deserializeEndpoint(parser, context);
+ } else if (fieldName.equals("upperEndpoint")) {
+ parser.nextToken();
+ upperEndpoint = deserializeEndpoint(parser, context);
+ } else if (fieldName.equals("lowerBoundType")) {
+ parser.nextToken();
+ lowerBoundType = deserializeBoundType(parser);
+ } else if (fieldName.equals("upperBoundType")) {
+ parser.nextToken();
+ upperBoundType = deserializeBoundType(parser);
+ } else {
+ throw context.mappingException("Unexpected Range field: " + fieldName);
+ }
+ } catch (IllegalStateException e) {
+ throw JsonMappingException.from(parser, 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 JsonMappingException.from(parser, 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 p, JsonToken expected, JsonToken actual) throws JsonMappingException
+ {
+ if (actual != expected) {
+ throw JsonMappingException.from(p, "Expecting " + expected + ", found " + actual);
+ }
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/TreeMultisetDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/TreeMultisetDeserializer.java
new file mode 100644
index 00000000..ff0ab476
--- /dev/null
+++ b/guava/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>
+{
+ private static final long serialVersionUID = 1L;
+
+ public TreeMultisetDeserializer(CollectionType type, TypeDeserializer typeDeser, JsonDeserializer> deser) {
+ super(type, typeDeser, deser);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected TreeMultiset createMultiset() {
+ @SuppressWarnings("rawtypes")
+ TreeMultiset> naturalOrder = TreeMultiset. create();
+ return (TreeMultiset) naturalOrder;
+ }
+
+ @Override
+ public GuavaCollectionDeserializer> withResolved(TypeDeserializer typeDeser,
+ JsonDeserializer> valueDeser) {
+ return new TreeMultisetDeserializer(_containerType, typeDeser, valueDeser);
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/GuavaMultimapDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/GuavaMultimapDeserializer.java
new file mode 100644
index 00000000..53cb4f32
--- /dev/null
+++ b/guava/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> extends JsonDeserializer implements ContextualDeserializer {
+
+ private static final List 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(jp, "Could not map to " + type, _peel(e));
+ } catch (IllegalArgumentException e) {
+ throw new JsonMappingException(jp, "Could not map to " + type, _peel(e));
+ } catch (IllegalAccessException e) {
+ throw new JsonMappingException(jp, "Could not map to " + type, _peel(e));
+ }
+ }
+
+ private void expect(JsonParser jp, JsonToken token) throws IOException {
+ if (jp.getCurrentToken() != token) {
+ throw new JsonMappingException(jp, "Expecting " + token + ", found " + jp.getCurrentToken(),
+ jp.getCurrentLocation());
+ }
+ }
+
+ private Throwable _peel(Throwable t) {
+ while (t.getCause() != null) {
+ t = t.getCause();
+ }
+ return t;
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/list/ArrayListMultimapDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/list/ArrayListMultimapDeserializer.java
new file mode 100644
index 00000000..cdf673b9
--- /dev/null
+++ b/guava/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> {
+
+ 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 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/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/list/LinkedListMultimapDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/list/LinkedListMultimapDeserializer.java
new file mode 100644
index 00000000..263f6fea
--- /dev/null
+++ b/guava/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>
+{
+ 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 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/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/set/HashMultimapDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/set/HashMultimapDeserializer.java
new file mode 100644
index 00000000..75693211
--- /dev/null
+++ b/guava/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> {
+
+ 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 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/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/set/LinkedHashMultimapDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/set/LinkedHashMultimapDeserializer.java
new file mode 100644
index 00000000..fc442acc
--- /dev/null
+++ b/guava/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> {
+
+ 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 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/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/util/RangeFactory.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/util/RangeFactory.java
new file mode 100644
index 00000000..0d7c54d8
--- /dev/null
+++ b/guava/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 > Range open(C lowerEndpoint, C upperEndpoint)
+ {
+ return range(lowerEndpoint, BoundType.OPEN, upperEndpoint, BoundType.OPEN);
+ }
+
+ public static > Range openClosed(C lowerEndpoint, C upperEndpoint)
+ {
+ return range(lowerEndpoint, BoundType.OPEN, upperEndpoint, BoundType.CLOSED);
+ }
+
+ public static > Range closedOpen(C lowerEndpoint, C upperEndpoint)
+ {
+ return range(lowerEndpoint, BoundType.CLOSED, upperEndpoint, BoundType.OPEN);
+ }
+
+ public static > Range closed(C lowerEndpoint, C upperEndpoint)
+ {
+ return range(lowerEndpoint, BoundType.CLOSED, upperEndpoint, BoundType.CLOSED);
+ }
+
+ public static > Range range(final C lowerEndpoint, final BoundType lowerBoundType,
+ final C upperEndpoint, final BoundType upperBoundType)
+ {
+ return createRange(new Callable>() {
+ @Override
+ public Range call() throws Exception {
+ return Range.range(lowerEndpoint, lowerBoundType, upperEndpoint, upperBoundType);
+ }
+ }, legacyRangeMethod, lowerEndpoint, lowerBoundType, upperEndpoint, upperBoundType);
+ }
+
+ public static > Range greaterThan(C lowerEndpoint)
+ {
+ return downTo(lowerEndpoint, BoundType.OPEN);
+ }
+
+ public static > Range atLeast(C lowerEndpoint)
+ {
+ return downTo(lowerEndpoint, BoundType.CLOSED);
+ }
+
+ public static > Range downTo(final C lowerEndpoint, final BoundType lowerBoundType)
+ {
+ return createRange(new Callable>() {
+ @Override
+ public Range call() throws Exception {
+ return Range.downTo(lowerEndpoint, lowerBoundType);
+ }
+ }, legacyDownToMethod, lowerEndpoint, lowerBoundType);
+ }
+
+ public static > Range lessThan(C upperEndpoint)
+ {
+ return upTo(upperEndpoint, BoundType.OPEN);
+ }
+
+ public static > Range atMost(C upperEndpoint)
+ {
+ return upTo(upperEndpoint, BoundType.CLOSED);
+ }
+
+ public static > Range upTo(final C upperEndpoint, final BoundType upperBoundType)
+ {
+ return createRange(new Callable>() {
+ @Override
+ public Range call() throws Exception {
+ return Range.upTo(upperEndpoint, upperBoundType);
+ }
+ }, legacyUpToMethod, upperEndpoint, upperBoundType);
+ }
+
+ public static > Range all()
+ {
+ return createRange(new Callable>() {
+ @Override
+ public Range call() throws Exception {
+ return Range.all();
+ }
+ }, legacyAllMethod);
+ }
+
+ public static > Range singleton(final C value)
+ {
+ return createRange(new Callable>() {
+ @Override
+ public Range call() throws Exception {
+ return Range.singleton(value);
+ }
+ }, legacyRangeMethod, value, BoundType.CLOSED, value, BoundType.CLOSED);
+ }
+
+ private static > Range createRange(Callable> 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 > Range invokeLegacyRangeFactoryMethod(Method method, Object... params)
+ {
+ try {
+ //noinspection unchecked
+ return (Range) 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/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaBeanSerializerModifier.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaBeanSerializerModifier.java
new file mode 100644
index 00000000..abc3a2a6
--- /dev/null
+++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaBeanSerializerModifier.java
@@ -0,0 +1,30 @@
+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;
+
+/**
+ * {@link BeanSerializerModifier} needed to sneak in handler to exclude "absent"
+ * optional values iff handling of "absent as nulls" is enabled.
+ */
+public class GuavaBeanSerializerModifier extends BeanSerializerModifier
+{
+ @Override
+ public List changeProperties(SerializationConfig config,
+ BeanDescription beanDesc,
+ List beanProperties)
+ {
+ for (int i = 0; i < beanProperties.size(); ++i) {
+ final BeanPropertyWriter writer = beanProperties.get(i);
+ if (Optional.class.isAssignableFrom(writer.getType().getRawClass())) {
+ beanProperties.set(i, new GuavaOptionalBeanPropertyWriter(writer));
+ }
+ }
+ return beanProperties;
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaOptionalBeanPropertyWriter.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaOptionalBeanPropertyWriter.java
new file mode 100644
index 00000000..98d3d61d
--- /dev/null
+++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaOptionalBeanPropertyWriter.java
@@ -0,0 +1,43 @@
+package com.fasterxml.jackson.datatype.guava.ser;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.PropertyName;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
+import com.fasterxml.jackson.databind.util.NameTransformer;
+import com.google.common.base.Optional;
+
+public class GuavaOptionalBeanPropertyWriter extends BeanPropertyWriter
+{
+ private static final long serialVersionUID = 1;
+
+ protected GuavaOptionalBeanPropertyWriter(BeanPropertyWriter base) {
+ super(base);
+ }
+
+ protected GuavaOptionalBeanPropertyWriter(BeanPropertyWriter base, PropertyName newName) {
+ super(base, newName);
+ }
+
+ @Override
+ protected BeanPropertyWriter _new(PropertyName newName) {
+ return new GuavaOptionalBeanPropertyWriter(this, newName);
+ }
+
+ @Override
+ public BeanPropertyWriter unwrappingWriter(NameTransformer unwrapper) {
+ return new GuavaUnwrappingOptionalBeanPropertyWriter(this, unwrapper);
+ }
+
+ @Override
+ public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception
+ {
+ if (_nullSerializer == null) {
+ Object value = get(bean);
+ if (value == null || Optional.absent().equals(value)) {
+ return;
+ }
+ }
+ super.serializeAsField(bean, gen, prov);
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaOptionalSerializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaOptionalSerializer.java
new file mode 100644
index 00000000..be24d788
--- /dev/null
+++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaOptionalSerializer.java
@@ -0,0 +1,347 @@
+package com.fasterxml.jackson.datatype.guava.ser;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.introspect.Annotated;
+import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+import com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import com.fasterxml.jackson.databind.type.ReferenceType;
+import com.fasterxml.jackson.databind.util.NameTransformer;
+import com.google.common.base.Optional;
+
+@SuppressWarnings("serial")
+public final class GuavaOptionalSerializer
+ extends StdSerializer>
+ implements ContextualSerializer
+{
+ /**
+ * Declared type parameter for Optional.
+ */
+ protected final JavaType _referredType;
+
+ protected final BeanProperty _property;
+
+ protected final TypeSerializer _valueTypeSerializer;
+ protected final JsonSerializer _valueSerializer;
+
+ /**
+ * Further guidance on serialization-inclusion (or not), regarding
+ * contained value (if any).
+ *
+ * @since 2.7
+ */
+ protected final JsonInclude.Include _contentInclusion;
+
+ /**
+ * To support unwrapped values of dynamic types, will need this:
+ */
+ protected final NameTransformer _unwrapper;
+
+ /**
+ * If element type can not be statically determined, mapping from
+ * runtime type to serializer is handled using this object
+ *
+ * @since 2.6
+ */
+ protected transient PropertySerializerMap _dynamicSerializers;
+
+ /*
+ /**********************************************************
+ /* Life-cycle
+ /**********************************************************
+ */
+
+ @SuppressWarnings("unchecked")
+ public GuavaOptionalSerializer(ReferenceType fullType,
+ TypeSerializer vts, JsonSerializer> valueSer)
+ {
+ super(fullType);
+ _referredType = fullType.getReferencedType();
+ _property = null;
+ _valueTypeSerializer = vts;
+ _valueSerializer = (JsonSerializer) valueSer;
+ _unwrapper = null;
+ _contentInclusion = null;
+ _dynamicSerializers = PropertySerializerMap.emptyForProperties();
+ }
+
+ @SuppressWarnings("unchecked")
+ protected GuavaOptionalSerializer(GuavaOptionalSerializer base,
+ BeanProperty property,
+ TypeSerializer vts, JsonSerializer> valueSer,
+ NameTransformer unwrapper, JsonInclude.Include contentIncl)
+ {
+ super(base);
+ _referredType = base._referredType;
+ _dynamicSerializers = base._dynamicSerializers;
+ _property = property;
+ _valueTypeSerializer = vts;
+ _valueSerializer = (JsonSerializer) valueSer;
+ _unwrapper = unwrapper;
+ if ((contentIncl == JsonInclude.Include.USE_DEFAULTS)
+ || (contentIncl == JsonInclude.Include.ALWAYS)) {
+ _contentInclusion = null;
+ } else {
+ _contentInclusion = contentIncl;
+ }
+ }
+
+ @Override
+ public JsonSerializer> unwrappingSerializer(NameTransformer transformer) {
+ JsonSerializer ser = _valueSerializer;
+ if (ser != null) {
+ ser = ser.unwrappingSerializer(transformer);
+ }
+ NameTransformer unwrapper = (_unwrapper == null) ? transformer
+ : NameTransformer.chainedTransformer(transformer, _unwrapper);
+ return withResolved(_property, _valueTypeSerializer, ser, unwrapper, _contentInclusion);
+ }
+
+ protected GuavaOptionalSerializer withResolved(BeanProperty prop,
+ TypeSerializer vts, JsonSerializer> valueSer,
+ NameTransformer unwrapper, JsonInclude.Include contentIncl)
+ {
+ if ((_property == prop) && (contentIncl == _contentInclusion)
+ && (_valueTypeSerializer == vts) && (_valueSerializer == valueSer)
+ && (_unwrapper == unwrapper)) {
+ return this;
+ }
+ return new GuavaOptionalSerializer(this, prop, vts, valueSer, unwrapper, contentIncl);
+ }
+
+ /*
+ /**********************************************************
+ /* Contextualization (support for property annotations)
+ /**********************************************************
+ */
+
+ @Override
+ public JsonSerializer> createContextual(SerializerProvider provider,
+ BeanProperty property) throws JsonMappingException
+ {
+ TypeSerializer vts = _valueTypeSerializer;
+ if (vts != null) {
+ vts = vts.forProperty(property);
+ }
+ JsonSerializer> ser = findContentSerializer(provider, property);
+ if (ser == null) {
+ ser = _valueSerializer;
+ if (ser == null) {
+ // A few conditions needed to be able to fetch serializer here:
+ if (_useStatic(provider, property, _referredType)) {
+ ser = _findSerializer(provider, _referredType, property);
+ }
+ } else {
+ ser = provider.handlePrimaryContextualization(ser, property);
+ }
+ }
+ // Also: may want to have more refined exclusion based on referenced value
+ JsonInclude.Include contentIncl = _contentInclusion;
+ if (property != null) {
+ JsonInclude.Value incl = property.findPropertyInclusion(provider.getConfig(),
+ Optional.class);
+ JsonInclude.Include newIncl = incl.getContentInclusion();
+ if ((newIncl != contentIncl) && (newIncl != JsonInclude.Include.USE_DEFAULTS)) {
+ contentIncl = newIncl;
+ }
+ }
+ return withResolved(property, vts, ser, _unwrapper, contentIncl);
+ }
+
+ protected boolean _useStatic(SerializerProvider provider, BeanProperty property,
+ JavaType referredType)
+ {
+ // First: no serializer for `Object.class`, must be dynamic
+ if (referredType.isJavaLangObject()) {
+ return false;
+ }
+ // but if type is final, might as well fetch
+ if (referredType.isFinal()) { // or should we allow annotation override? (only if requested...)
+ return true;
+ }
+ // also: if indicated by typing, should be considered static
+ if (referredType.useStaticType()) {
+ return true;
+ }
+ // if neither, maybe explicit annotation?
+ AnnotationIntrospector intr = provider.getAnnotationIntrospector();
+ if ((intr != null) && (property != null)) {
+ Annotated ann = property.getMember();
+ if (ann != null) {
+ JsonSerialize.Typing t = intr.findSerializationTyping(property.getMember());
+ if (t == JsonSerialize.Typing.STATIC) {
+ return true;
+ }
+ if (t == JsonSerialize.Typing.DYNAMIC) {
+ return false;
+ }
+ }
+ }
+ // and finally, may be forced by global static typing (unlikely...)
+ return provider.isEnabled(MapperFeature.USE_STATIC_TYPING);
+ }
+
+ /*
+ /**********************************************************
+ /* API overrides
+ /**********************************************************
+ */
+
+ @Override
+ public boolean isEmpty(SerializerProvider provider, Optional> value)
+ {
+ if ((value == null) || !value.isPresent()) {
+ return true;
+ }
+ if (_contentInclusion == null) {
+ return false;
+ }
+ Object contents = value.get();
+ JsonSerializer ser = _valueSerializer;
+ if (ser == null) {
+ try {
+ ser = _findCachedSerializer(provider, value.getClass());
+ } catch (JsonMappingException e) { // nasty but necessary
+ throw new RuntimeJsonMappingException(e);
+ }
+ }
+ return ser.isEmpty(provider, contents);
+ }
+
+ @Override
+ public boolean isUnwrappingSerializer() {
+ return (_unwrapper != null);
+ }
+
+ /*
+ /**********************************************************
+ /* Serialization methods
+ /**********************************************************
+ */
+
+ @Override
+ public void serialize(Optional> opt, JsonGenerator gen, SerializerProvider provider)
+ throws IOException
+ {
+ if (!opt.isPresent()) {
+ // 22-Oct-2015, tatu: With unwrapping we can not serialize value, just key/value pairs so:
+ if (_unwrapper == null) {
+ provider.defaultSerializeNull(gen);
+ }
+ return;
+ }
+ Object value = opt.get();
+ JsonSerializer ser = _valueSerializer;
+ if (ser == null) {
+ ser = _findCachedSerializer(provider, value.getClass());
+ }
+ if (_valueTypeSerializer != null) {
+ ser.serializeWithType(value, gen, provider, _valueTypeSerializer);
+ } else {
+ ser.serialize(value, gen, provider);
+ }
+ }
+
+ @Override
+ public void serializeWithType(Optional> opt,
+ JsonGenerator gen, SerializerProvider provider,
+ TypeSerializer typeSer) throws IOException
+ {
+ if (!opt.isPresent()) {
+ if (_unwrapper == null) {
+ provider.defaultSerializeNull(gen);
+ }
+ return;
+ }
+ // Otherwise apply type-prefix/suffix, then std serialize:
+ typeSer.writeTypePrefixForScalar(opt, gen, Optional.class);
+ serialize(opt, gen, provider);
+ typeSer.writeTypeSuffixForScalar(opt, gen);
+ }
+
+ /*
+ /**********************************************************
+ /* Introspection support
+ /**********************************************************
+ */
+
+ @Override
+ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException
+ {
+ JsonSerializer> ser = _valueSerializer;
+ if (ser == null) {
+ // 28-Sep-2015, tatu: as per [datatype-guava#83] need to ensure we don't
+ // accidentally drop parameterization
+ ser = _findSerializer(visitor.getProvider(), _referredType, _property);
+ if (_unwrapper != null) {
+ ser = ser.unwrappingSerializer(_unwrapper);
+ }
+ }
+ ser.acceptJsonFormatVisitor(visitor, _referredType);
+ }
+
+ /*
+ /**********************************************************
+ /* Misc other
+ /**********************************************************
+ */
+
+ /**
+ * Helper method that encapsulates logic of retrieving and caching required
+ * serializer.
+ */
+ protected final JsonSerializer _findCachedSerializer(SerializerProvider provider,
+ Class> type) throws JsonMappingException
+ {
+ JsonSerializer ser = _dynamicSerializers.serializerFor(type);
+ if (ser == null) {
+ ser = _findSerializer(provider, type, _property);
+ if (_unwrapper != null) {
+ ser = ser.unwrappingSerializer(_unwrapper);
+ }
+ _dynamicSerializers = _dynamicSerializers.newWith(type, ser);
+ }
+ return ser;
+ }
+
+ private final JsonSerializer _findSerializer(SerializerProvider provider,
+ Class> type, BeanProperty prop) throws JsonMappingException
+ {
+ // Important: ask for TYPED serializer, in case polymorphic handling is needed!
+ return provider.findTypedValueSerializer(type, true, prop);
+ }
+
+ private final JsonSerializer _findSerializer(SerializerProvider provider,
+ JavaType type, BeanProperty prop) throws JsonMappingException
+ {
+ // Important: ask for TYPED serializer, in case polymorphic handling is needed!
+ return provider.findTypedValueSerializer(type, true, prop);
+ }
+
+ // !!! TODO: added late in 2.7 in `jackson-databind`: remove from 2.8
+ protected JsonSerializer> findContentSerializer(SerializerProvider serializers,
+ BeanProperty property)
+ throws JsonMappingException
+ {
+ if (property != null) {
+ AnnotatedMember m = property.getMember();
+ final AnnotationIntrospector intr = serializers.getAnnotationIntrospector();
+ if (m != null) {
+ Object serDef = intr.findContentSerializer(m);
+ if (serDef != null) {
+ return serializers.serializerInstance(m, serDef);
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaUnwrappingOptionalBeanPropertyWriter.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaUnwrappingOptionalBeanPropertyWriter.java
new file mode 100644
index 00000000..5c1e3a30
--- /dev/null
+++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/GuavaUnwrappingOptionalBeanPropertyWriter.java
@@ -0,0 +1,42 @@
+package com.fasterxml.jackson.datatype.guava.ser;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.io.SerializedString;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
+import com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanPropertyWriter;
+import com.fasterxml.jackson.databind.util.NameTransformer;
+import com.google.common.base.Optional;
+
+public class GuavaUnwrappingOptionalBeanPropertyWriter extends UnwrappingBeanPropertyWriter
+{
+ private static final long serialVersionUID = 1L;
+
+ public GuavaUnwrappingOptionalBeanPropertyWriter(BeanPropertyWriter base,
+ NameTransformer transformer) {
+ super(base, transformer);
+ }
+
+ protected GuavaUnwrappingOptionalBeanPropertyWriter(UnwrappingBeanPropertyWriter base,
+ NameTransformer transformer, SerializedString name) {
+ super(base, transformer, name);
+ }
+
+ @Override
+ protected UnwrappingBeanPropertyWriter _new(NameTransformer transformer, SerializedString newName)
+ {
+ return new GuavaUnwrappingOptionalBeanPropertyWriter(this, transformer, newName);
+ }
+
+ @Override
+ public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception
+ {
+ if (_nullSerializer == null) {
+ Object value = get(bean);
+ if (value == null || Optional.absent().equals(value)) {
+ return;
+ }
+ }
+ super.serializeAsField(bean, gen, prov);
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/MultimapSerializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/MultimapSerializer.java
new file mode 100644
index 00000000..e2e6ed97
--- /dev/null
+++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/MultimapSerializer.java
@@ -0,0 +1,409 @@
+package com.fasterxml.jackson.datatype.guava.ser;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.Map.Entry;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonMapFormatVisitor;
+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.ser.PropertyFilter;
+import com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap;
+import com.fasterxml.jackson.databind.ser.std.MapProperty;
+import com.fasterxml.jackson.databind.type.MapLikeType;
+
+import com.google.common.collect.Multimap;
+
+/**
+ * Serializer for Guava's {@link Multimap} values. Output format encloses all
+ * value sets in JSON Array, regardless of number of values; this to reduce
+ * complexity (and inaccuracy) of trying to handle cases where values themselves
+ * would be serialized as arrays (in which cases determining whether given array
+ * is a wrapper or value gets complicated and unreliable).
+ *
+ * Missing features, compared to standard Java Maps:
+ *
+ * Inclusion checks for content entries (non-null, non-empty)
+ *
+ * Sorting of entries
+ *
+ *
+ */
+public class MultimapSerializer
+ extends ContainerSerializer>
+ implements ContextualSerializer
+{
+ private static final long serialVersionUID = 1L;
+
+ private final MapLikeType _type;
+ private final BeanProperty _property;
+ private final JsonSerializer _keySerializer;
+ private final TypeSerializer _valueTypeSerializer;
+ private final JsonSerializer _valueSerializer;
+
+ /**
+ * Set of entries to omit during serialization, if any
+ *
+ * @since 2.5
+ */
+ protected final Set _ignoredEntries;
+
+ /**
+ * If value type can not be statically determined, mapping from
+ * runtime value types to serializers are stored in this object.
+ *
+ * @since 2.5
+ */
+ protected PropertySerializerMap _dynamicValueSerializers;
+
+ /**
+ * Id of the property filter to use, if any; null if none.
+ *
+ * @since 2.5
+ */
+ protected final Object _filterId;
+
+ /**
+ * Flag set if output is forced to be sorted by keys (usually due
+ * to annotation).
+ *
+ * NOTE: not yet used.
+ *
+ * @since 2.5
+ */
+ protected final boolean _sortKeys;
+
+ public MultimapSerializer(MapLikeType type, BeanDescription beanDesc,
+ JsonSerializer keySerializer, TypeSerializer vts, JsonSerializer valueSerializer,
+ Set ignoredEntries, Object filterId)
+ {
+ super(type.getRawClass(), false);
+ _type = type;
+ _property = null;
+ _keySerializer = keySerializer;
+ _valueTypeSerializer = vts;
+ _valueSerializer = valueSerializer;
+ _ignoredEntries = ignoredEntries;
+ _filterId = filterId;
+ _sortKeys = false;
+
+ _dynamicValueSerializers = PropertySerializerMap.emptyForProperties();
+ }
+
+ /**
+ * @since 2.5
+ */
+ @SuppressWarnings("unchecked")
+ protected MultimapSerializer(MultimapSerializer src, BeanProperty property,
+ JsonSerializer> keySerializer, TypeSerializer vts, JsonSerializer> valueSerializer,
+ Set ignoredEntries, Object filterId, boolean sortKeys)
+ {
+ super(src);
+ _type = src._type;
+ _property = property;
+ _keySerializer = (JsonSerializer) keySerializer;
+ _valueTypeSerializer = vts;
+ _valueSerializer = (JsonSerializer) valueSerializer;
+ _dynamicValueSerializers = src._dynamicValueSerializers;
+ _ignoredEntries = ignoredEntries;
+ _filterId = filterId;
+ _sortKeys = sortKeys;
+ }
+
+ protected MultimapSerializer withResolved(BeanProperty property,
+ JsonSerializer> keySer, TypeSerializer vts, JsonSerializer> valueSer,
+ Set ignored, Object filterId, boolean sortKeys)
+ {
+ return new MultimapSerializer(this, property, keySer, vts, valueSer,
+ ignored, filterId, sortKeys);
+ }
+
+ @Override
+ protected ContainerSerializer> _withValueTypeSerializer(TypeSerializer typeSer) {
+ return new MultimapSerializer(this, _property, _keySerializer,
+ typeSer, _valueSerializer, _ignoredEntries, _filterId, _sortKeys);
+ }
+
+ /*
+ /**********************************************************
+ /* 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);
+ }
+
+ final AnnotationIntrospector intr = provider.getAnnotationIntrospector();
+ final AnnotatedMember propertyAcc = (property == null) ? null : property.getMember();
+ JsonSerializer> keySer = null;
+ Object filterId = _filterId;
+
+ // First: if we have a property, may have property-annotation overrides
+ if (propertyAcc != null && intr != null) {
+ Object serDef = intr.findKeySerializer(propertyAcc);
+ if (serDef != null) {
+ keySer = provider.serializerInstance(propertyAcc, serDef);
+ }
+ serDef = intr.findContentSerializer(propertyAcc);
+ if (serDef != null) {
+ valueSer = provider.serializerInstance(propertyAcc, serDef);
+ }
+ filterId = intr.findFilterId(propertyAcc);
+ }
+ if (valueSer == null) {
+ valueSer = _valueSerializer;
+ }
+ // [datatype-guava#124]: May have a content converter
+ valueSer = findConvertingContentSerializer(provider, property, valueSer);
+ if (valueSer == null) {
+ // One more thing -- if explicit content type is annotated,
+ // we can consider it a static case as well.
+ JavaType valueType = _type.getContentType();
+ if (valueType.useStaticType()) {
+ valueSer = provider.findValueSerializer(valueType, property);
+ }
+ } else {
+ valueSer = provider.handleSecondaryContextualization(valueSer, property);
+ }
+ if (keySer == null) {
+ keySer = _keySerializer;
+ }
+ if (keySer == null) {
+ keySer = provider.findKeySerializer(_type.getKeyType(), property);
+ } else {
+ keySer = provider.handleSecondaryContextualization(keySer, property);
+ }
+ // finally, TypeSerializers may need contextualization as well
+ TypeSerializer typeSer = _valueTypeSerializer;
+ if (typeSer != null) {
+ typeSer = typeSer.forProperty(property);
+ }
+
+ Set ignored = _ignoredEntries;
+ boolean sortKeys = false;
+ if (intr != null && propertyAcc != null) {
+ String[] moreToIgnore = intr.findPropertiesToIgnore(propertyAcc, true);
+ if (moreToIgnore != null) {
+ ignored = (ignored == null) ? new HashSet() : new HashSet(ignored);
+ for (String str : moreToIgnore) {
+ ignored.add(str);
+ }
+ }
+ Boolean b = intr.findSerializationSortAlphabetically(propertyAcc);
+ sortKeys = (b != null) && b.booleanValue();
+ }
+ return withResolved(property, keySer, typeSer, valueSer,
+ ignored, filterId, sortKeys);
+ }
+
+ /*
+ /**********************************************************
+ /* 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
+ @Deprecated // since 2.5
+ public boolean isEmpty(Multimap,?> value) {
+ return isEmpty(null, value);
+ }
+
+ @Override
+ public boolean isEmpty(SerializerProvider prov, Multimap,?> value) {
+ return value.isEmpty();
+ }
+
+ /*
+ /**********************************************************
+ /* Post-processing (contextualization)
+ /**********************************************************
+ */
+
+ @Override
+ public void serialize(Multimap, ?> value, JsonGenerator gen, SerializerProvider provider)
+ throws IOException
+ {
+ gen.writeStartObject();
+ // [databind#631]: Assign current value, to be accessible by custom serializers
+ gen.setCurrentValue(value);
+ if (!value.isEmpty()) {
+ if (_filterId != null) {
+ serializeOptionalFields(value, gen, provider);
+ } else {
+ serializeFields(value, gen, provider);
+ }
+ }
+ gen.writeEndObject();
+ }
+
+ @Override
+ public void serializeWithType(Multimap,?> value, JsonGenerator gen, SerializerProvider provider,
+ TypeSerializer typeSer)
+ throws IOException
+ {
+ typeSer.writeTypePrefixForObject(value, gen);
+ gen.setCurrentValue(value);
+ if (!value.isEmpty()) {
+ if (_filterId != null) {
+ serializeOptionalFields(value, gen, provider);
+ } else {
+ serializeFields(value, gen, provider);
+ }
+ }
+ typeSer.writeTypeSuffixForObject(value, gen);
+ }
+
+ private final void serializeFields(Multimap, ?> mmap, JsonGenerator gen, SerializerProvider provider)
+ throws IOException
+ {
+ final Set ignored = _ignoredEntries;
+ PropertySerializerMap serializers = _dynamicValueSerializers;
+ for (Entry, ? extends Collection>> entry : mmap.asMap().entrySet()) {
+ // First, serialize key
+ Object key = entry.getKey();
+ if ((ignored != null) && ignored.contains(key)) {
+ continue;
+ }
+ if (key == null) {
+ provider.findNullKeySerializer(_type.getKeyType(), _property)
+ .serialize(null, gen, provider);
+ } else {
+ _keySerializer.serialize(key, gen, provider);
+ }
+ // note: value is a List, but generic type is for contents... so:
+ gen.writeStartArray();
+ for (Object vv : entry.getValue()) {
+ if (vv == null) {
+ provider.defaultSerializeNull(gen);
+ continue;
+ }
+ JsonSerializer valueSer = _valueSerializer;
+ if (valueSer == null) {
+ Class> cc = vv.getClass();
+ valueSer = serializers.serializerFor(cc);
+ if (valueSer == null) {
+ valueSer = _findAndAddDynamic(serializers, cc, provider);
+ serializers = _dynamicValueSerializers;
+ }
+ }
+ if (_valueTypeSerializer == null) {
+ valueSer.serialize(vv, gen, provider);
+ } else {
+ valueSer.serializeWithType(vv, gen, provider, _valueTypeSerializer);
+ }
+ }
+ gen.writeEndArray();
+ }
+ }
+
+ private final void serializeOptionalFields(Multimap, ?> mmap, JsonGenerator gen, SerializerProvider provider)
+ throws IOException
+ {
+ final Set ignored = _ignoredEntries;
+ PropertyFilter filter = findPropertyFilter(provider, _filterId, mmap);
+ final MapProperty prop = new MapProperty(_valueTypeSerializer, _property);
+ for (Entry, ? extends Collection>> entry : mmap.asMap().entrySet()) {
+ // First, serialize key
+ Object key = entry.getKey();
+ if ((ignored != null) && ignored.contains(key)) {
+ continue;
+ }
+ Collection> value = entry.getValue();
+ JsonSerializer valueSer;
+ if (value == null) {
+ // !!! TODO: null suppression?
+ valueSer = provider.getDefaultNullValueSerializer();
+ } else {
+ valueSer = _valueSerializer;
+ }
+ prop.reset(key, _keySerializer, valueSer);
+ try {
+ filter.serializeAsField(value, gen, provider, prop);
+ } catch (Exception e) {
+ String keyDesc = ""+key;
+ wrapAndThrow(provider, e, value, keyDesc);
+ }
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Schema related functionality
+ /**********************************************************
+ */
+
+ @Override
+ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
+ throws JsonMappingException
+ {
+ JsonMapFormatVisitor v2 = (visitor == null) ? null : visitor.expectMapFormat(typeHint);
+ if (v2 != null) {
+ v2.keyFormat(_keySerializer, _type.getKeyType());
+ JsonSerializer> valueSer = _valueSerializer;
+ JavaType vt = _type.getContentType();
+ if (valueSer == null) {
+ valueSer = _findAndAddDynamic(_dynamicValueSerializers,
+ vt, visitor.getProvider());
+ }
+ v2.valueFormat(valueSer, vt);
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Internal helper methods
+ /**********************************************************
+ */
+
+ protected final JsonSerializer _findAndAddDynamic(PropertySerializerMap map,
+ Class> type, SerializerProvider provider) throws JsonMappingException
+ {
+ PropertySerializerMap.SerializerAndMapResult result = map.findAndAddSecondarySerializer(type, provider, _property);
+ // did we get a new map of serializers? If so, start using it
+ if (map != result.map) {
+ _dynamicValueSerializers = result.map;
+ }
+ return result.serializer;
+ }
+
+ protected final JsonSerializer _findAndAddDynamic(PropertySerializerMap map,
+ JavaType type, SerializerProvider provider) throws JsonMappingException
+ {
+ PropertySerializerMap.SerializerAndMapResult result = map.findAndAddSecondarySerializer(type, provider, _property);
+ if (map != result.map) {
+ _dynamicValueSerializers = result.map;
+ }
+ return result.serializer;
+ }
+}
diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/RangeSerializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/RangeSerializer.java
new file mode 100644
index 00000000..cf653312
--- /dev/null
+++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/RangeSerializer.java
@@ -0,0 +1,149 @@
+package com.fasterxml.jackson.datatype.guava.ser;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.*;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
+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.BoundType;
+import com.google.common.collect.Range;
+
+/**
+ * Jackson serializer for a Guava {@link Range}.
+ */
+@SuppressWarnings("serial")
+public class RangeSerializer extends StdSerializer>
+ implements ContextualSerializer
+{
+ protected final JavaType _rangeType;
+
+ protected final JsonSerializer _endpointSerializer;
+
+ /*
+ /**********************************************************
+ /* Life-cycle
+ /**********************************************************
+ */
+
+ public RangeSerializer(JavaType type) { this(type, null); }
+
+ @SuppressWarnings("unchecked")
+ public RangeSerializer(JavaType type, JsonSerializer> endpointSer)
+ {
+ super(type);
+ _rangeType = type;
+ _endpointSerializer = (JsonSerializer) endpointSer;
+ }
+
+ @Override
+ @Deprecated
+ public boolean isEmpty(Range> value) {
+ return isEmpty(null, value);
+ }
+
+ @Override
+ public boolean isEmpty(SerializerProvider prov, Range> value) {
+ return value.isEmpty();
+ }
+
+ @Override
+ public JsonSerializer> createContextual(SerializerProvider prov,
+ BeanProperty property) throws JsonMappingException
+ {
+ if (_endpointSerializer == null) {
+ JavaType endpointType = _rangeType.containedTypeOrUnknown(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);
+ }
+ /* 21-Sep-2014, tatu: Need to make sure all serializers get proper contextual
+ * access, in case they rely on annotations on properties... (or, more generally,
+ * in getting a chance to be contextualized)
+ */
+ } else if (_endpointSerializer instanceof ContextualSerializer) {
+ JsonSerializer> cs = ((ContextualSerializer)_endpointSerializer).createContextual(prov, property);
+ if (cs != _endpointSerializer) {
+ return new RangeSerializer(_rangeType, cs);
+ }
+ }
+ return this;
+ }
+
+ /*
+ /**********************************************************
+ /* Serialization methods
+ /**********************************************************
+ */
+
+ @Override
+ public void serialize(Range> value, JsonGenerator gen, SerializerProvider provider)
+ throws IOException, JsonGenerationException
+ {
+ gen.writeStartObject();
+ _writeContents(value, gen, provider);
+ gen.writeEndObject();
+
+ }
+
+ @Override
+ public void serializeWithType(Range> value, JsonGenerator gen, SerializerProvider provider,
+ TypeSerializer typeSer)
+ throws IOException, JsonProcessingException
+ {
+ // Will be serialized as a JSON Object, so:
+ typeSer.writeTypePrefixForObject(value, gen);
+ _writeContents(value, gen, provider);
+ typeSer.writeTypeSuffixForObject(value, gen);
+ }
+
+ @Override
+ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
+ throws JsonMappingException
+ {
+ if (visitor != null) {
+ JsonObjectFormatVisitor objectVisitor = visitor.expectObjectFormat(typeHint);
+ if (objectVisitor != null) {
+ if (_endpointSerializer != null) {
+ JavaType endpointType = _rangeType.containedType(0);
+ JavaType btType = visitor.getProvider().constructType(BoundType.class);
+ JsonSerializer> btSer = visitor.getProvider()
+ .findValueSerializer(btType, null);
+ objectVisitor.property("lowerEndpoint", _endpointSerializer, endpointType);
+ objectVisitor.property("lowerBoundType", btSer, btType);
+ objectVisitor.property("upperEndpoint", _endpointSerializer, endpointType);
+ objectVisitor.property("upperBoundType", btSer, btType);
+ }
+ }
+ }
+ }
+
+ 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/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/TableSerializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/TableSerializer.java
new file mode 100644
index 00000000..13726f20
--- /dev/null
+++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/TableSerializer.java
@@ -0,0 +1,233 @@
+package com.fasterxml.jackson.datatype.guava.ser;
+
+import java.io.IOException;
+import java.util.Map;
+
+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.ser.std.MapSerializer;
+import com.fasterxml.jackson.databind.type.MapType;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
+import com.google.common.collect.Table;
+
+/**
+ * @author stevenmhood (via hyandell) - Initial implementation
+ * @author tatu - Some refactoring to streamline code
+ */
+public class TableSerializer
+ extends ContainerSerializer> implements ContextualSerializer
+{
+ private static final long serialVersionUID = -1449462718192917949L;
+
+ /**
+ * Type declaration that defines parameters; may be a supertype of actual
+ * type of property being serialized.
+ */
+ private final JavaType _type;
+
+ private final BeanProperty _property;
+
+ private final JsonSerializer _rowSerializer;
+ private final JsonSerializer _columnSerializer;
+ private final TypeSerializer _valueTypeSerializer;
+ private final JsonSerializer _valueSerializer;
+
+ private final MapSerializer _rowMapSerializer;
+
+ /*
+ /**********************************************************
+ /* Serializer lifecycle
+ /**********************************************************
+ */
+
+ public TableSerializer(final JavaType type)
+ {
+ super(type);
+ _type = type;
+ _property = null;
+ _rowSerializer = null;
+ _columnSerializer = null;
+ _valueTypeSerializer = null;
+ _valueSerializer = null;
+
+ _rowMapSerializer = null;
+ }
+
+ @SuppressWarnings( "unchecked" )
+ protected TableSerializer(final TableSerializer src,
+ final BeanProperty property,
+ final TypeFactory typeFactory,
+ final JsonSerializer> rowKeySerializer,
+ final JsonSerializer> columnKeySerializer,
+ final TypeSerializer valueTypeSerializer,
+ final JsonSerializer> valueSerializer)
+ {
+ super(src);
+ _type = src._type;
+ _property = property;
+ _rowSerializer = (JsonSerializer) rowKeySerializer;
+ _columnSerializer = (JsonSerializer) columnKeySerializer;
+ _valueTypeSerializer = valueTypeSerializer;
+ _valueSerializer = (JsonSerializer) valueSerializer;
+
+ final MapType columnAndValueType = typeFactory.constructMapType(Map.class,
+ _type.containedTypeOrUnknown(1), _type.containedTypeOrUnknown(2));
+ JsonSerializer> columnAndValueSerializer =
+ MapSerializer.construct(null,
+ columnAndValueType,
+ false,
+ _valueTypeSerializer,
+ _columnSerializer,
+ _valueSerializer,
+ null);
+
+ final MapType rowMapType = typeFactory.constructMapType(Map.class,
+ _type.containedTypeOrUnknown(0), columnAndValueType);
+ _rowMapSerializer =
+ MapSerializer.construct(null,
+ rowMapType,
+ false,
+ null,
+ _rowSerializer,
+ (JsonSerializer) columnAndValueSerializer,
+ null);
+ }
+
+ protected TableSerializer(final TableSerializer src, TypeSerializer typeSer)
+ {
+ super(src);
+ _type = src._type;
+ _property = src._property;
+ _rowSerializer = src._rowSerializer;
+ _columnSerializer = src._columnSerializer;
+ _valueTypeSerializer = typeSer;
+ _valueSerializer = src._valueSerializer;
+
+ _rowMapSerializer = src._rowMapSerializer;
+
+ }
+
+ protected TableSerializer withResolved(final BeanProperty property,
+ final TypeFactory typeFactory,
+ final JsonSerializer> rowKeySer,
+ final JsonSerializer> columnKeySer,
+ final TypeSerializer vts,
+ final JsonSerializer> valueSer )
+ {
+ return new TableSerializer(this, property, typeFactory,
+ rowKeySer, columnKeySer, vts, valueSer);
+ }
+
+ @Override
+ protected ContainerSerializer> _withValueTypeSerializer(final TypeSerializer typeSer)
+ {
+ return new TableSerializer(this, typeSer);
+ }
+
+ @Override
+ public JsonSerializer> createContextual(final SerializerProvider provider, final BeanProperty property ) throws JsonMappingException
+ {
+ JsonSerializer> valueSer = _valueSerializer;
+ if (valueSer == null) { // if type is final, can actually resolve:
+ final JavaType valueType = _type.containedTypeOrUnknown(2);
+ if (valueType.isFinal()) {
+ valueSer = provider.findValueSerializer(valueType, property);
+ }
+ }
+ else if (valueSer instanceof ContextualSerializer) {
+ valueSer = ((ContextualSerializer) valueSer).createContextual(provider, property);
+ }
+ JsonSerializer> rowKeySer = _rowSerializer;
+ if (rowKeySer == null) {
+ rowKeySer = provider.findKeySerializer(_type.containedTypeOrUnknown(0), property);
+ }
+ else if (rowKeySer instanceof ContextualSerializer) {
+ rowKeySer = ((ContextualSerializer) rowKeySer).createContextual(provider, property);
+ }
+ JsonSerializer> columnKeySer = _columnSerializer;
+ if (columnKeySer == null) {
+ columnKeySer = provider.findKeySerializer(_type.containedTypeOrUnknown(1), property);
+ }
+ else if (columnKeySer instanceof ContextualSerializer) {
+ columnKeySer = ((ContextualSerializer) columnKeySer).createContextual(provider, property);
+ }
+ // finally, TypeSerializers may need contextualization as well
+ TypeSerializer typeSer = _valueTypeSerializer;
+ if (typeSer != null) {
+ typeSer = typeSer.forProperty(property);
+ }
+ return withResolved(property, provider.getTypeFactory(), rowKeySer, columnKeySer, typeSer, valueSer);
+ }
+
+ /*
+ /**********************************************************
+ /* Simple accessor API
+ /**********************************************************
+ */
+
+ @Override
+ public JavaType getContentType() {
+ return _type.getContentType();
+ }
+
+ @Override
+ public JsonSerializer> getContentSerializer() {
+ return _valueSerializer;
+ }
+
+ @Override
+ public boolean isEmpty(SerializerProvider provider, Table, ?, ?> table) {
+ return table.isEmpty();
+ }
+
+ @Override
+ @Deprecated // since 2.6, remove from 2.8
+ public boolean isEmpty(Table, ?, ?> table) {
+ return table.isEmpty();
+ }
+
+ @Override
+ public boolean hasSingleElement(final Table, ?, ?> table) {
+ return table.size() == 1;
+ }
+
+ /*
+ /**********************************************************
+ /* Main serialization methods
+ /**********************************************************
+ */
+
+ @Override
+ public void serialize(final Table, ?, ?> value,
+ final JsonGenerator gen, final SerializerProvider provider)
+ throws IOException
+ {
+ gen.writeStartObject();
+ if ( !value.isEmpty()) {
+ serializeFields(value, gen, provider);
+ }
+ gen.writeEndObject();
+ }
+
+ @Override
+ public void serializeWithType(final Table, ?, ?> value,
+ final JsonGenerator gen,
+ final SerializerProvider provider,
+ final TypeSerializer typeSer) throws IOException
+ {
+ typeSer.writeTypePrefixForObject(value, gen);
+ serializeFields(value, gen, provider);
+ typeSer.writeTypeSuffixForObject(value, gen);
+ }
+
+ private final void serializeFields( final Table, ?, ?> table, final JsonGenerator jgen, final SerializerProvider provider )
+ throws IOException
+ {
+ _rowMapSerializer.serializeFields(table.rowMap(), jgen, provider);
+ }
+}
diff --git a/guava/src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module b/guava/src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module
new file mode 100644
index 00000000..047e1f8b
--- /dev/null
+++ b/guava/src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module
@@ -0,0 +1 @@
+com.fasterxml.jackson.datatype.guava.GuavaModule
diff --git a/guava/src/test/java/com/fasterxml/jackson/datatype/guava/FluentIterableTest.java b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/FluentIterableTest.java
new file mode 100644
index 00000000..c6e3aa6b
--- /dev/null
+++ b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/FluentIterableTest.java
@@ -0,0 +1,46 @@
+package com.fasterxml.jackson.datatype.guava;
+
+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();
+
+ public static class FluentHolder {
+ public final Iterable value = createFluentIterable();
+ }
+
+ static FluentIterable createFluentIterable() {
+ return FluentIterable.from(Sets.newHashSet(1, 2, 3));
+ }
+
+ /**
+ * 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();
+ FluentHolder holder = new FluentHolder();
+ String json = mapper.writeValueAsString(holder);
+ assertEquals("{\"value\":{\"empty\":false}}", json);
+ }
+
+ public void testSerialization() throws Exception {
+ String json = MAPPER.writeValueAsString(createFluentIterable());
+ assertEquals("[1,2,3]", json);
+ }
+
+ public void testWrappedSerialization() throws Exception {
+ FluentHolder holder = new FluentHolder();
+ String json = MAPPER.writeValueAsString(holder);
+ assertEquals("{\"value\":[1,2,3]}", json);
+ }
+
+}
diff --git a/guava/src/test/java/com/fasterxml/jackson/datatype/guava/HashCodeTest.java b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/HashCodeTest.java
new file mode 100644
index 00000000..c4271b54
--- /dev/null
+++ b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/HashCodeTest.java
@@ -0,0 +1,32 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.hash.HashCode;
+
+public class HashCodeTest extends ModuleTestBase
+{
+ private final ObjectMapper MAPPER = mapperWithModule();
+
+ public void testSerialization() throws Exception
+ {
+ HashCode input = HashCode.fromString("cafebabe12345678");
+ String json = MAPPER.writeValueAsString(input);
+ assertEquals("\"cafebabe12345678\"", json);
+ }
+
+ public void testDeserialization() throws Exception
+ {
+ // success:
+ HashCode result = MAPPER.readValue(quote("0123456789cAfEbAbE"), HashCode.class);
+ assertEquals("0123456789cafebabe", result.toString());
+
+ // and ... error:
+ try {
+ result = MAPPER.readValue(quote("ghijklmn0123456789"), HashCode.class);
+ fail("Should not deserialize from non-hex string: got "+result);
+ } catch (JsonProcessingException e) {
+ verifyException(e, "Illegal hexadecimal character");
+ }
+ }
+}
diff --git a/guava/src/test/java/com/fasterxml/jackson/datatype/guava/HostAndPortTest.java b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/HostAndPortTest.java
new file mode 100644
index 00000000..e409d1c3
--- /dev/null
+++ b/guava/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 (note: numbers, booleans may all be fine)
+ try {
+ result = MAPPER.readValue("[ ]", HostAndPort.class);
+ fail("Should not deserialize from boolean: got "+result);
+ } catch (JsonProcessingException e) {
+ verifyException(e, "Can not deserialize");
+ }
+ }
+}
diff --git a/guava/src/test/java/com/fasterxml/jackson/datatype/guava/IterablesTest.java b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/IterablesTest.java
new file mode 100644
index 00000000..ddd2e77f
--- /dev/null
+++ b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/IterablesTest.java
@@ -0,0 +1,50 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class IterablesTest extends ModuleTestBase
+{
+ private final ObjectMapper MAPPER = mapperWithModule();
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ static class IterableWrapper {
+ public Iterable values;
+
+ public IterableWrapper(Iterable v) { values = v; }
+ }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ public void testIterablesSerialization() throws Exception
+ {
+ String json = MAPPER.writeValueAsString(Iterables.limit(Iterables.cycle(1,2,3), 3));
+ assertNotNull(json);
+ assertEquals("[1,2,3]", json);
+ }
+
+ // for [#60]
+ public void testIterablesWithTransform() throws Exception
+ {
+ Iterable input = Iterables.transform(ImmutableList.of("mr", "bo", "jangles"),
+ new Function() {
+ @Override
+ public String apply(String x) {
+ return new StringBuffer(x).reverse().toString();
+ }
+ });
+ String json = MAPPER.writeValueAsString(input);
+ assertEquals(aposToQuotes("['rm','ob','selgnaj']"), json);
+
+ // and then as property?
+ json = MAPPER.writeValueAsString(new IterableWrapper(input));
+ assertEquals(aposToQuotes("{'values':['rm','ob','selgnaj']}"), json);
+ }
+}
diff --git a/guava/src/test/java/com/fasterxml/jackson/datatype/guava/ModuleTestBase.java b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/ModuleTestBase.java
new file mode 100644
index 00000000..a174e2f9
--- /dev/null
+++ b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/ModuleTestBase.java
@@ -0,0 +1,46 @@
+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() {
+ return mapperWithModule(false);
+ }
+
+ protected ObjectMapper mapperWithModule(boolean absentsAsNulls)
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ GuavaModule module = new GuavaModule();
+ module.configureAbsentsAsNulls(absentsAsNulls);
+ mapper.registerModule(module);
+ 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/guava/src/test/java/com/fasterxml/jackson/datatype/guava/ScalarTypesTest.java b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/ScalarTypesTest.java
new file mode 100644
index 00000000..9835e993
--- /dev/null
+++ b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/ScalarTypesTest.java
@@ -0,0 +1,25 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.net.InternetDomainName;
+
+public class ScalarTypesTest extends ModuleTestBase
+{
+ private final ObjectMapper MAPPER = mapperWithModule();
+
+ public void testInternetDomainNameSerialization() throws Exception
+ {
+ final String INPUT = "google.com";
+ InternetDomainName name = InternetDomainName.from(INPUT);
+ assertEquals(quote(INPUT), MAPPER.writeValueAsString(name));
+ }
+
+ public void testInternetDomainNameDeserialization() throws Exception
+ {
+ final String INPUT = "google.com";
+// InternetDomainName name = MAPPER.readValue(quote(INPUT), InternetDomainName.class);
+ InternetDomainName name = new ObjectMapper().readValue(quote(INPUT), InternetDomainName.class);
+ assertNotNull(name);
+ assertEquals(INPUT, name.toString());
+ }
+}
diff --git a/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TableSerializationTest.java b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TableSerializationTest.java
new file mode 100644
index 00000000..c1c9af62
--- /dev/null
+++ b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TableSerializationTest.java
@@ -0,0 +1,165 @@
+package com.fasterxml.jackson.datatype.guava;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+import com.google.common.collect.ImmutableTable;
+
+public class TableSerializationTest extends ModuleTestBase
+{
+ private final ObjectMapper MAPPER = mapperWithModule(false);
+ {
+ MAPPER.registerModule(new ComplexKeyModule());
+ }
+
+ static class ComplexKeyModule extends SimpleModule
+ {
+ private static final long serialVersionUID = 1L;
+
+ public ComplexKeyModule()
+ {
+ this.addKeySerializer(ComplexKey.class, new JsonSerializer() {
+ @Override
+ public void serialize( final ComplexKey value, final JsonGenerator jgen, final SerializerProvider provider )
+ throws IOException, JsonProcessingException
+ {
+ jgen.writeFieldName(value.getKey1() + ":" + value.getKey2());
+ }
+ });
+
+ this.addKeyDeserializer(ComplexKey.class, new KeyDeserializer() {
+ @Override
+ public Object deserializeKey( final String key, final DeserializationContext ctxt ) throws IOException, JsonProcessingException
+ {
+ final String[] split = key.split(":");
+ return new ComplexKey(split[0], split[1]);
+ }
+ });
+ }
+ }
+
+ static class ComplexKey
+ {
+ private String key1;
+ private String key2;
+
+ public ComplexKey( final String key1, final String key2 )
+ {
+ super();
+ this.key1 = key1;
+ this.key2 = key2;
+ }
+
+ public String getKey1()
+ {
+ return this.key1;
+ }
+
+ public void setKey1( final String key1 )
+ {
+ this.key1 = key1;
+ }
+
+ public String getKey2()
+ {
+ return this.key2;
+ }
+
+ public void setKey2( final String key2 )
+ {
+ this.key2 = key2;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((this.key1 == null) ? 0 : this.key1.hashCode());
+ result = prime * result + ((this.key2 == null) ? 0 : this.key2.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals( final Object obj )
+ {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if ( !(obj instanceof ComplexKey)) {
+ return false;
+ }
+ final ComplexKey other = (ComplexKey) obj;
+ if (this.key1 == null) {
+ if (other.key1 != null) {
+ return false;
+ }
+ }
+ else if ( !this.key1.equals(other.key1)) {
+ return false;
+ }
+ if (this.key2 == null) {
+ if (other.key2 != null) {
+ return false;
+ }
+ }
+ else if ( !this.key2.equals(other.key2)) {
+ return false;
+ }
+ return true;
+ }
+
+ }
+
+ public void testSimpleKeyImmutableTableSerde() throws IOException
+ {
+ final ImmutableTable.Builder builder = ImmutableTable.builder();
+ builder.put(Integer.valueOf(42), "column42", "some value 42");
+ builder.put(Integer.valueOf(45), "column45", "some value 45");
+ final ImmutableTable simpleTable = builder.build();
+
+ final String simpleJson = MAPPER.writeValueAsString(simpleTable);
+ assertEquals("{\"42\":{\"column42\":\"some value 42\"},\"45\":{\"column45\":\"some value 45\"}}", simpleJson);
+
+ // !!! TODO: support deser
+
+ /*
+ final ImmutableTable reconstitutedTable =
+ this.MAPPER.readValue(simpleJson, new TypeReference>() {});
+ assertEquals(simpleTable, reconstitutedTable);
+ */
+ }
+
+ /**
+ * This test illustrates one way to use objects as keys in Tables.
+ */
+ public void testComplexKeyImmutableTableSerde() throws IOException
+ {
+ final ImmutableTable.Builder ckBuilder = ImmutableTable.builder();
+ ckBuilder.put(Integer.valueOf(42), new ComplexKey("field1", "field2"), "some value 42");
+ ckBuilder.put(Integer.valueOf(45), new ComplexKey("field1", "field2"), "some value 45");
+ final ImmutableTable complexKeyTable = ckBuilder.build();
+
+ final TypeReference> tableType = new TypeReference>()
+ {};
+
+ final String ckJson = this.MAPPER.writerFor(tableType).writeValueAsString(complexKeyTable);
+ assertEquals("{\"42\":{\"field1:field2\":\"some value 42\"},\"45\":{\"field1:field2\":\"some value 45\"}}", ckJson);
+
+ // !!! TODO: support deser
+/*
+
+ final ImmutableTable reconstitutedTable = this.MAPPER.readValue(ckJson, tableType);
+ assertEquals(complexKeyTable, reconstitutedTable);
+ */
+ }
+}
diff --git a/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TestImmutables.java b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TestImmutables.java
new file mode 100644
index 00000000..074c858d
--- /dev/null
+++ b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TestImmutables.java
@@ -0,0 +1,244 @@
+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 list = ImmutableList.builder()
+ .add(1).add(2).add(3).build();
+ assertEquals("[1,2,3]", MAPPER.writeValueAsString(list));
+
+ ImmutableSet set = ImmutableSet.builder()
+ .add("abc").add("def").build();
+ assertEquals("[\"abc\",\"def\"]", MAPPER.writeValueAsString(set));
+
+ ImmutableMap map = ImmutableMap.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>() { });
+ fail("Expected failure for missing deserializer");
+ } catch (JsonMappingException e) {
+ verifyException(e, "can not find a deserializer");
+ }
+
+ try {
+ mapper.readValue("[1,2,3]", new TypeReference>() { });
+ fail("Expected failure for missing deserializer");
+ } catch (JsonMappingException e) {
+ verifyException(e, "can not find a deserializer");
+ }
+
+ try {
+ mapper.readValue("[1,2,3]", new TypeReference>() { });
+ 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>() { });
+ 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 list = MAPPER.readValue("[1,2,3]", new TypeReference>() { });
+ 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 set = MAPPER.readValue("[3,7,8]",
+ new TypeReference>() { });
+ assertEquals(3, set.size());
+ Iterator 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>() { });
+ assertEquals(0, set.size());
+ }
+
+ public void testImmutableSetFromSingle() throws Exception
+ {
+ ObjectMapper mapper = mapperWithModule()
+ .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
+ ImmutableSet set = mapper.readValue("\"abc\"",
+ new TypeReference>() { });
+ assertEquals(1, set.size());
+ assertTrue(set.contains("abc"));
+ }
+
+ public void testTypedImmutableset() throws Exception
+ {
+ ImmutableSet set;
+ Holder h;
+ String json;
+ Holder result;
+
+ // First, with one entry
+ set = new ImmutableSet.Builder()
+ .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().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 set = MAPPER.readValue("[5,1,2]", new TypeReference>() { });
+ assertEquals(3, set.size());
+ Iterator 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 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());
+
+ // and for [datatype-guava#52], verify allowance of JSON nulls
+ map = MAPPER.readValue("{\"12\":true,\"4\":null}", type);
+ assertEquals(1, map.size());
+ }
+
+ public void testTypedImmutableMap() throws Exception
+ {
+ ImmutableMap map;
+ Holder h;
+ String json;
+ Holder result;
+
+ // First, with one entry
+ map = new ImmutableMap.Builder()
+ .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().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 map = MAPPER.readValue("{\"12\":true,\"4\":false}", new TypeReference>() { });
+ 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 map = MAPPER.readValue("{\"12\":true,\"4\":false}", new TypeReference>() { });
+ 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/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TestMultimaps.java b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TestMultimaps.java
new file mode 100644
index 00000000..d0cc27a7
--- /dev/null
+++ b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TestMultimaps.java
@@ -0,0 +1,312 @@
+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.fasterxml.jackson.datatype.guava.pojo.AddOp;
+import com.fasterxml.jackson.datatype.guava.pojo.MathOp;
+import com.fasterxml.jackson.datatype.guava.pojo.MulOp;
+
+import com.google.common.collect.*;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+
+import static com.google.common.collect.TreeMultimap.create;
+
+/**
+ * Unit tests to verify handling of various {@link Multimap}s.
+ *
+ * @author steven@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 map = ArrayListMultimap.create();
+ }
+
+ static class MultiMapWithIgnores {
+ @JsonIgnoreProperties({ "x", "y" })
+ public Multimap map = ArrayListMultimap.create();
+
+ public MultiMapWithIgnores()
+ {
+ map.put("a", "foo");
+ map.put("x", "bar");
+ }
+ }
+
+ public static class ImmutableMultimapWrapper {
+
+ private ImmutableMultimap multimap;
+
+ public ImmutableMultimapWrapper() {
+ }
+
+ public ImmutableMultimapWrapper(ImmutableMultimap f) {
+ this.multimap = f;
+ }
+
+ public ImmutableMultimap getMultimap() {
+ return multimap;
+ }
+
+ public void setMultimap(ImmutableMultimap multimap) {
+ this.multimap = multimap;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 97 * hash + (this.multimap != null ? this.multimap.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final ImmutableMultimapWrapper other = (ImmutableMultimapWrapper) obj;
+ return !(this.multimap != other.multimap && (this.multimap == null || !this.multimap.equals(other.multimap)));
+ }
+
+ }
+
+ private static final String STRING_STRING_MULTIMAP =
+ "{\"first\":[\"abc\",\"abc\",\"foo\"]," + "\"second\":[\"bar\"]}";
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ 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 map = (Multimap) 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.writerFor(new TypeReference>() {}).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.>readValue(serializedForm, new TypeReference>() {}));
+ assertEquals(map, create(MAPPER.>readValue(serializedForm, new TypeReference>() {})));
+ assertEquals(map, create(MAPPER.>readValue(serializedForm, new TypeReference>() {})));
+ assertEquals(map, create(MAPPER.>readValue(serializedForm, new TypeReference>() {})));
+ }
+ }
+
+ public void testMultimapIssue3() throws Exception
+ {
+ Multimap m1 = TreeMultimap.create();
+ m1.put("foo", "bar");
+ m1.put("foo", "baz");
+ m1.put("qux", "quux");
+ ObjectMapper o = MAPPER;
+
+ String t1 = o.writerFor(new TypeReference>(){}).writeValueAsString(m1);
+ Map,?> javaMap = o.readValue(t1, Map.class);
+ assertEquals(2, javaMap.size());
+
+ String t2 = o.writerFor(new TypeReference>(){}).writeValueAsString(m1);
+ javaMap = o.readValue(t2, Map.class);
+ assertEquals(2, javaMap.size());
+
+ TreeMultimap m2 = TreeMultimap.create();
+ m2.put("foo", "bar");
+ m2.put("foo", "baz");
+ m2.put("qux", "quux");
+
+ String t3 = o.writerFor(new TypeReference>(){}).writeValueAsString(m2);
+ javaMap = o.readValue(t3, Map.class);
+ assertEquals(2, javaMap.size());
+
+ String t4 = o.writerFor(new TypeReference>(){}).writeValueAsString(m2);
+ javaMap = o.readValue(t4, Map.class);
+ assertEquals(2, javaMap.size());
+ }
+
+ public void testEnumKey() throws Exception
+ {
+ final TypeReference> type = new TypeReference>() {};
+ final Multimap map = TreeMultimap.create();
+
+ map.put(MyEnum.YAY, 5);
+ map.put(MyEnum.BOO, 2);
+
+ final String serializedForm = MAPPER.writerFor(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);
+ }
+
+ public void testNullHandling() throws Exception
+ {
+ Multimap input = ArrayListMultimap.create();
+ input.put("empty", null);
+ String json = MAPPER.writeValueAsString(input);
+ assertEquals(aposToQuotes("{'empty':[null]}"), json);
+ }
+
+ /*
+ /**********************************************************************
+ /* Unit tests for set-based multimaps
+ /**********************************************************************
+ */
+
+ /*
+ public void testTreeMultimap() throws IOException {
+ }
+
+ public void testForwardingSortedSetMultimap() throws IOException {
+
+ }
+ */
+
+ public void testImmutableSetMultimap() throws IOException {
+ SetMultimap map =
+ _verifyMultiMapRead(new TypeReference>() {
+ });
+ assertTrue(map instanceof ImmutableSetMultimap);
+ }
+
+ public void testHashMultimap() throws IOException {
+ SetMultimap map =
+ _verifyMultiMapRead(new TypeReference>() {
+ });
+ assertTrue(map instanceof HashMultimap);
+ }
+
+ public void testLinkedHashMultimap() throws IOException {
+ SetMultimap map =
+ _verifyMultiMapRead(new TypeReference>() {
+ });
+ assertTrue(map instanceof LinkedHashMultimap);
+ }
+
+ /*
+ public void testForwardingSetMultimap() {
+ }
+ */
+
+ private SetMultimap