001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.marshall;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.math.*;
022
023/**
024 * Supported wire formats for {@link BigInteger} and {@link BigDecimal} values.
025 *
026 * <p>
027 * Used by {@link MarshallingContext.Builder#bigNumberFormat(BigNumberFormat)},
028 * {@link Marshalled#bigNumberFormat()},
029 * {@link MarshalledProp#bigNumberFormat()}, and
030 * {@link MarshalledConfig#bigNumberFormat()} to control how
031 * {@link BigInteger} / {@link BigDecimal} values are written to text-based wire formats.
032 *
033 * <p>
034 * The default is {@link #NUMBER} which preserves the historical wire output (a bare numeric token).
035 *
036 * <h5 class='topic'>JS-interop motivation</h5>
037 *
038 * <p>
039 * JavaScript's {@code Number.MAX_SAFE_INTEGER} is {@code 2^53 − 1} (= {@code 9007199254740991}).
040 * Values outside that range silently lose precision when parsed by a JavaScript client. {@link #STRING}
041 * forces a quoted-string representation that round-trips losslessly. {@link #AUTO} picks {@link #NUMBER}
042 * for values that are JS-safe and {@link #STRING} for everything else — the typical "safe default" for
043 * services that may be consumed by both JVM and browser clients.
044 *
045 * <h5 class='topic'>Precedence (highest to lowest)</h5>
046 * <ol>
047 *    <li>{@link MarshalledProp#bigNumberFormat() @MarshalledProp(bigNumberFormat=…)} on the bean property.
048 *    <li>{@link Marshalled#bigNumberFormat() @Marshalled(bigNumberFormat=…)} on the bean class.
049 *    <li>{@link MarshalledConfig#bigNumberFormat() @MarshalledConfig(bigNumberFormat=…)} on
050 *       <code><ja>@Rest</ja></code>-annotated classes / methods.
051 *    <li>Programmatic {@link MarshallingContext.Builder#bigNumberFormat(BigNumberFormat)}.
052 *    <li>Environment variable <c>MarshallingContext.bigNumberFormat</c>.
053 *    <li>The default constant ({@link #NUMBER}).
054 * </ol>
055 *
056 * <h5 class='topic'>Parser leniency</h5>
057 *
058 * <p>
059 * Parsers SHALL accept both bare numeric and quoted-string input regardless of the parser-side
060 * {@code BigNumberFormat} setting. The configured constant is informational only.
061 *
062 * <h5 class='topic'>Binary serializers</h5>
063 *
064 * <p>
065 * Binary serializers (BSON / CBOR / MsgPack / Prototext) emit native numeric types regardless of the
066 * configured {@code BigNumberFormat}; this setting only affects text-based serializers. BSON has
067 * {@code decimal128} for exact decimal values; CBOR / MsgPack handle big-integer values via
068 * tag&nbsp;2 / tag&nbsp;3 with string fallback.
069 */
070public enum BigNumberFormat {
071
072   /** Sentinel meaning "no value configured" — falls through to the next-higher precedence level. */
073   NOT_SET,
074
075   /**
076    * Bare numeric token (the default).
077    *
078    * <h5 class='figure'>Example:</h5>
079    * <p class='bcode'>
080    *    12345678901234567890
081    * </p>
082    *
083    * <p>
084    * This is the historical wire output and the natural form for server-to-server JVM clients.
085    */
086   NUMBER,
087
088   /**
089    * Quoted-string form for lossless precision.
090    *
091    * <h5 class='figure'>Example:</h5>
092    * <p class='bcode'>
093    *    "12345678901234567890"
094    * </p>
095    *
096    * <p>
097    * Round-trips losslessly through any JSON / YAML / etc. parser, including JavaScript clients which
098    * would otherwise truncate to a 64-bit double.
099    */
100   STRING,
101
102   /**
103    * Hybrid — emit {@link #NUMBER} when the value is JS-safe (fits in {@code ±(2^53 − 1)}), otherwise
104    * emit {@link #STRING}.
105    *
106    * <p>
107    * For {@link BigDecimal} values, any non-zero scale forces {@link #STRING} (a fractional value cannot
108    * be safely represented in a JS double regardless of magnitude).
109    *
110    * <p>
111    * The "safe default" for services that may be consumed by both JVM and browser clients.
112    */
113   AUTO;
114
115   /** {@link Number#MAX_SAFE_INTEGER} equivalent — largest integer that round-trips through a JS double. */
116   private static final long JS_MAX_SAFE_INTEGER = 9007199254740991L;
117   private static final BigInteger BIG_JS_MAX_SAFE = BigInteger.valueOf(JS_MAX_SAFE_INTEGER);
118   private static final BigInteger BIG_JS_MIN_SAFE = BigInteger.valueOf(-JS_MAX_SAFE_INTEGER);
119
120   /**
121    * Formats the specified {@link BigInteger} using this format.
122    *
123    * <p>
124    * Returns either a {@link Number} (when {@link #NUMBER} or {@link #AUTO} produces a JS-safe value) or
125    * a {@link String} (when {@link #STRING} or {@link #AUTO} produces an out-of-range value). Callers
126    * should branch on the runtime type:
127    * <ul>
128    *    <li>{@link Number} → emit as a bare numeric wire token.
129    *    <li>{@link String} → emit as a quoted string.
130    * </ul>
131    *
132    * @param value The value to format. Can be <jk>null</jk> (returns <jk>null</jk>).
133    * @param format The configured format. Can be <jk>null</jk> (treated as {@link #NUMBER}).
134    * @return The formatted value (a {@link BigInteger} for numeric output, a {@link String} for string output),
135    *   or <jk>null</jk> if {@code value} is <jk>null</jk>.
136    */
137   public static Object format(BigInteger value, BigNumberFormat format) {
138      if (value == null)
139         return null;
140      var fmt = format == null ? NUMBER : format;
141      return switch (fmt) {
142         case NOT_SET, NUMBER -> value;
143         case STRING -> value.toString();
144         case AUTO -> isJsSafe(value) ? (Object) value : value.toString();
145      };
146   }
147
148   /**
149    * Formats the specified {@link BigDecimal} using this format.
150    *
151    * <p>
152    * Returns either a {@link Number} (when {@link #NUMBER} or {@link #AUTO} produces a JS-safe value) or
153    * a {@link String} (when {@link #STRING} or {@link #AUTO} produces an out-of-range value).
154    *
155    * <p>
156    * For {@link #AUTO}: any non-zero-scale {@link BigDecimal} (i.e. has a fractional part) forces
157    * {@link #STRING} since fractional values cannot be safely represented in a JS double regardless of
158    * magnitude.  Zero-scale values are tested against the integer {@code ±(2^53 − 1)} bound.
159    *
160    * @param value The value to format. Can be <jk>null</jk> (returns <jk>null</jk>).
161    * @param format The configured format. Can be <jk>null</jk> (treated as {@link #NUMBER}).
162    * @return The formatted value (a {@link BigDecimal} for numeric output, a {@link String} for string output),
163    *   or <jk>null</jk> if {@code value} is <jk>null</jk>.
164    */
165   public static Object format(BigDecimal value, BigNumberFormat format) {
166      if (value == null)
167         return null;
168      var fmt = format == null ? NUMBER : format;
169      return switch (fmt) {
170         case NOT_SET, NUMBER -> value;
171         case STRING -> value.toPlainString();
172         case AUTO -> {
173            if (value.scale() > 0)
174               yield value.toPlainString();
175            yield isJsSafe(value.toBigInteger()) ? (Object) value : value.toPlainString();
176         }
177      };
178   }
179
180   /**
181    * Parses the specified wire value into a {@link BigInteger} or {@link BigDecimal}.
182    *
183    * <p>
184    * Accepts both bare numeric and quoted-string input regardless of the {@code format} hint. The hint is
185    * informational only.
186    *
187    * @param <T> The target type ({@link BigInteger} or {@link BigDecimal}).
188    * @param value The wire value. Can be <jk>null</jk> or blank (returns <jk>null</jk>).
189    * @param format The configured format (informational only — parsing is format-agnostic). Can be <jk>null</jk> (ignored; parsing is format-agnostic).
190    * @param targetType The desired return type — {@link BigInteger} or {@link BigDecimal}.
191    * @return The parsed value, or <jk>null</jk> if {@code value} is <jk>null</jk> or blank.
192    * @throws IllegalArgumentException If the value cannot be parsed as a number or {@code targetType} is unsupported.
193    */
194   @SuppressWarnings({
195      "unchecked" // Cast is safe: parameterized by caller.
196   })
197   public static <T extends Number> T parse(String value, BigNumberFormat format, Class<T> targetType) {
198      if (value == null)
199         return null;
200      var s = value.trim();
201      if (s.isEmpty())
202         return null;
203      // Strip surrounding quotes if present (lenient parsing — quoted-string and bare-numeric both accepted).
204      if (s.length() >= 2 && (s.charAt(0) == '"' || s.charAt(0) == '\'') && s.charAt(s.length() - 1) == s.charAt(0))
205         s = s.substring(1, s.length() - 1).trim();
206      try {
207         if (BigInteger.class.equals(targetType))
208            return (T) new BigInteger(s);
209         if (BigDecimal.class.equals(targetType))
210            return (T) new BigDecimal(s);
211         throw iaex("Unsupported BigNumberFormat target type: %s", targetType.getName());
212      } catch (NumberFormatException e) {
213         throw iaex("Invalid big-number value '%s' for format %s: %s", value, format, e.getMessage());
214      }
215   }
216
217   /**
218    * Returns <jk>true</jk> if this format always emits a numeric wire value.
219    *
220    * <p>
221    * {@link #NUMBER} returns <jk>true</jk>; {@link #STRING} returns <jk>false</jk>. {@link #AUTO}
222    * returns <jk>false</jk> conservatively — its {@code format(...)} return type varies per value, so
223    * serializers should branch on the actual runtime type returned by
224    * {@link #format(BigInteger, BigNumberFormat)} / {@link #format(BigDecimal, BigNumberFormat)} rather
225    * than this hint.
226    *
227    * @return <jk>true</jk> if this format emits a bare numeric wire value.
228    */
229   public boolean isNumeric() {
230      return this == NUMBER;
231   }
232
233   private static boolean isJsSafe(BigInteger value) {
234      return value.compareTo(BIG_JS_MIN_SAFE) >= 0 && value.compareTo(BIG_JS_MAX_SAFE) <= 0;
235   }
236}