1 package com.fasterxml.jackson.databind.ser.std;
2
3 import java.io.IOException;
4 import java.util.UUID;
5
6 import com.fasterxml.jackson.annotation.JsonFormat;
7 import com.fasterxml.jackson.core.JsonGenerator;
8 import com.fasterxml.jackson.databind.*;
9 import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
10 import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonValueFormat;
11 import com.fasterxml.jackson.databind.ser.ContextualSerializer;
12 import com.fasterxml.jackson.databind.util.TokenBuffer;
13
14 /**
15  * Specialized {@link JsonSerializer} to output {@link java.util.UUID}s.
16  * Beyond optimized access and writing of textual representation (which
17  * is the default handling in most cases), it will alternatively
18  * allow serialization using raw binary output (as 16-byte block)
19  * if underlying data format has efficient means to access that.
20  */

21 @SuppressWarnings("serial")
22 public class UUIDSerializer
23     extends StdScalarSerializer<UUID>
24     implements ContextualSerializer // since 2.11.3 (for databind#2815)
25 {
26     final static char[] HEX_CHARS = "0123456789abcdef".toCharArray();
27
28     /**
29      * Configuration setting that indicates if serialization as binary
30      * (native or Base64-encoded) has been forced; {@code null} means
31      * "use default heuristic"
32      *
33      * @since 2.11.3
34      */

35     protected final Boolean _asBinary;
36
37     public UUIDSerializer() { this(null); }
38
39     /**
40      * @since 2.11.3
41      */

42     protected UUIDSerializer(Boolean asBinary) {
43         super(UUID.class);
44         _asBinary = asBinary;
45     }
46
47     @Override
48     public boolean isEmpty(SerializerProvider prov, UUID value)
49     {
50         // Null UUID is empty, so...
51         if (value.getLeastSignificantBits() == 0L
52                 && value.getMostSignificantBits() == 0L) {
53             return true;
54         }
55         return false;
56     }
57
58     @Override
59     public JsonSerializer<?> createContextual(SerializerProvider serializers,
60             BeanProperty property) throws JsonMappingException
61     {
62         JsonFormat.Value format = findFormatOverrides(serializers,
63                 property, handledType());
64         Boolean asBinary = null;
65
66         if (format != null) {
67             JsonFormat.Shape shape = format.getShape();
68             if (shape == JsonFormat.Shape.BINARY) {
69                 asBinary = true;
70             } else if (shape == JsonFormat.Shape.STRING) {
71                 asBinary = false;
72             }
73             // otherwise leave as `null` meaning about same as NATURAL
74         }
75         if (asBinary != _asBinary) {
76             return new UUIDSerializer(asBinary);
77         }
78         return this;
79     }
80
81     @Override
82     public void serialize(UUID value, JsonGenerator gen, SerializerProvider provider)
83         throws IOException
84     {
85         // First: perhaps we could serialize it as raw binary data?
86         if (_writeAsBinary(gen)) {
87             gen.writeBinary(_asBytes(value));
88             return;
89         }
90
91         // UUID.toString() works ok functionally, but we can make it go much faster
92         // (by 4x with micro-benchmark)
93
94         final char[] ch = new char[36];
95         final long msb = value.getMostSignificantBits();
96         _appendInt((int) (msb >> 32), ch, 0);
97         ch[8] = '-';
98         int i = (int) msb;
99         _appendShort(i >>> 16, ch, 9);
100         ch[13] = '-';
101         _appendShort(i, ch, 14);
102         ch[18] = '-';
103
104         final long lsb = value.getLeastSignificantBits();
105         _appendShort((int) (lsb >>> 48), ch, 19);
106         ch[23] = '-';
107         _appendShort((int) (lsb >>> 32), ch, 24);
108         _appendInt((int) lsb, ch, 28);
109
110         gen.writeString(ch, 0, 36);
111     }
112
113     // @since 2.11.3
114     protected boolean _writeAsBinary(JsonGenerator g)
115     {
116         if (_asBinary != null) {
117             return _asBinary;
118         }
119         // 07-Dec-2013, tatu: One nasty case; that of TokenBuffer. While it can
120         //   technically retain binary data, we do not want to do use binary
121         //   with it, as that results in UUIDs getting converted to Base64 for
122         //   most conversions.
123         return !(g instanceof TokenBuffer) && g.canWriteBinaryNatively();
124     }
125
126
127     // Need to add bit of extra info, format
128     @Override
129     public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
130         throws JsonMappingException
131     {
132         visitStringFormat(visitor, typeHint, JsonValueFormat.UUID);
133     }
134
135     private static void _appendInt(int bits, char[] ch, int offset)
136     {
137         _appendShort(bits >> 16, ch, offset);
138         _appendShort(bits, ch, offset+4);
139     }
140
141     private static void _appendShort(int bits, char[] ch, int offset)
142     {
143         ch[offset] = HEX_CHARS[(bits >> 12) & 0xF];
144         ch[++offset] = HEX_CHARS[(bits >> 8) & 0xF];
145         ch[++offset] = HEX_CHARS[(bits >> 4) & 0xF];
146         ch[++offset] = HEX_CHARS[bits  & 0xF];
147     }
148
149     private final static byte[] _asBytes(UUID uuid)
150     {
151         byte[] buffer = new byte[16];
152         long hi = uuid.getMostSignificantBits();
153         long lo = uuid.getLeastSignificantBits();
154         _appendInt((int) (hi >> 32), buffer, 0);
155         _appendInt((int) hi, buffer, 4);
156         _appendInt((int) (lo >> 32), buffer, 8);
157         _appendInt((int) lo, buffer, 12);
158         return buffer;
159     }
160
161     private final static void _appendInt(int value, byte[] buffer, int offset)
162     {
163         buffer[offset] = (byte) (value >> 24);
164         buffer[++offset] = (byte) (value >> 16);
165         buffer[++offset] = (byte) (value >> 8);
166         buffer[++offset] = (byte) value;
167     }
168 }
169