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.util.Locale;
022
023/**
024 * Supported wire formats for non-finite {@link Float} / {@link Double} values
025 * ({@link Double#NaN NaN}, {@link Double#POSITIVE_INFINITY +Infinity}, {@link Double#NEGATIVE_INFINITY -Infinity}).
026 *
027 * <p>
028 * Used by {@link MarshallingContext.Builder#floatFormat(FloatFormat)},
029 * {@link Marshalled#floatFormat()},
030 * {@link MarshalledProp#floatFormat()}, and
031 * {@link MarshalledConfig#floatFormat()} to control how non-finite
032 * floating-point values are written to text-based wire formats.  Finite values are unaffected and ride
033 * the natural bare-numeric-token wire form for the underlying serializer.
034 *
035 * <p>
036 * The default is {@link #NaN_AS_NULL} which is JSON-spec-compliant per RFC 8259 §6 (non-finite floats
037 * are not valid JSON tokens; emitting {@code null} is the closest valid representation).
038 *
039 * <h5 class='topic'>Precedence (highest to lowest)</h5>
040 * <ol>
041 *    <li>{@link MarshalledProp#floatFormat() @MarshalledProp(floatFormat=…)} on the bean property.
042 *    <li>{@link Marshalled#floatFormat() @Marshalled(floatFormat=…)} on the bean class.
043 *    <li>{@link MarshalledConfig#floatFormat() @MarshalledConfig(floatFormat=…)} on
044 *       <code><ja>@Rest</ja></code>-annotated classes / methods.
045 *    <li>Programmatic {@link MarshallingContext.Builder#floatFormat(FloatFormat)}.
046 *    <li>Environment variable <c>MarshallingContext.floatFormat</c>.
047 *    <li>The default constant ({@link #NaN_AS_NULL}).
048 * </ol>
049 *
050 * <h5 class='topic'>Parser leniency</h5>
051 *
052 * <p>
053 * Parsers SHALL accept all of the following wire shapes regardless of the parser-side
054 * {@code FloatFormat} setting:
055 * <ul>
056 *    <li>Bare numeric tokens including the non-standard {@code NaN} / {@code Infinity} / {@code -Infinity} tokens
057 *       (Juneau's lenient parser accepts these by default).
058 *    <li>Quoted-string forms — {@code "NaN"}, {@code "Infinity"}, {@code "-Infinity"} (case-insensitive).
059 *    <li>{@code null} — reconstructed as {@link Double#NaN} when reading into a primitive {@code double} / {@code float};
060 *       preserved as {@code null} when reading into a boxed {@link Double} / {@link Float}.
061 * </ul>
062 *
063 * <h5 class='topic'>JSON-spec compliance</h5>
064 *
065 * <p>
066 * {@link #NaN_AS_NUMBER} is <b>non-standard JSON</b> per RFC 8259 §6 — JSON does not permit {@code NaN} /
067 * {@code Infinity} / {@code -Infinity} tokens. Juneau's own JSON parser accepts these in lenient mode,
068 * but strict-mode consumers (e.g. some browser {@code JSON.parse} pipelines) will reject them. Use only
069 * when you control both ends of the wire.
070 *
071 * <h5 class='topic'>Binary serializers</h5>
072 *
073 * <p>
074 * Binary serializers (BSON / CBOR / MsgPack / Prototext / Parquet) emit native IEEE-754 floats regardless
075 * of this setting; native non-finite representations exist in every supported binary format (BSON
076 * {@code double} / {@code decimal128}; CBOR's tag 22 / 23 / 24 for special floats; MsgPack {@code float64}).
077 * This setting only affects text-based serializers.
078 */
079@SuppressWarnings({
080   "java:S115" // NaN_AS_* enum constants use the IEEE 754 abbreviation 'NaN' which includes lowercase letters; intentional
081})
082public enum FloatFormat {
083
084   /** Sentinel meaning "no value configured" — falls through to the next-higher precedence level. */
085   NOT_SET,
086
087   /**
088    * Emit {@code null} for {@code NaN} / {@code ±Infinity} (the default).
089    *
090    * <p>
091    * <b>JSON-spec compliant.</b> Loses the ability to distinguish {@code NaN} from
092    * {@code +Infinity} from {@code -Infinity} on the wire — all three become {@code null}. Round-trips
093    * back to {@link Double#NaN} when reading into a primitive {@code double}; into a boxed {@link Double}
094    * field, {@code null} stays {@code null}.
095    */
096   NaN_AS_NULL,
097
098   /**
099    * Emit the quoted string {@code "NaN"} / {@code "Infinity"} / {@code "-Infinity"}.
100    *
101    * <p>
102    * <b>Round-trip preserving.</b> The parser unambiguously decodes the string back to the original
103    * non-finite value.
104    */
105   NaN_AS_STRING,
106
107   /**
108    * Emit the bare {@code NaN} / {@code Infinity} / {@code -Infinity} token.
109    *
110    * <p>
111    * <b>Non-standard JSON</b> per RFC 8259 §6. Accepted by Juneau's lenient parser and V8 / several
112    * Python libs; rejected by strict consumers. Use only when you control both ends of the wire.
113    */
114   NaN_AS_NUMBER,
115
116   /**
117    * Throw {@link IllegalArgumentException} when a non-finite value is encountered.
118    *
119    * <p>
120    * <b>Fail-fast</b> for data-cleanliness pipelines where a {@code NaN} indicates upstream corruption.
121    */
122   NaN_AS_ERROR;
123
124   /**
125    * Formats the specified {@code double} using this format.
126    *
127    * <p>
128    * Finite values always return the boxed {@link Double} so text serializers emit the bare numeric
129    * token. Non-finite values are dispatched per the constant:
130    * <ul>
131    *    <li>{@link #NaN_AS_NULL} → {@code null}.
132    *    <li>{@link #NaN_AS_STRING} → {@code "NaN"} / {@code "Infinity"} / {@code "-Infinity"}.
133    *    <li>{@link #NaN_AS_NUMBER} → boxed {@link Double} (non-finite); text serializers emit the bare token.
134    *    <li>{@link #NaN_AS_ERROR} → throws {@link IllegalArgumentException}.
135    * </ul>
136    *
137    * <p>
138    * {@link #NOT_SET} / {@code null} format are treated as {@link #NaN_AS_NULL}.
139    *
140    * @param value The value to format.
141    * @param format The configured format. Can be <jk>null</jk> (treated as {@link #NaN_AS_NULL}).
142    * @return The formatted value — a {@link Double} for finite or {@code NaN_AS_NUMBER}, a {@link String}
143    *   for {@code NaN_AS_STRING}, or {@code null} for non-finite under {@code NaN_AS_NULL}.
144    * @throws IllegalArgumentException If {@code format} is {@link #NaN_AS_ERROR} and the value is non-finite.
145    */
146   public static Object format(double value, FloatFormat format) {
147      if (Double.isFinite(value))
148         return Double.valueOf(value);
149      return formatNonFinite(value, format);
150   }
151
152   /**
153    * Formats the specified {@code float} using this format.
154    *
155    * <p>
156    * Finite values return the boxed {@link Float}. Non-finite values follow the same rules as
157    * {@link #format(double, FloatFormat)} except that {@link #NaN_AS_NUMBER} returns the boxed
158    * {@link Float} (not {@link Double}).
159    *
160    * @param value The value to format.
161    * @param format The configured format. Can be <jk>null</jk> (treated as {@link #NaN_AS_NULL}).
162    * @return The formatted value — a {@link Float} for finite or {@code NaN_AS_NUMBER}, a {@link String}
163    *   for {@code NaN_AS_STRING}, or {@code null} for non-finite under {@code NaN_AS_NULL}.
164    * @throws IllegalArgumentException If {@code format} is {@link #NaN_AS_ERROR} and the value is non-finite.
165    */
166   public static Object format(float value, FloatFormat format) {
167      if (Float.isFinite(value))
168         return Float.valueOf(value);
169      var fmt = format == null ? NaN_AS_NULL : format;
170      return switch (fmt) {
171         case NOT_SET, NaN_AS_NULL -> null;
172         case NaN_AS_STRING -> nonFiniteTokenFloat(value);
173         case NaN_AS_NUMBER -> Float.valueOf(value);
174         case NaN_AS_ERROR -> throw iaex("Non-finite float value %s rejected by FloatFormat.NaN_AS_ERROR", value);
175      };
176   }
177
178   private static Object formatNonFinite(double value, FloatFormat format) {
179      var fmt = format == null ? NaN_AS_NULL : format;
180      return switch (fmt) {
181         case NOT_SET, NaN_AS_NULL -> null;
182         case NaN_AS_STRING -> nonFiniteToken(value);
183         case NaN_AS_NUMBER -> Double.valueOf(value);
184         case NaN_AS_ERROR -> throw iaex("Non-finite double value %s rejected by FloatFormat.NaN_AS_ERROR", value);
185      };
186   }
187
188   private static String nonFiniteToken(double value) {
189      if (Double.isNaN(value))
190         return "NaN";
191      return value > 0 ? "Infinity" : "-Infinity";
192   }
193
194   private static String nonFiniteTokenFloat(float value) {
195      if (Float.isNaN(value))
196         return "NaN";
197      return value > 0 ? "Infinity" : "-Infinity";
198   }
199
200   /**
201    * Parses the specified wire value into a non-finite-aware {@link Double} or {@link Float}.
202    *
203    * <p>
204    * Lenient parsing — accepts bare numeric, quoted-string {@code "NaN"} / {@code "Infinity"} /
205    * {@code "-Infinity"} (case-insensitive), and unquoted {@code NaN} / {@code Infinity} / {@code -Infinity}
206    * tokens regardless of the {@code format} hint. The hint is informational only.
207    *
208    * @param <T> The target type ({@link Double} or {@link Float}).
209    * @param value The wire value. Can be <jk>null</jk> or blank (returns <jk>null</jk>).
210    * @param format The configured format hint (informational only — parsing is format-agnostic). Can be <jk>null</jk> (ignored; parsing is format-agnostic).
211    * @param targetType The desired return type — {@link Double} or {@link Float}.
212    * @return The parsed value, or <jk>null</jk> if {@code value} is <jk>null</jk> or blank.
213    * @throws IllegalArgumentException If the value cannot be parsed or {@code targetType} is unsupported.
214    */
215   @SuppressWarnings({
216      "unchecked" // Cast is safe: parameterized by caller.
217   })
218   public static <T extends Number> T parse(String value, FloatFormat format, Class<T> targetType) {
219      if (value == null)
220         return null;
221      var s = value.trim();
222      if (s.isEmpty())
223         return null;
224      // Strip surrounding quotes if present (lenient parsing — quoted-string and bare-numeric both accepted).
225      if (s.length() >= 2 && (s.charAt(0) == '"' || s.charAt(0) == '\'') && s.charAt(s.length() - 1) == s.charAt(0))
226         s = s.substring(1, s.length() - 1).trim();
227      try {
228         if (Double.class.equals(targetType) || double.class.equals(targetType))
229            return (T) Double.valueOf(parseDouble(s));
230         if (Float.class.equals(targetType) || float.class.equals(targetType))
231            return (T) Float.valueOf(parseFloat(s));
232         throw iaex("Unsupported FloatFormat target type: %s", targetType.getName());
233      } catch (NumberFormatException e) {
234         throw iaex("Invalid float value '%s' for format %s: %s", value, format, e.getMessage());
235      }
236   }
237
238   private static double parseDouble(String s) {
239      var lower = s.toLowerCase(Locale.ROOT);
240      return switch (lower) {
241         case "nan" -> Double.NaN;
242         case "infinity", "+infinity", "inf", "+inf" -> Double.POSITIVE_INFINITY;
243         case "-infinity", "-inf" -> Double.NEGATIVE_INFINITY;
244         default -> Double.parseDouble(s);
245      };
246   }
247
248   private static float parseFloat(String s) {
249      var lower = s.toLowerCase(Locale.ROOT);
250      return switch (lower) {
251         case "nan" -> Float.NaN;
252         case "infinity", "+infinity", "inf", "+inf" -> Float.POSITIVE_INFINITY;
253         case "-infinity", "-inf" -> Float.NEGATIVE_INFINITY;
254         default -> Float.parseFloat(s);
255      };
256   }
257
258   /**
259    * Returns <jk>true</jk> if this format always emits a bare numeric wire token for non-finite values.
260    *
261    * <p>
262    * Only {@link #NaN_AS_NUMBER} returns <jk>true</jk>. {@link #NaN_AS_NULL} may emit {@code null};
263    * {@link #NaN_AS_STRING} emits a quoted string; {@link #NaN_AS_ERROR} throws. Finite values are
264    * always emitted as bare numeric tokens regardless of this setting.
265    *
266    * @return <jk>true</jk> if this format emits a bare numeric wire token for non-finite values.
267    */
268   public boolean isNumeric() {
269      return this == NaN_AS_NUMBER;
270   }
271}