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.*; 020import static org.apache.juneau.commons.utils.StringUtils.*; 021 022import java.util.*; 023 024/** 025 * Supported wire formats for <code><jk>byte</jk>[]</code> values and stream-based serialization output. 026 * 027 * <p> 028 * Used by {@link MarshallingContext.Builder#binaryFormat(BinaryFormat)}, 029 * {@link Marshalled#binaryFormat()}, 030 * {@link MarshalledProp#binaryFormat()}, and 031 * {@link MarshalledConfig#binaryFormat()} to control how 032 * <code><jk>byte</jk>[]</code> values are written to and read from the wire on text formats, as well as 033 * the encoding used by stream-based serializers when converting their byte output to a string. 034 * 035 * <p> 036 * The default is {@link #NOT_SET} — leaves {@code byte[]} handling to the surrounding serializer / parser 037 * (e.g. textual serializers fall back to the natural language-level array representation, and binary 038 * serializers emit native bytes). Callers wanting a textual encoding on the wire should set this to 039 * {@link #HEX}, {@link #BASE64}, {@link #SPACED_HEX}, or {@link #BASE64_URL}. 040 * 041 * <p> 042 * Binary serializers (BSON / CBOR / MsgPack / Prototext) emit native bytes regardless of the configured 043 * {@code BinaryFormat}; this setting only affects text-based serializers and the 044 * {@code OutputStreamSerializerSession#writeToString(Object)} stream-to-string convenience. 045 */ 046public enum BinaryFormat { 047 048 /** Sentinel meaning "no value configured" — falls through to the next-higher precedence level. */ 049 NOT_SET, 050 051 /** 052 * Spaced-hex. 053 * 054 * <h5 class='figure'>Example:</h5> 055 * <p class='bcode'> 056 * 12 34 56 78 90 AB CD EF 057 * </p> 058 */ 059 SPACED_HEX, 060 061 /** 062 * Hex. 063 * 064 * <h5 class='figure'>Example:</h5> 065 * <p class='bcode'> 066 * 1234567890ABCDEF 067 * </p> 068 */ 069 HEX, 070 071 /** Standard {@link Base64} encoding. */ 072 BASE64, 073 074 /** 075 * URL-safe {@link Base64} encoding (RFC 4648 §5). 076 * 077 * <p> 078 * Uses {@code -} and {@code _} in place of {@code +} and {@code /}. Padding ({@code =}) is omitted on 079 * the wire but accepted on input. 080 */ 081 BASE64_URL; 082 083 private static final byte[] EMPTY_BYTES = new byte[0]; 084 085 /** 086 * Formats the specified value using this format. 087 * 088 * @param value The value to format. Can be <jk>null</jk> (returns <jk>null</jk>). 089 * @return The formatted string, or <jk>null</jk> if {@code value} is <jk>null</jk>. 090 */ 091 public String format(byte[] value) { 092 if (value == null) 093 return null; 094 return switch (this) { 095 case NOT_SET, BASE64 -> base64Encode(value); 096 case BASE64_URL -> Base64.getUrlEncoder().withoutPadding().encodeToString(value); 097 case HEX -> toHex(value); 098 case SPACED_HEX -> toSpacedHex(value); 099 }; 100 } 101 102 /** 103 * Parses the specified wire value using this format. 104 * 105 * <p> 106 * The parser accepts any of the formats produced by {@link #format(byte[])} regardless of the 107 * configured constant, mirroring the format-agnostic parser convention used by other {@code <type>Format} 108 * enums in this package. The configured format is consulted only as a hint for ambiguous input. 109 * 110 * @param value The wire value. Can be <jk>null</jk> or blank (returns an empty array). 111 * @return The parsed bytes, or an empty array if {@code value} is <jk>null</jk> or blank. 112 */ 113 public byte[] parse(String value) { 114 if (value == null) 115 return EMPTY_BYTES; 116 var s = value.trim(); 117 if (s.isEmpty()) 118 return EMPTY_BYTES; 119 // Honor the configured format constant for BASE64_URL: the URL-safe encoder emits without padding, 120 // so a non-3-aligned payload that happens not to contain `-`/`_` (because the encoded chars all fall 121 // in the base64-alphabet intersection) would otherwise be misrouted to the standard decoder, which 122 // rejects missing padding. The URL-safe decoder accepts both with-padding and without-padding input. 123 if (this == BASE64_URL) { 124 try { 125 return Base64.getUrlDecoder().decode(s); 126 } catch (IllegalArgumentException e) { 127 throw iaex("Invalid binary value '%s' for format %s: %s", s, this, e.getMessage()); 128 } 129 } 130 // Format-agnostic parsing: sniff the wire shape and decode accordingly, regardless of the 131 // configured format on `this`. A space anywhere implies spaced-hex; a `-`/`_` implies url-safe 132 // base64; otherwise pure hex shape (even-length, hex-only chars) implies hex. Anything left 133 // is standard base64. 134 if (s.indexOf(' ') >= 0) 135 return fromSpacedHex(s); 136 if (s.indexOf('-') >= 0 || s.indexOf('_') >= 0) 137 return Base64.getUrlDecoder().decode(s); 138 if (isHex(s)) 139 return fromHex(s); 140 try { 141 return base64Decode(s); 142 } catch (IllegalArgumentException e) { 143 throw iaex("Invalid binary value '%s' for format %s: %s", s, this, e.getMessage()); 144 } 145 } 146 147 private static boolean isHex(String s) { 148 if (s.isEmpty() || (s.length() & 1) == 1) 149 return false; 150 for (var i = 0; i < s.length(); i++) { 151 var c = s.charAt(i); 152 if (! ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) 153 return false; 154 } 155 return true; 156 } 157}