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}