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 {@link Boolean} / <code><jk>boolean</jk></code> values.
025 *
026 * <p>
027 * Used by {@link MarshallingContext.Builder#booleanFormat(BooleanFormat)},
028 * {@link Marshalled#booleanFormat()},
029 * {@link MarshalledProp#booleanFormat()}, and
030 * {@link MarshalledConfig#booleanFormat()} to control how boolean values
031 * are written to text-based wire formats.
032 *
033 * <p>
034 * The default is {@link #TRUE_FALSE} which preserves the historical wire output (the bare
035 * <c>true</c> / <c>false</c> tokens).
036 *
037 * <h5 class='topic'>Precedence (highest to lowest)</h5>
038 * <ol>
039 *    <li>{@link MarshalledProp#booleanFormat() @MarshalledProp(booleanFormat=…)} on the bean property.
040 *    <li>{@link Marshalled#booleanFormat() @Marshalled(booleanFormat=…)} on the bean class.
041 *    <li>{@link MarshalledConfig#booleanFormat() @MarshalledConfig(booleanFormat=…)} on
042 *       <code><ja>@Rest</ja></code>-annotated classes / methods.
043 *    <li>Programmatic {@link MarshallingContext.Builder#booleanFormat(BooleanFormat)}.
044 *    <li>Environment variable <c>MarshallingContext.booleanFormat</c>.
045 *    <li>The default constant ({@link #TRUE_FALSE}).
046 * </ol>
047 *
048 * <h5 class='topic'>Parser leniency</h5>
049 *
050 * <p>
051 * Parsers SHALL accept any of the textual shapes ({@code true}/{@code false}, {@code 1}/{@code 0},
052 * {@code yes}/{@code no}, {@code y}/{@code n}, {@code on}/{@code off}) regardless of the parser-side
053 * {@code BooleanFormat} setting. Matching is case-insensitive. The configured constant is informational only.
054 *
055 * <h5 class='topic'>Binary serializers</h5>
056 *
057 * <p>
058 * Binary serializers (BSON / CBOR / MsgPack / Prototext / Parquet) emit a native boolean wire type regardless
059 * of this setting; the setting only affects text-based serializers (JSON / XML / CSV / HOCON / TOML / etc.).
060 * For {@link #ZERO_ONE}, JSON / JSON5 emit the bare numeric token <c>0</c> / <c>1</c> (not the string
061 * <c>"0"</c> / <c>"1"</c>) so the wire shape matches an <code><jk>int</jk></code> field.
062 */
063public enum BooleanFormat {
064
065   /** Sentinel meaning "no value configured" — falls through to the next-higher precedence level. */
066   NOT_SET,
067
068   /**
069    * Standard boolean tokens (the default).
070    *
071    * <h5 class='figure'>Example:</h5>
072    * <p class='bcode'>
073    *    true
074    *    false
075    * </p>
076    *
077    * <p>
078    * Round-trips through {@link Boolean#toString()} / {@link Boolean#parseBoolean(String)}.
079    */
080   TRUE_FALSE,
081
082   /**
083    * Numeric tokens — <c>0</c> for {@code false} and <c>1</c> for {@code true}.
084    *
085    * <h5 class='figure'>Example:</h5>
086    * <p class='bcode'>
087    *    1
088    *    0
089    * </p>
090    *
091    * <p>
092    * JSON / JSON5 emit the bare numeric token <c>0</c> / <c>1</c> (not the quoted string <c>"0"</c> /
093    * <c>"1"</c>) so the wire shape matches an <code><jk>int</jk></code> field. Useful for CSV-shaped
094    * JSON consumers and SQL-style payloads.
095    */
096   ZERO_ONE,
097
098   /**
099    * English long-form yes / no.
100    *
101    * <h5 class='figure'>Example:</h5>
102    * <p class='bcode'>
103    *    yes
104    *    no
105    * </p>
106    *
107    * <p>
108    * Common in human-friendly CSV and form-encoded payloads.
109    */
110   YES_NO,
111
112   /**
113    * Compact single-character yes / no.
114    *
115    * <h5 class='figure'>Example:</h5>
116    * <p class='bcode'>
117    *    Y
118    *    N
119    * </p>
120    *
121    * <p>
122    * Compact single-character form, common in DB and CSV exports.
123    */
124   Y_N,
125
126   /**
127    * On / off.
128    *
129    * <h5 class='figure'>Example:</h5>
130    * <p class='bcode'>
131    *    on
132    *    off
133    * </p>
134    *
135    * <p>
136    * Common in config files (HOCON / INI / TOML) and HTML form checkboxes.
137    */
138   ON_OFF;
139
140   /**
141    * Formats the specified boolean using this format.
142    *
143    * <p>
144    * The return type varies per constant:
145    * <ul>
146    *    <li>{@link #TRUE_FALSE} → {@link Boolean} (so JSON / XML emit the bare {@code true} / {@code false} token).
147    *    <li>{@link #ZERO_ONE} → {@link Integer} (so JSON emits the bare numeric token {@code 0} / {@code 1}).
148    *    <li>{@link #YES_NO} / {@link #Y_N} / {@link #ON_OFF} → {@link String}.
149    * </ul>
150    *
151    * <p>
152    * {@link #NOT_SET} falls through to {@link #TRUE_FALSE}.
153    *
154    * @param value The value to format.
155    * @param format The configured format. Can be <jk>null</jk> (treated as {@link #TRUE_FALSE}).
156    * @return The formatted wire value — a {@link Boolean}, {@link Integer}, or {@link String} depending on the format.
157    */
158   public static Object format(boolean value, BooleanFormat format) {
159      var fmt = format == null ? TRUE_FALSE : format;
160      return switch (fmt) {
161         case NOT_SET, TRUE_FALSE -> Boolean.valueOf(value);
162         case ZERO_ONE -> Integer.valueOf(value ? 1 : 0);
163         case YES_NO -> value ? "yes" : "no";
164         case Y_N -> value ? "Y" : "N";
165         case ON_OFF -> value ? "on" : "off";
166      };
167   }
168
169   /**
170    * Parses the specified wire value into a primitive {@code boolean}.
171    *
172    * <p>
173    * Lenient parsing — accepts any of the textual shapes ({@code true}/{@code false}, {@code 1}/{@code 0},
174    * {@code yes}/{@code no}, {@code y}/{@code n}, {@code on}/{@code off}) regardless of the {@code format}
175    * hint, case-insensitive. The hint is informational only.
176    *
177    * @param value The wire value. Must not be <jk>null</jk> or blank, or an {@link IllegalArgumentException} is thrown.
178    * @param format The configured format hint (informational only — parsing is format-agnostic). Can be <jk>null</jk> (ignored; parsing is format-agnostic).
179    * @return The parsed boolean value.
180    * @throws IllegalArgumentException If {@code value} is <jk>null</jk>, blank, or does not match any supported boolean textual shape.
181    */
182   public static boolean parse(String value, BooleanFormat format) {
183      if (value == null)
184         throw iaex("Cannot parse a null value as Boolean");
185      var s = value.trim();
186      if (s.isEmpty())
187         throw iaex("Cannot parse a blank value as Boolean");
188      return switch (s.toLowerCase(Locale.ROOT)) {
189         case "true", "1", "yes", "y", "on" -> true;
190         case "false", "0", "no", "n", "off" -> false;
191         default -> throw iaex("Invalid boolean value '%s' for format %s", value, format);
192      };
193   }
194
195   /**
196    * Returns <jk>true</jk> if this format always emits a numeric wire token.
197    *
198    * <p>
199    * {@link #ZERO_ONE} returns <jk>true</jk>; every other constant returns <jk>false</jk>.
200    * Note that {@link #TRUE_FALSE} emits a native boolean token (not a string) in JSON / XML — that is
201    * considered a boolean-typed wire value, not a numeric one.
202    *
203    * @return <jk>true</jk> if this format emits a bare numeric wire token.
204    */
205   public boolean isNumeric() {
206      return this == ZERO_ONE;
207   }
208}