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.concurrent.atomic.*;
022import java.util.function.*;
023
024/**
025 * Supported wire formats for {@link Class} values.
026 *
027 * <p>
028 * Used by {@link MarshallingContext.Builder#classFormat(ClassFormat)},
029 * {@link Marshalled#classFormat()},
030 * {@link MarshalledProp#classFormat()}, and
031 * {@link MarshalledConfig#classFormat()} to control how {@link Class} values
032 * are written to text-based wire formats.
033 *
034 * <p>
035 * The default is {@link #FQCN} which mirrors the historical {@code ClassSwap} behavior (canonical dotted
036 * name with sensible fallback to the JVM binary name).
037 *
038 * <h5 class='topic'>FQCN vs BINARY_NAME for nested classes</h5>
039 *
040 * <p>
041 * For nested-type encoding the two forms diverge:
042 * <ul>
043 *    <li>{@link #FQCN} uses {@link Class#getCanonicalName()} when available — {@code "java.util.Map.Entry"}
044 *       — and falls back to {@link Class#getName()} for local / anonymous classes (where
045 *       {@code getCanonicalName()} returns {@code null}).
046 *    <li>{@link #BINARY_NAME} always uses {@link Class#getName()} — {@code "java.util.Map$Entry"}.
047 * </ul>
048 *
049 * <p>
050 * For arrays the difference is more dramatic:
051 * <ul>
052 *    <li>{@link #FQCN} produces {@code "int[]"} / {@code "java.lang.String[]"}.
053 *    <li>{@link #BINARY_NAME} produces {@code "[I"} / {@code "[Ljava.lang.String;"} (JVM descriptor form).
054 * </ul>
055 *
056 * <h5 class='topic'>SIMPLE_NAME is serialize-only</h5>
057 *
058 * <p>
059 * {@link #SIMPLE_NAME} discards package and outer-class context and is <b>not round-trip safe</b>. The
060 * parser path for this constant throws {@link UnsupportedOperationException}; use {@link #FQCN} or
061 * {@link #BINARY_NAME} for round-trippable wires.
062 *
063 * <h5 class='topic'>Precedence (highest to lowest)</h5>
064 * <ol>
065 *    <li>{@link MarshalledProp#classFormat() @MarshalledProp(classFormat=…)} on the bean property.
066 *    <li>{@link Marshalled#classFormat() @Marshalled(classFormat=…)} on the bean class.
067 *    <li>{@link MarshalledConfig#classFormat() @MarshalledConfig(classFormat=…)} on
068 *       <code><ja>@Rest</ja></code>-annotated classes / methods.
069 *    <li>Programmatic {@link MarshallingContext.Builder#classFormat(ClassFormat)}.
070 *    <li>Environment variable <c>MarshallingContext.classFormat</c>.
071 *    <li>The default constant ({@link #FQCN}).
072 * </ol>
073 *
074 * <h5 class='topic'>Parser leniency</h5>
075 *
076 * <p>
077 * Parsers SHALL try {@link Class#forName(String, boolean, ClassLoader)} first regardless of the
078 * parser-side {@code ClassFormat} setting — this handles {@link #BINARY_NAME} and most {@link #FQCN}
079 * inputs unambiguously. For nested-type {@link #FQCN} input ({@code "java.util.Map.Entry"}), the parser
080 * walks back through dotted segments from right to left, replacing each uppercase-prefixed dot with
081 * {@code $} and retrying — this resolves multi-level nesting like
082 * {@code "com.example.Outer.Inner.Deepest"} to {@code com.example.Outer$Inner$Deepest}.  For
083 * {@link #FQCN} array input the parser strips trailing {@code []} pairs, resolves the leaf component
084 * type (including primitives like {@code "int"}), and rebuilds the array via {@link Class#arrayType()}
085 * — so {@code "int[]"} resolves to {@code int[].class} and {@code "java.lang.String[][]"} resolves to
086 * {@code String[][].class}.
087 *
088 * <h5 class='topic'>Binary serializers</h5>
089 *
090 * <p>
091 * Binary serializers (BSON / CBOR / MsgPack / Prototext / Parquet) emit a UTF-8 string regardless of this
092 * setting — there is no native {@code Class} wire type in any supported binary format.
093 */
094public enum ClassFormat {
095
096   /** Sentinel meaning "no value configured" — falls through to the next-higher precedence level. */
097   NOT_SET,
098
099   /**
100    * Canonical fully-qualified name with dotted nested-type separators (the default).
101    *
102    * <p>
103    * Uses {@link Class#getCanonicalName()} when available; falls back to {@link Class#getName()} for
104    * local / anonymous classes where {@code getCanonicalName()} returns {@code null}.
105    *
106    * <h5 class='figure'>Example:</h5>
107    * <p class='bcode'>
108    *    java.lang.String
109    *    java.util.Map.Entry
110    *    int[]
111    * </p>
112    */
113   FQCN,
114
115   /**
116    * JVM binary name per {@link Class#getName()}.
117    *
118    * <h5 class='figure'>Example:</h5>
119    * <p class='bcode'>
120    *    java.lang.String
121    *    java.util.Map$Entry
122    *    [Ljava.lang.String;
123    *    [I
124    * </p>
125    *
126    * <p>
127    * Identical to {@link #FQCN} for top-level non-array reference types; diverges for nested types
128    * ({@code $} vs {@code .}) and arrays (JVM descriptor vs {@code Type[]} form).
129    */
130   BINARY_NAME,
131
132   /**
133    * Unqualified simple name per {@link Class#getSimpleName()}.
134    *
135    * <h5 class='figure'>Example:</h5>
136    * <p class='bcode'>
137    *    java.lang.String
138    *    Entry
139    *    int
140    * </p>
141    *
142    * <p>
143    * <b>Serialize-only.</b> The parser path for this constant throws
144    * {@link UnsupportedOperationException} — there is no defensible way to resolve {@code "Map"} back
145    * to a unique {@link Class} without a registry hint, and adding such a hint is out of scope.
146    */
147   SIMPLE_NAME;
148
149   // Optional global guard vetting class names before the parser path resolves them.  null == allow all.
150   private static final AtomicReference<Predicate<String>> classNameGuard = new AtomicReference<>();
151
152   /**
153    * Installs a global guard that vets class names before the parser path resolves them to a {@link Class}.
154    *
155    * <p>
156    * The guard is consulted for every candidate name (including the nested-type <c>$</c>-rewritten forms)
157    * before it is passed to {@link Class#forName(String, boolean, ClassLoader)}.  Names the guard rejects are
158    * treated as unresolvable.  This lets a deployment restrict which types may be materialized from parsed
159    * input to an explicit allow-list.
160    *
161    * @param value The guard predicate returning <jk>true</jk> for permitted class names, or <jk>null</jk> to
162    *    allow all names (the default).
163    */
164   @SuppressWarnings({
165      "java:S3066" // Public setter is the documented deployment-configuration entry point (see Javadoc above); it is also exercised directly from ClassFormat_Test in another package, so visibility cannot be narrowed without breaking that call site.
166   })
167   public static void setClassNameGuard(Predicate<String> value) {
168      classNameGuard.set(value);
169   }
170
171   /**
172    * Returns whether the specified candidate class name is permitted by the installed guard.
173    *
174    * @param name The candidate class name.
175    * @return <jk>true</jk> if no guard is installed or the guard permits the name.
176    */
177   static boolean isClassNameAllowed(String name) {
178      var g = classNameGuard.get();
179      return g == null || g.test(name);
180   }
181
182   /**
183    * Formats the specified {@link Class} using this format.
184    *
185    * <p>
186    * {@link #NOT_SET} / {@code null} format are treated as {@link #FQCN}.
187    *
188    * @param value The value to format. Can be <jk>null</jk> (returns <jk>null</jk>).
189    * @param format The configured format. Can be <jk>null</jk> (treated as {@link #FQCN}).
190    * @return The formatted wire representation, or <jk>null</jk> if {@code value} is <jk>null</jk>.
191    */
192   public static String format(Class<?> value, ClassFormat format) {
193      if (value == null)
194         return null;
195      var fmt = format == null ? FQCN : format;
196      return switch (fmt) {
197         case NOT_SET, FQCN -> {
198            // getCanonicalName() returns null for local / anonymous classes; fall back to binary name.
199            var canonical = value.getCanonicalName();
200            yield canonical != null ? canonical : value.getName();
201         }
202         case BINARY_NAME -> value.getName();
203         case SIMPLE_NAME -> value.getSimpleName();
204      };
205   }
206
207   /**
208    * Parses the specified wire value into a {@link Class}.
209    *
210    * <p>
211    * Lenient parsing — handles every shape produced by {@link #format(Class, ClassFormat)} for the
212    * round-trippable formats ({@link #FQCN} and {@link #BINARY_NAME}), and accepts cross-form input
213    * as well (an {@link #FQCN} parser still resolves {@link #BINARY_NAME} input and vice versa).
214    *
215    * <p>
216    * Resolution steps:
217    * <ol>
218    *    <li>Strip trailing {@code []} pairs ({@link #FQCN} array form), tracking dimension count.  Empty
219    *       brackets at the leading edge (JVM descriptor form like {@code "[I"} /
220    *       {@code "[Ljava.lang.String;"}) are left to {@link Class#forName(String, boolean, ClassLoader)}
221    *       because that method already handles them.
222    *    <li>Resolve the leaf component:
223    *       <ul>
224    *          <li>If the leaf names a primitive type ({@code "int"}, {@code "boolean"}, etc. — emitted
225    *             by {@link #FQCN} for primitive arrays), return the corresponding {@code TYPE} class.
226    *          <li>Otherwise try {@link Class#forName(String, boolean, ClassLoader)} directly.
227    *          <li>On failure, walk dots from right to left, replacing each uppercase-prefixed dot with
228    *             {@code $} (cumulatively — already-replaced dots stay replaced) and retrying.  This
229    *             resolves multi-level nesting like {@code "com.example.Outer.Inner.Deepest"} to
230    *             {@code com.example.Outer$Inner$Deepest}.  Dots whose trailing segment starts with a
231    *             lowercase letter are treated as package boundaries and skipped.
232    *       </ul>
233    *    <li>Apply {@link Class#arrayType()} once per stripped dimension to rebuild the array class.
234    * </ol>
235    *
236    * <p>
237    * {@link #SIMPLE_NAME} is not supported on the parser side — throws
238    * {@link UnsupportedOperationException}.
239    *
240    * @param value The wire value. Can be <jk>null</jk> or blank (returns <jk>null</jk>).
241    * @param format The configured format hint. Can be <jk>null</jk> (treated as {@link #FQCN}).
242    * @param classLoader The class loader. Can be <jk>null</jk> (uses
243    *   {@link Thread#getContextClassLoader() context class loader}).
244    * @return The parsed {@link Class}, or <jk>null</jk> if {@code value} is <jk>null</jk> or blank.
245    * @throws IllegalArgumentException If the value cannot be resolved.
246    * @throws UnsupportedOperationException If {@code format} is {@link #SIMPLE_NAME}.
247    */
248   public static Class<?> parse(String value, ClassFormat format, ClassLoader classLoader) {
249      if (value == null)
250         return null;
251      var s = value.trim();
252      if (s.isEmpty())
253         return null;
254      var fmt = format == null ? FQCN : format;
255      if (fmt == SIMPLE_NAME)
256         throw new UnsupportedOperationException("SIMPLE_NAME is serialize-only — use FQCN or BINARY_NAME for round-trippable wires");
257      var cl = classLoader != null ? classLoader : Thread.currentThread().getContextClassLoader();
258      try {
259         return resolveWithArraySuffix(s, cl);
260      } catch (ClassNotFoundException firstAttempt) {
261         throw iaex("Could not resolve class '%s' for format %s: %s", value, format, firstAttempt.getMessage());
262      }
263   }
264
265   /**
266    * Strips trailing {@code []} pairs from the FQCN array form, resolves the leaf component, then
267    * rebuilds the array dimensions via {@link Class#arrayType()}.
268    *
269    * <p>
270    * Inputs that don't end in {@code []} (top-level classes and JVM-descriptor array forms like
271    * {@code "[I"}) pass straight through to {@link #resolveLeaf(String, ClassLoader)}.
272    */
273   private static Class<?> resolveWithArraySuffix(String s, ClassLoader cl) throws ClassNotFoundException {
274      var dims = 0;
275      var leaf = s;
276      while (leaf.endsWith("[]")) {
277         dims++;
278         leaf = leaf.substring(0, leaf.length() - 2);
279      }
280      if (dims == 0)
281         return resolveLeaf(s, cl);
282      if (leaf.isEmpty())
283         throw new ClassNotFoundException(s);
284      var result = resolveLeaf(leaf, cl);
285      for (var i = 0; i < dims; i++)
286         result = result.arrayType();
287      return result;
288   }
289
290   /**
291    * Resolves a non-array leaf token to a {@link Class}.
292    *
293    * <p>
294    * Checks the primitive-name table first ({@link #FQCN} emits {@code "int"} / {@code "boolean"} /
295    * etc. for primitive array leafs), then falls through to {@link Class#forName(String, boolean,
296    * ClassLoader)} with a multi-level nested-type fallback heuristic.
297    */
298   private static Class<?> resolveLeaf(String s, ClassLoader cl) throws ClassNotFoundException {
299      var prim = primitiveByName(s);
300      if (prim != null)
301         return prim;
302      if (! isClassNameAllowed(s))
303         throw new ClassNotFoundException(s);
304      try {
305         // Resolve without initializing — a parsed name must never trigger a target type's static initializer.
306         return Class.forName(s, false, cl);
307      } catch (ClassNotFoundException firstAttempt) {
308         var nested = nestedTypeFallback(s, cl);
309         if (nested != null)
310            return nested;
311         throw firstAttempt;
312      }
313   }
314
315   /**
316    * Walks dots from right to left, replacing each uppercase-prefixed dot with {@code $} and retrying
317    * {@link Class#forName(String, boolean, ClassLoader)}.  Replacements accumulate — once a dot has
318    * been replaced, it stays replaced for subsequent iterations — so multi-level nesting like
319    * {@code "Outer.Inner.Deepest"} resolves to {@code "Outer$Inner$Deepest"}.
320    *
321    * <p>
322    * Returns <jk>null</jk> if no rewrite resolves to a loadable class.  Dots whose trailing segment
323    * starts with a lowercase letter are treated as package boundaries and left alone.
324    */
325   private static Class<?> nestedTypeFallback(String s, ClassLoader cl) {
326      var chars = s.toCharArray();
327      for (var i = chars.length - 1; i > 0; i--) {
328         if (chars[i] == '.' && i + 1 < chars.length && Character.isUpperCase(chars[i + 1])) {
329            chars[i] = '$';
330            var candidate = new String(chars);
331            if (! isClassNameAllowed(candidate))
332               continue;
333            try {
334               return Class.forName(candidate, false, cl);
335            } catch (@SuppressWarnings("unused") ClassNotFoundException ignored) {
336               // Keep walking; the previous-dot replacement persists in chars so deeper nesting can resolve.
337            }
338         }
339      }
340      return null;
341   }
342
343   private static Class<?> primitiveByName(String s) {
344      return switch (s) {
345         case "boolean" -> boolean.class;
346         case "byte" -> byte.class;
347         case "char" -> char.class;
348         case "short" -> short.class;
349         case "int" -> int.class;
350         case "long" -> long.class;
351         case "float" -> float.class;
352         case "double" -> double.class;
353         case "void" -> void.class;
354         default -> null;
355      };
356   }
357
358   /**
359    * Returns <jk>true</jk> if this format emits a numeric wire value.
360    *
361    * <p>
362    * Always <jk>false</jk> — every constant emits a textual representation.
363    *
364    * @return <jk>false</jk>.
365    */
366   @SuppressWarnings({
367      "java:S3400", // Same rationale — must remain an instance method, not a constant, to match the cross-Format API contract.
368      "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.
369   })
370   public boolean isNumeric() {
371      return false;
372   }
373}