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.*; 022 023/** 024 * Supported wire formats for {@link UUID} values. 025 * 026 * <p> 027 * Used by {@link MarshallingContext.Builder#uuidFormat(UuidFormat)}, 028 * {@link Marshalled#uuidFormat()}, 029 * {@link MarshalledProp#uuidFormat()}, and 030 * {@link MarshalledConfig#uuidFormat()} to control how {@link UUID} values 031 * are written to text-based wire formats. 032 * 033 * <p> 034 * The default is {@link #STANDARD} which preserves the historical wire output produced by 035 * {@link UUID#toString()} (8-4-4-4-12 hyphenated form). 036 * 037 * <h5 class='topic'>Precedence (highest to lowest)</h5> 038 * <ol> 039 * <li>{@link MarshalledProp#uuidFormat() @MarshalledProp(uuidFormat=…)} on the bean property. 040 * <li>{@link Marshalled#uuidFormat() @Marshalled(uuidFormat=…)} on the bean class. 041 * <li>{@link MarshalledConfig#uuidFormat() @MarshalledConfig(uuidFormat=…)} on 042 * <code><ja>@Rest</ja></code>-annotated classes / methods. 043 * <li>Programmatic {@link MarshallingContext.Builder#uuidFormat(UuidFormat)}. 044 * <li>Environment variable <c>MarshallingContext.uuidFormat</c>. 045 * <li>The default constant ({@link #STANDARD}). 046 * </ol> 047 * 048 * <h5 class='topic'>Parser leniency</h5> 049 * 050 * <p> 051 * Parsers SHALL accept any wire shape that the corresponding serializer can produce, regardless of the 052 * parser-side {@code UuidFormat} setting. Each constant accepts all three textual shapes 053 * ({@link #STANDARD} / {@link #NO_DASHES} / {@link #URN}) — the configured constant is informational and 054 * is consulted only as a hint. 055 * 056 * <h5 class='topic'>Binary serializers</h5> 057 * 058 * <p> 059 * Binary serializers (BSON / CBOR / MsgPack / Prototext) emit native 16-byte binary regardless of the 060 * configured {@code UuidFormat}; this setting only affects text-based serializers. BSON in particular 061 * uses {@code binData} subtype 4 (UUID). 062 */ 063public enum UuidFormat { 064 065 /** Sentinel meaning "no value configured" — falls through to the next-higher precedence level. */ 066 NOT_SET, 067 068 /** 069 * 8-4-4-4-12 hyphenated form (the default). 070 * 071 * <h5 class='figure'>Example:</h5> 072 * <p class='bcode'> 073 * 550e8400-e29b-41d4-a716-446655440000 074 * </p> 075 * 076 * <p> 077 * Round-trips through {@link UUID#toString()} / {@link UUID#fromString(String)}. 078 */ 079 STANDARD, 080 081 /** 082 * Compact 32-hex-character form (no hyphens). 083 * 084 * <h5 class='figure'>Example:</h5> 085 * <p class='bcode'> 086 * 550e8400e29b41d4a716446655440000 087 * </p> 088 * 089 * <p> 090 * Useful for URL paths, database column keys, and other compact-token use cases where the standard 091 * hyphens add noise. 092 */ 093 NO_DASHES, 094 095 /** 096 * RFC 4122 URN namespace form. 097 * 098 * <h5 class='figure'>Example:</h5> 099 * <p class='bcode'> 100 * urn:uuid:550e8400-e29b-41d4-a716-446655440000 101 * </p> 102 * 103 * <p> 104 * Surfaces in JSON-LD, SOAP, and some semantic-web payloads. 105 */ 106 URN; 107 108 private static final String URN_PREFIX = "urn:uuid:"; 109 110 /** 111 * Formats the specified {@link UUID} using this format. 112 * 113 * <p> 114 * {@link #NOT_SET} falls through to {@link #STANDARD}. 115 * 116 * @param value The value to format. Can be <jk>null</jk> (returns <jk>null</jk>). 117 * @return The formatted wire representation, or <jk>null</jk> if {@code value} is <jk>null</jk>. 118 */ 119 public static String format(UUID value, UuidFormat format) { 120 if (value == null) 121 return null; 122 var fmt = format == null ? STANDARD : format; 123 return switch (fmt) { 124 case NOT_SET, STANDARD -> value.toString(); 125 case NO_DASHES -> value.toString().replace("-", ""); 126 case URN -> URN_PREFIX + value; 127 }; 128 } 129 130 /** 131 * Parses the specified wire value into a {@link UUID}. 132 * 133 * <p> 134 * Lenient parsing — accepts any of the three textual shapes ({@link #STANDARD} / {@link #NO_DASHES} / 135 * {@link #URN}) regardless of the {@code format} hint. The hint is informational only. 136 * 137 * @param value The wire value. Can be <jk>null</jk> or blank (returns <jk>null</jk>). 138 * @param format The configured format hint (informational only — parsing is format-agnostic). Can be <jk>null</jk> (ignored; parsing is format-agnostic). 139 * @return The parsed {@link UUID}, or <jk>null</jk> if {@code value} is <jk>null</jk> or blank. 140 * @throws IllegalArgumentException If the value does not match any supported UUID textual shape. 141 */ 142 public static UUID parse(String value, UuidFormat format) { 143 if (value == null) 144 return null; 145 var s = value.trim(); 146 if (s.isEmpty()) 147 return null; 148 // Strip URN prefix when present (case-insensitive per RFC 4122). 149 if (s.length() > URN_PREFIX.length() && s.regionMatches(true, 0, URN_PREFIX, 0, URN_PREFIX.length())) 150 s = s.substring(URN_PREFIX.length()); 151 // Insert dashes if the input is the 32-hex-character compact form. 152 if (s.length() == 32 && s.indexOf('-') < 0) 153 s = s.substring(0, 8) + '-' + s.substring(8, 12) + '-' + s.substring(12, 16) + '-' + s.substring(16, 20) + '-' + s.substring(20); 154 try { 155 return UUID.fromString(s); 156 } catch (IllegalArgumentException e) { 157 throw iaex("Invalid UUID value '%s' for format %s: %s", value, format, e.getMessage()); 158 } 159 } 160 161 /** 162 * Returns <jk>true</jk> if this format is numeric on the wire. 163 * 164 * <p> 165 * Always <jk>false</jk> — every constant emits a textual representation. 166 * 167 * @return <jk>false</jk>. 168 */ 169 @SuppressWarnings({ 170 "java:S3400", // Same rationale — must remain an instance method, not a constant, to match the cross-Format API contract. 171 "static-method" // Kept as an instance method for polymorphic-by-convention symmetry with the other Format classes (BigNumberFormat, FloatFormat, DurationFormat, etc.) where isNumeric() depends on the enum constant. 172 }) 173 public boolean isNumeric() { 174 return false; 175 } 176}