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}