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}