View Javadoc
1   package edu.internet2.middleware.grouperClientExt.com.fasterxml.jackson.databind.ser.std;
2   
3   import java.io.IOException;
4   import java.lang.reflect.Type;
5   import java.math.BigDecimal;
6   import java.math.BigInteger;
7   
8   import edu.internet2.middleware.grouperClientExt.com.fasterxml.jackson.annotation.JsonFormat;
9   
10  import edu.internet2.middleware.grouperClientExt.com.fasterxml.jackson.core.JsonGenerator;
11  import edu.internet2.middleware.grouperClientExt.com.fasterxml.jackson.core.JsonParser;
12  
13  import edu.internet2.middleware.grouperClientExt.com.fasterxml.jackson.databind.*;
14  import edu.internet2.middleware.grouperClientExt.com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
15  import edu.internet2.middleware.grouperClientExt.com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
16  import edu.internet2.middleware.grouperClientExt.com.fasterxml.jackson.databind.ser.ContextualSerializer;
17  
18  /**
19   * As a fallback, we may need to use this serializer for other
20   * types of {@link Number}s: both custom types and "big" numbers
21   * like {@link BigInteger} and {@link BigDecimal}.
22   */
23  @JacksonStdImpl
24  @SuppressWarnings("serial")
25  public class NumberSerializer
26      extends StdScalarSerializer<Number>
27      implements ContextualSerializer
28  {
29      /**
30       * Static instance that is only to be used for {@link java.lang.Number}.
31       */
32      public final static NumberSerializerre/grouperClientExt/com/fasterxml/jackson/databind/ser/std/NumberSerializer.html#NumberSerializer">NumberSerializer instance = new NumberSerializer(Number.class);
33  
34      /**
35       * Copied from `jackson-core` class `GeneratorBase`
36       */
37      protected final static int MAX_BIG_DECIMAL_SCALE = 9999;
38      
39      protected final boolean _isInt;
40  
41      /**
42       * @since 2.5
43       */
44      public NumberSerializer(Class<? extends Number> rawType) {
45          super(rawType, false);
46          // since this will NOT be constructed for Integer or Long, only case is:
47          _isInt = (rawType == BigInteger.class);
48      }
49  
50      @Override
51      public JsonSerializer<?> createContextual(SerializerProvider prov,
52              BeanProperty property) throws JsonMappingException
53      {
54          JsonFormat.Value format = findFormatOverrides(prov, property, handledType());
55          if (format != null) {
56              switch (format.getShape()) {
57              case STRING:
58                  // [databind#2264]: Need special handling for `BigDecimal`
59                  if (((Class<?>) handledType()) == BigDecimal.class) {
60                      return bigDecimalAsStringSerializer();
61                  }
62                  return ToStringSerializer.instance;
63              default:
64              }
65          }
66          return this;
67      }
68  
69      @Override
70      public void serialize(Number value, JsonGenerator g, SerializerProvider provider) throws IOException
71      {
72          // should mostly come in as one of these two:
73          if (value instanceof BigDecimal) {
74              g.writeNumber((BigDecimal) value);
75          } else if (value instanceof BigInteger) {
76              g.writeNumber((BigInteger) value);
77              
78          // These should not occur, as more specific methods should have been called; but
79          // just in case let's cover all bases:
80          } else if (value instanceof Long) {
81              g.writeNumber(value.longValue());
82          } else if (value instanceof Double) {
83              g.writeNumber(value.doubleValue());
84          } else if (value instanceof Float) {
85              g.writeNumber(value.floatValue());
86          } else if (value instanceof Integer || value instanceof Byte || value instanceof Short) {
87              g.writeNumber(value.intValue()); // doesn't need to be cast to smaller numbers
88          } else {
89              // We'll have to use fallback "untyped" number write method
90              g.writeNumber(value.toString());
91          }
92      }
93  
94      @Override
95      public JsonNode getSchema(SerializerProvider provider, Type typeHint) {
96          return createSchemaNode(_isInt ? "integer" : "number", true);
97      }
98  
99      @Override
100     public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException
101     {
102         if (_isInt) {
103             visitIntFormat(visitor, typeHint, JsonParser.NumberType.BIG_INTEGER);
104         } else {
105             if (((Class<?>) handledType()) == BigDecimal.class) {
106                 visitFloatFormat(visitor, typeHint, JsonParser.NumberType.BIG_DECIMAL);
107             } else {
108                 // otherwise bit unclear what to call... but let's try:
109                 /*JsonNumberFormatVisitor v2 =*/ visitor.expectNumberFormat(typeHint);
110             }
111         }
112     }
113 
114     /**
115      * @since 2.10
116      */
117     public static JsonSerializer<?> bigDecimalAsStringSerializer() {
118         return BigDecimalAsStringSerializer.BD_INSTANCE;
119     }
120     
121     final static class BigDecimalAsStringSerializer
122         extends ToStringSerializerBase
123     {
124         final static BigDecimalAsStringSerializer BD_INSTANCE = new BigDecimalAsStringSerializer();
125         
126         public BigDecimalAsStringSerializer() {
127             super(BigDecimal.class);
128         }
129 
130         @Override
131         public boolean isEmpty(SerializerProvider prov, Object value) {
132             // As per [databind#2513], should not delegate; also, never empty (numbers do
133             // have "default value" to filter by, just not "empty"
134             return false;
135         }
136 
137         @Override
138         public void serialize(Object value, JsonGenerator gen, SerializerProvider provider)
139             throws IOException
140         {
141             final String text;
142             if (gen.isEnabled(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN)) {
143                 final BigDecimal bd = (BigDecimal) value;
144                 // 24-Aug-2016, tatu: [core#315] prevent possible DoS vector, so we need this
145                 if (!_verifyBigDecimalRange(gen, bd)) {
146                     // ... but wouldn't it be nice to trigger error via generator? Alas,
147                     // no method to do that. So we'll do...
148                     final String errorMsg = String.format(
149 "Attempt to write plain `java.math.BigDecimal` (see JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN) with illegal scale (%d): needs to be between [-%d, %d]",
150 bd.scale(), MAX_BIG_DECIMAL_SCALE, MAX_BIG_DECIMAL_SCALE);
151                     provider.reportMappingProblem(errorMsg);
152                 }
153                 text = bd.toPlainString();
154             } else {
155                 text = value.toString();
156             }
157             gen.writeString(text);
158         }
159 
160         @Override
161         public String valueToString(Object value) {
162             // should never be called
163             throw new IllegalStateException();
164         }
165 
166         // 24-Aug-2016, tatu: [core#315] prevent possible DoS vector, so we need this
167         protected boolean _verifyBigDecimalRange(JsonGenerator gen, BigDecimal value) throws IOException {
168             int scale = value.scale();
169             return ((scale >= -MAX_BIG_DECIMAL_SCALE) && (scale <= MAX_BIG_DECIMAL_SCALE));
170         }
171     }
172 }