1 package com.fasterxml.jackson.databind.ser.std;
2
3 import java.io.IOException;
4 import java.lang.reflect.Type;
5 import java.util.*;
6
7 import com.fasterxml.jackson.annotation.JsonFormat;
8 import com.fasterxml.jackson.annotation.JsonFormat.Shape;
9
10 import com.fasterxml.jackson.core.*;
11
12 import com.fasterxml.jackson.databind.*;
13 import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
14 import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
15 import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonStringFormatVisitor;
16 import com.fasterxml.jackson.databind.node.ArrayNode;
17 import com.fasterxml.jackson.databind.node.ObjectNode;
18 import com.fasterxml.jackson.databind.ser.ContextualSerializer;
19 import com.fasterxml.jackson.databind.util.EnumValues;
20
21 /**
22  * Standard serializer used for {@link java.lang.Enum} types.
23  *<p>
24  * Based on {@link StdScalarSerializer} since the JSON value is
25  * scalar (String).
26  */

27 @JacksonStdImpl
28 public class EnumSerializer
29     extends StdScalarSerializer<Enum<?>>
30     implements ContextualSerializer
31 {
32     private static final long serialVersionUID = 1L;
33
34     /**
35      * This map contains pre-resolved values (since there are ways
36      * to customize actual String constants to use) to use as
37      * serializations.
38      */

39     protected final EnumValues _values;
40
41     /**
42      * Flag that is set if we statically know serialization choice
43      * between index and textual format (null if it needs to be dynamically
44      * checked).
45      * 
46      * @since 2.1
47      */

48     protected final Boolean _serializeAsIndex;
49
50     /*
51     /**********************************************************
52     /* Construction, initialization
53     /**********************************************************
54      */

55
56     public EnumSerializer(EnumValues v, Boolean serializeAsIndex)
57     {
58         super(v.getEnumClass(), false);
59         _values = v;
60         _serializeAsIndex = serializeAsIndex;
61     }
62
63     /**
64      * Factory method used by {@link com.fasterxml.jackson.databind.ser.BasicSerializerFactory}
65      * for constructing serializer instance of Enum types.
66      * 
67      * @since 2.1
68      */

69     @SuppressWarnings("unchecked")
70     public static EnumSerializer construct(Class<?> enumClass, SerializationConfig config,
71             BeanDescription beanDesc, JsonFormat.Value format)
72     {
73         /* 08-Apr-2015, tatu: As per [databind#749], we cannot statically determine
74          *   between name() and toString(), need to construct `EnumValues` with names,
75          *   handle toString() case dynamically (for example)
76          */

77         EnumValues v = EnumValues.constructFromName(config, (Class<Enum<?>>) enumClass);
78         Boolean serializeAsIndex = _isShapeWrittenUsingIndex(enumClass, format, truenull);
79         return new EnumSerializer(v, serializeAsIndex);
80     }
81
82     /**
83      * To support some level of per-property configuration, we will need
84      * to make things contextual. We are limited to "textual vs index"
85      * choice here, however.
86      */

87     @Override
88     public JsonSerializer<?> createContextual(SerializerProvider serializers,
89             BeanProperty property) throws JsonMappingException
90     {
91         JsonFormat.Value format = findFormatOverrides(serializers,
92                 property, handledType());
93         if (format != null) {
94             Class<?> type = handledType();
95             Boolean serializeAsIndex = _isShapeWrittenUsingIndex(type,
96                     format, false, _serializeAsIndex);
97             if (serializeAsIndex != _serializeAsIndex) {
98                 return new EnumSerializer(_values, serializeAsIndex);
99             }
100         }
101         return this;
102     }
103
104     /*
105     /**********************************************************
106     /* Extended API for Jackson databind core
107     /**********************************************************
108      */

109     
110     public EnumValues getEnumValues() { return _values; }
111
112     /*
113     /**********************************************************
114     /* Actual serialization
115     /**********************************************************
116      */

117     
118     @Override
119     public final void serialize(Enum<?> en, JsonGenerator gen, SerializerProvider serializers)
120         throws IOException
121     {
122         if (_serializeAsIndex(serializers)) {
123             gen.writeNumber(en.ordinal());
124             return;
125         }
126         // [databind#749]: or via toString()?
127         if (serializers.isEnabled(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)) {
128             gen.writeString(en.toString());
129             return;
130         }
131         gen.writeString(_values.serializedValueFor(en));
132     }
133
134     /*
135     /**********************************************************
136     /* Schema support
137     /**********************************************************
138      */

139
140     @Override
141     public JsonNode getSchema(SerializerProvider provider, Type typeHint)
142     {
143         if (_serializeAsIndex(provider)) {
144             return createSchemaNode("integer"true);
145         }
146         ObjectNode objectNode = createSchemaNode("string"true);
147         if (typeHint != null) {
148             JavaType type = provider.constructType(typeHint);
149             if (type.isEnumType()) {
150                 ArrayNode enumNode = objectNode.putArray("enum");
151                 for (SerializableString value : _values.values()) {
152                     enumNode.add(value.getValue());
153                 }
154             }
155         }
156         return objectNode;
157     }
158     
159     @Override
160     public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
161         throws JsonMappingException
162     {
163         SerializerProvider serializers = visitor.getProvider();
164         if (_serializeAsIndex(serializers)) {
165             visitIntFormat(visitor, typeHint, JsonParser.NumberType.INT);
166             return;
167         }
168         JsonStringFormatVisitor stringVisitor = visitor.expectStringFormat(typeHint);
169         if (stringVisitor != null) {
170             Set<String> enums = new LinkedHashSet<String>();
171             
172             // Use toString()?
173             if ((serializers != null) && 
174                     serializers.isEnabled(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)) {
175                 for (Enum<?> e : _values.enums()) {
176                     enums.add(e.toString());
177                 }
178             } else {
179                 // No, serialize using name() or explicit overrides
180                 for (SerializableString value : _values.values()) {
181                     enums.add(value.getValue());
182                 }
183             }
184             stringVisitor.enumTypes(enums);
185         }
186     }
187
188     /*
189     /**********************************************************
190     /* Helper methods
191     /**********************************************************
192      */

193     
194     protected final boolean _serializeAsIndex(SerializerProvider serializers)
195     {
196         if (_serializeAsIndex != null) {
197             return _serializeAsIndex.booleanValue();
198         }
199         return serializers.isEnabled(SerializationFeature.WRITE_ENUMS_USING_INDEX);
200     }
201
202     /**
203      * Helper method called to check whether serialization should be done using
204      * index (number) or not.
205      */

206     protected static Boolean _isShapeWrittenUsingIndex(Class<?> enumClass,
207             JsonFormat.Value format, boolean fromClass,
208             Boolean defaultValue)
209     {
210         JsonFormat.Shape shape = (format == null) ? null : format.getShape();
211         if (shape == null) {
212             return defaultValue;
213         }
214         // i.e. "default", check dynamically
215         if (shape == Shape.ANY || shape == Shape.SCALAR) {
216             return defaultValue;
217         }
218         // 19-May-2016, tatu: also consider "natural" shape
219         if (shape == Shape.STRING || shape == Shape.NATURAL) {
220             return Boolean.FALSE;
221         }
222         // 01-Oct-2014, tatu: For convenience, consider "as-array" to also mean 'yes, use index')
223         if (shape.isNumeric() || (shape == Shape.ARRAY)) {
224             return Boolean.TRUE;
225         }
226         // 07-Mar-2017, tatu: Also means `OBJECT` not available as property annotation...
227         throw new IllegalArgumentException(String.format(
228                 "Unsupported serialization shape (%s) for Enum %s, not supported as %s annotation",
229                     shape, enumClass.getName(), (fromClass? "class" : "property")));
230     }
231 }
232