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.commons.utils; 018 019import static org.apache.juneau.commons.reflect.ReflectionUtils.*; 020import static org.apache.juneau.commons.utils.AssertionUtils.*; 021import static org.apache.juneau.commons.utils.Utils.*; 022 023import java.lang.reflect.*; 024import java.util.*; 025import java.util.function.*; 026 027import org.apache.juneau.commons.collections.*; 028import org.apache.juneau.commons.lang.*; 029import org.apache.juneau.commons.reflect.*; 030 031/** 032 * Utility methods for working with classes. 033 * 034 */ 035public class ClassUtils { 036 037 /** 038 * Predicate check to filter out void classes. 039 */ 040 public static final Predicate<Class<?>> NOT_VOID = ClassUtils::isNotVoid; 041 042 @SuppressWarnings("rawtypes") 043 private static Cache<Class,Boolean> MODIFIABLE_COLLECTION_TYPES = Cache.of(Class.class, Boolean.class).build(); 044 045 /** 046 * Determines whether the specified collection supports modification operations (e.g., {@code add()}, {@code remove()}). 047 * 048 * <p> 049 * This method performs a heuristic check based on the collection's class name to determine if it's likely modifiable. 050 * It checks whether the class name contains indicators of immutability such as "Immutable", "Unmodifiable", 051 * or "Arrays$ArrayList" (which represents the unmodifiable list returned by {@link Arrays#asList(Object...)}). 052 * 053 * <p> 054 * Results are cached for performance, so repeated calls for the same collection type are very fast. 055 * 056 * <h5 class='section'>Examples:</h5> 057 * <p class='bjava'> 058 * <jc>// Modifiable collections</jc> 059 * canAddTo(<jk>new</jk> ArrayList<>()); <jc>// true</jc> 060 * canAddTo(<jk>new</jk> LinkedList<>()); <jc>// true</jc> 061 * canAddTo(<jk>new</jk> HashSet<>()); <jc>// true</jc> 062 * 063 * <jc>// Unmodifiable collections</jc> 064 * canAddTo(Collections.unmodifiableList(...)); <jc>// false</jc> 065 * canAddTo(Collections.unmodifiableSet(...)); <jc>// false</jc> 066 * canAddTo(Arrays.asList(<js>"a"</js>, <js>"b"</js>)); <jc>// false</jc> 067 * canAddTo(List.of(<js>"a"</js>, <js>"b"</js>)); <jc>// false (ImmutableCollections)</jc> 068 * </p> 069 * 070 * <p> 071 * <b>Note:</b> This is a heuristic check based on naming conventions. It does not attempt to actually 072 * modify the collection, so it never throws exceptions. However, it may produce false positives for 073 * custom collection implementations with misleading names. 074 * 075 * @param value The collection to check. Must not be <jk>null</jk>. 076 * @return <jk>true</jk> if the collection is likely modifiable, <jk>false</jk> if it's likely unmodifiable. 077 * @throws IllegalArgumentException If value is <jk>null</jk>. 078 */ 079 public static boolean canAddTo(Collection<?> value) { 080 assertArgNotNull("value", value); 081 return canAddTo(value.getClass()); 082 } 083 084 /** 085 * Determines whether the specified map supports modification operations (e.g., {@code put()}, {@code remove()}). 086 * 087 * <p> 088 * This method performs a heuristic check based on the map's class name to determine if it's likely modifiable. 089 * It checks whether the class name contains indicators of immutability such as "Immutable", "Unmodifiable", 090 * or "Arrays$ArrayList" (which represents unmodifiable collections). 091 * 092 * <p> 093 * Results are cached for performance, so repeated calls for the same map type are very fast. 094 * 095 * <h5 class='section'>Examples:</h5> 096 * <p class='bjava'> 097 * <jc>// Modifiable maps</jc> 098 * canPutTo(<jk>new</jk> HashMap<>()); <jc>// true</jc> 099 * canPutTo(<jk>new</jk> LinkedHashMap<>()); <jc>// true</jc> 100 * canPutTo(<jk>new</jk> TreeMap<>()); <jc>// true</jc> 101 * 102 * <jc>// Unmodifiable maps</jc> 103 * canPutTo(Collections.unmodifiableMap(...)); <jc>// false</jc> 104 * canPutTo(Map.of(<js>"key"</js>, <js>"value"</js>)); <jc>// false (ImmutableCollections)</jc> 105 * </p> 106 * 107 * <p> 108 * <b>Note:</b> This is a heuristic check based on naming conventions. It does not attempt to actually 109 * modify the map, so it never throws exceptions. However, it may produce false positives for 110 * custom map implementations with misleading names. 111 * 112 * @param value The map to check. Must not be <jk>null</jk>. 113 * @return <jk>true</jk> if the map is likely modifiable, <jk>false</jk> if it's likely unmodifiable. 114 * @throws IllegalArgumentException If value is <jk>null</jk>. 115 */ 116 public static boolean canPutTo(Map<?,?> value) { 117 assertArgNotNull("value", value); 118 return canAddTo(value.getClass()); 119 } 120 121 /** 122 * Returns the fully-qualified class name for the specified object. 123 * 124 * <p> 125 * This method returns the canonical JVM class name including the full package path. 126 * 127 * <h5 class='section'>Examples:</h5> 128 * <p class='bjava'> 129 * <jc>// Regular classes</jc> 130 * className(String.<jk>class</jk>); <jc>// "java.lang.String"</jc> 131 * className(<jk>new</jk> HashMap<>()); <jc>// "java.util.HashMap"</jc> 132 * 133 * <jc>// Inner classes</jc> 134 * className(Map.Entry.<jk>class</jk>); <jc>// "java.util.Map$Entry"</jc> 135 * 136 * <jc>// Primitives</jc> 137 * className(<jk>int</jk>.<jk>class</jk>); <jc>// "int"</jc> 138 * className(<jk>boolean</jk>.<jk>class</jk>); <jc>// "boolean"</jc> 139 * 140 * <jc>// Arrays</jc> 141 * className(String[].<jk>class</jk>); <jc>// "[Ljava.lang.String;"</jc> 142 * className(<jk>int</jk>[].<jk>class</jk>); <jc>// "[I"</jc> 143 * className(String[][].<jk>class</jk>); <jc>// "[[Ljava.lang.String;"</jc> 144 * 145 * <jc>// Null</jc> 146 * className(<jk>null</jk>); <jc>// null</jc> 147 * </p> 148 * 149 * @param value The object to get the class name for. 150 * @return The name of the class or <jk>null</jk> if the value was null. 151 */ 152 public static String className(Object value) { 153 if (value == null) 154 return null; 155 if (value instanceof Class value2) 156 return value2.getName(); 157 if (value instanceof ClassInfo value2) 158 return value2.getName(); 159 return value.getClass().getName(); 160 } 161 162 /** 163 * Returns the simple (non-qualified) class name for the specified object. 164 * 165 * <p> 166 * This method returns only the simple class name without any package or outer class information. 167 * For inner classes, only the innermost class name is returned. 168 * 169 * <h5 class='section'>Examples:</h5> 170 * <p class='bjava'> 171 * <jc>// Regular classes</jc> 172 * simpleClassName(String.<jk>class</jk>); <jc>// "String"</jc> 173 * simpleClassName(<jk>new</jk> HashMap<>()); <jc>// "HashMap"</jc> 174 * 175 * <jc>// Inner classes</jc> 176 * simpleClassName(Map.Entry.<jk>class</jk>); <jc>// "Entry"</jc> 177 * 178 * <jc>// Primitives</jc> 179 * simpleClassName(<jk>int</jk>.<jk>class</jk>); <jc>// "int"</jc> 180 * simpleClassName(<jk>boolean</jk>.<jk>class</jk>); <jc>// "boolean"</jc> 181 * 182 * <jc>// Arrays</jc> 183 * simpleClassName(String[].<jk>class</jk>); <jc>// "String[]"</jc> 184 * simpleClassName(<jk>int</jk>[].<jk>class</jk>); <jc>// "int[]"</jc> 185 * simpleClassName(String[][].<jk>class</jk>); <jc>// "String[][]"</jc> 186 * 187 * <jc>// Null</jc> 188 * simpleClassName(<jk>null</jk>); <jc>// null</jc> 189 * </p> 190 * 191 * @param value The object to get the simple class name for. 192 * @return The simple name of the class or <jk>null</jk> if the value was null. 193 */ 194 public static String classNameSimple(Object value) { 195 if (value == null) 196 return null; 197 if (value instanceof Class value2) 198 return value2.getSimpleName(); 199 if (value instanceof ClassInfo value2) 200 return value2.getNameSimple(); 201 return value.getClass().getSimpleName(); 202 } 203 204 /** 205 * Returns the simple qualified class name for the specified object. 206 * 207 * <p> 208 * This returns the simple class name including outer class names, but without the package. 209 * Inner class separators ($) are replaced with dots (.). 210 * Array types are properly formatted with brackets. 211 * 212 * <h5 class='section'>Examples:</h5> 213 * <p class='bjava'> 214 * <jc>// Regular classes</jc> 215 * simpleQualifiedClassName(String.<jk>class</jk>); <jc>// "String"</jc> 216 * simpleQualifiedClassName(<jk>new</jk> HashMap<>()); <jc>// "HashMap"</jc> 217 * 218 * <jc>// Inner classes</jc> 219 * simpleQualifiedClassName(Map.Entry.<jk>class</jk>); <jc>// "Map.Entry"</jc> 220 * simpleQualifiedClassName(Outer.Inner.Deep.<jk>class</jk>); <jc>// "Outer.Inner.Deep"</jc> 221 * 222 * <jc>// Primitives</jc> 223 * simpleQualifiedClassName(<jk>int</jk>.<jk>class</jk>); <jc>// "int"</jc> 224 * simpleQualifiedClassName(<jk>boolean</jk>.<jk>class</jk>); <jc>// "boolean"</jc> 225 * 226 * <jc>// Object arrays</jc> 227 * simpleQualifiedClassName(String[].<jk>class</jk>); <jc>// "String[]"</jc> 228 * simpleQualifiedClassName(Map.Entry[].<jk>class</jk>); <jc>// "Map.Entry[]"</jc> 229 * simpleQualifiedClassName(String[][].<jk>class</jk>); <jc>// "String[][]"</jc> 230 * 231 * <jc>// Primitive arrays</jc> 232 * simpleQualifiedClassName(<jk>int</jk>[].<jk>class</jk>); <jc>// "int[]"</jc> 233 * simpleQualifiedClassName(<jk>boolean</jk>[][].<jk>class</jk>); <jc>// "boolean[][]"</jc> 234 * 235 * <jc>// Null</jc> 236 * simpleQualifiedClassName(<jk>null</jk>); <jc>// null</jc> 237 * </p> 238 * 239 * @param value The object to get the simple qualified class name for. 240 * @return The simple qualified name of the class or <jk>null</jk> if the value was null. 241 */ 242 public static String classNameSimpleQualified(Object value) { 243 if (value == null) 244 return null; 245 var clazz = value instanceof Class<?> ? (Class<?>)value : value.getClass(); 246 247 // Handle array types by recursively getting component type 248 if (clazz.isArray()) { 249 return classNameSimpleQualified(clazz.getComponentType()) + "[]"; 250 } 251 252 // Handle non-array types 253 var className = clazz.getName(); 254 var lastDot = className.lastIndexOf('.'); 255 var simpleName = lastDot == -1 ? className : className.substring(lastDot + 1); 256 return simpleName.replace('$', '.'); 257 } 258 259 /** 260 * Returns the class types for the specified arguments. 261 * 262 * @param args The objects we're getting the classes of. 263 * @return The classes of the arguments. 264 */ 265 public static Class<?>[] getClasses(Object...args) { 266 var pt = new Class<?>[args.length]; 267 for (var i = 0; i < args.length; i++) 268 pt[i] = args[i] == null ? null : args[i].getClass(); 269 return pt; 270 } 271 272 /** 273 * Matches arguments to a list of parameter types. 274 * 275 * <p> 276 * This method intelligently matches a variable number of arguments to a fixed set of parameter types, 277 * handling cases where arguments may be provided in a different order, or where some arguments are 278 * missing or extra arguments are provided. This is particularly useful for reflective method/constructor 279 * invocation where parameter order flexibility is desired. 280 * 281 * <h5 class='section'>Matching Rules:</h5> 282 * <ul> 283 * <li>If arguments already match parameter types in order and count, they are returned as-is (fast path) 284 * <li>Otherwise, each parameter type is matched with the first compatible argument 285 * <li>Extra arguments are ignored 286 * <li>Missing parameters are left as <jk>null</jk> 287 * <li>Primitive types are automatically matched with their wrapper equivalents 288 * <li>Type hierarchy is respected (subclasses match parent parameters) 289 * </ul> 290 * 291 * <h5 class='section'>Examples:</h5> 292 * <p class='bjava'> 293 * <jc>// Already in correct order - fast path returns original array</jc> 294 * Class<?>[] types = {String.<jk>class</jk>, Integer.<jk>class</jk>}; 295 * Object[] args = {<js>"hello"</js>, 42}; 296 * Object[] result = getMatchingArgs(types, args); 297 * <jc>// Returns: ["hello", 42]</jc> 298 * 299 * <jc>// Arguments in wrong order - method reorders them</jc> 300 * Class<?>[] types = {Integer.<jk>class</jk>, String.<jk>class</jk>}; 301 * Object[] args = {<js>"hello"</js>, 42}; 302 * Object[] result = getMatchingArgs(types, args); 303 * <jc>// Returns: [42, "hello"]</jc> 304 * 305 * <jc>// Extra arguments are ignored</jc> 306 * Class<?>[] types = {String.<jk>class</jk>}; 307 * Object[] args = {<js>"hello"</js>, 42, <jk>true</jk>}; 308 * Object[] result = getMatchingArgs(types, args); 309 * <jc>// Returns: ["hello"]</jc> 310 * 311 * <jc>// Missing arguments become null</jc> 312 * Class<?>[] types = {String.<jk>class</jk>, Integer.<jk>class</jk>, Boolean.<jk>class</jk>}; 313 * Object[] args = {<js>"hello"</js>}; 314 * Object[] result = getMatchingArgs(types, args); 315 * <jc>// Returns: ["hello", null, null]</jc> 316 * 317 * <jc>// Handles primitive types and their wrappers</jc> 318 * Class<?>[] types = {<jk>int</jk>.<jk>class</jk>, String.<jk>class</jk>}; 319 * Object[] args = {<js>"hello"</js>, 42}; <jc>// Integer object matches int.class</jc> 320 * Object[] result = getMatchingArgs(types, args); 321 * <jc>// Returns: [42, "hello"]</jc> 322 * 323 * <jc>// Respects type hierarchy - subclasses match parent types</jc> 324 * Class<?>[] types = {Number.<jk>class</jk>, String.<jk>class</jk>}; 325 * Object[] args = {<js>"hello"</js>, 42}; <jc>// Integer extends Number</jc> 326 * Object[] result = getMatchingArgs(types, args); 327 * <jc>// Returns: [42, "hello"]</jc> 328 * </p> 329 * 330 * <p> 331 * This method is used internally by {@link ClassInfo}, {@link MethodInfo}, and {@link ConstructorInfo} 332 * to provide flexible parameter matching during reflective invocation. 333 * 334 * @param paramTypes The parameter types to match against. Must not be <jk>null</jk>. 335 * @param args The arguments to match to the parameter types. Can be empty or contain <jk>null</jk> values. 336 * @return 337 * An array of arguments matched to the parameter types. The returned array will always have 338 * the same length as {@code paramTypes}. Returns the original {@code args} array if it already 339 * matches (fast path optimization). 340 */ 341 public static Object[] getMatchingArgs(Class<?>[] paramTypes, Object...args) { 342 assertArgNotNull("args", args); 343 var needsShuffle = paramTypes.length != args.length; 344 if (! needsShuffle) { 345 for (var i = 0; i < paramTypes.length; i++) { 346 if (! paramTypes[i].isInstance(args[i])) 347 needsShuffle = true; 348 } 349 } 350 if (! needsShuffle) 351 return args; 352 var params = new Object[paramTypes.length]; 353 for (var i = 0; i < paramTypes.length; i++) { 354 var pt = info(paramTypes[i]).getWrapperIfPrimitive(); 355 for (var arg : args) { 356 if (nn(arg) && pt.isParentOf(arg.getClass())) { 357 params[i] = arg; 358 break; 359 } 360 } 361 } 362 return params; 363 } 364 365 /** 366 * Attempts to unwrap a proxy object and return the underlying "real" class. 367 * 368 * <p> 369 * This method handles several common proxy types used in Java applications: 370 * <ul> 371 * <li><b>JDK Dynamic Proxies</b> - Created via {@link Proxy#newProxyInstance} 372 * <li><b>CGLIB Proxies</b> - Created by Spring's CGLIB enhancer (class name contains <js>"$$EnhancerBySpringCGLIB$$"</js>) 373 * <li><b>Javassist Proxies</b> - Created by Javassist proxy factory (class name contains <js>"_$$_javassist"</js> or <js>"_$$_jvst"</js>) 374 * <li><b>ByteBuddy Proxies</b> - Created by ByteBuddy (class name contains <js>"$ByteBuddy$"</js>) 375 * </ul> 376 * 377 * <h5 class='section'>Example:</h5> 378 * <p class='bjava'> 379 * <jc>// Get the real class behind a Spring proxy</jc> 380 * <ja>@Service</ja> 381 * <jk>class</jk> MyService {} 382 * 383 * MyService proxy = applicationContext.getBean(MyService.<jk>class</jk>); 384 * Class<?> realClass = ClassUtils.<jsm>getProxyFor</jsm>(proxy); 385 * <jc>// Returns MyService.class instead of the CGLIB proxy class</jc> 386 * </p> 387 * 388 * <h5 class='section'>Notes:</h5> 389 * <ul class='spaced-list'> 390 * <li>For JDK dynamic proxies, returns the first interface implemented by the proxy 391 * <li>For CGLIB/Javassist/ByteBuddy proxies, returns the superclass 392 * <li>For Spring CGLIB proxies with a <c>getTargetClass()</c> method, invokes that method for more accurate results 393 * <li>Returns <jk>null</jk> if the object is not a recognized proxy type 394 * </ul> 395 * 396 * @param o The object to unwrap. Can be <jk>null</jk>. 397 * @return 398 * The underlying class, or <jk>null</jk> if: 399 * <ul> 400 * <li>The object is <jk>null</jk> 401 * <li>The object is not a recognized proxy type 402 * <li>The proxy cannot be unwrapped 403 * </ul> 404 */ 405 public static Class<?> getProxyFor(Object o) { 406 if (o == null) 407 return null; 408 409 var c = o.getClass(); 410 var s = c.getName(); 411 412 // Quick exit: Most classes aren't proxies, and proxies typically have '$' in their name 413 if (s.indexOf('$') == -1) 414 return null; 415 416 // JDK Dynamic Proxy: Created via Proxy.newProxyInstance() 417 // These implement interfaces and are instances of java.lang.reflect.Proxy 418 if (Proxy.isProxyClass(c)) { 419 var interfaces = c.getInterfaces(); 420 return interfaces.length > 0 ? interfaces[0] : null; 421 } 422 423 // CGLIB Proxy: Spring's CGLIB enhancer creates subclasses 424 // Pattern: com.example.MyClass$$EnhancerBySpringCGLIB$$abc123 425 if (s.contains("$$EnhancerBySpringCGLIB$$")) { 426 // Try to invoke getTargetClass() if available (Spring specific) 427 var v = Value.<Class<?>>empty(); 428 info(c).getPublicMethods().stream().filter(m -> m.hasName("getTargetClass") && m.getParameterCount() == 0 && m.hasReturnType(Class.class)).forEach(m -> safe(() -> v.set(m.invoke(o)))); // HTT - Requires bytecode manipulation to create classes with $$EnhancerBySpringCGLIB$$ in name 429 return v.isPresent() ? v.get() : c.getSuperclass(); // HTT - Requires bytecode manipulation to create classes with $$EnhancerBySpringCGLIB$$ in name 430 } 431 432 // Javassist Proxy: Created by Javassist ProxyFactory 433 // Pattern: com.example.MyClass_$$_javassist_123 or com.example.MyClass_$$_jvst123 434 // ByteBuddy Proxy: Created by ByteBuddy framework 435 // Pattern: com.example.MyClass$ByteBuddy$abc123 436 if (s.contains("_$$_javassist") || s.contains("_$$_jvst") || s.contains("$ByteBuddy$")) { 437 return c.getSuperclass(); // HTT - Requires bytecode manipulation to create classes with _$$_javassist, _$$_jvst, or $ByteBuddy$ in name 438 } 439 440 // Not a recognized proxy type 441 return null; 442 } 443 444 /** 445 * Returns <jk>false</jk> if the specific class is <jk>null</jk> or <c><jk>void</jk>.<jk>class</jk></c> or {@link Void} or has the simple name <js>"Void</js>. 446 * 447 * @param c The class to check. 448 * @return <jk>false</jk> if the specific class is <jk>null</jk> or <c><jk>void</jk>.<jk>class</jk></c> or {@link Void} or has the simple name <js>"Void</js>. 449 */ 450 @SuppressWarnings("rawtypes") 451 public static boolean isNotVoid(Class c) { 452 return ! isVoid(c); 453 } 454 455 /** 456 * Returns <jk>true</jk> if the specific class is <jk>null</jk> or <c><jk>void</jk>.<jk>class</jk></c> or {@link Void} or has the simple name <js>"Void</js>. 457 * 458 * @param c The class to check. 459 * @return <jk>true</jk> if the specific class is <jk>null</jk> or <c><jk>void</jk>.<jk>class</jk></c> or {@link Void} or has the simple name <js>"Void</js>. 460 */ 461 @SuppressWarnings("rawtypes") 462 public static boolean isVoid(Class c) { 463 return c == null || c == void.class || c == Void.class || cns(c).equalsIgnoreCase("void"); 464 } 465 466 /** 467 * Attempts to call <code>x.setAccessible(<jk>true</jk>)</code> and quietly ignores security exceptions. 468 * 469 * @param x The constructor. 470 * @return <jk>true</jk> if call was successful. 471 */ 472 public static boolean setAccessible(Constructor<?> x) { 473 assertArgNotNull("x", x); 474 return safeOpt(() -> { 475 x.setAccessible(true); 476 return true; 477 }).orElse(false); 478 } 479 480 /** 481 * Attempts to call <code>x.setAccessible(<jk>true</jk>)</code> and quietly ignores security exceptions. 482 * 483 * @param x The field. 484 * @return <jk>true</jk> if call was successful. 485 */ 486 public static boolean setAccessible(Field x) { 487 assertArgNotNull("x", x); 488 return safeOpt(() -> { 489 x.setAccessible(true); 490 return true; 491 }).orElse(false); 492 } 493 494 /** 495 * Attempts to call <code>x.setAccessible(<jk>true</jk>)</code> and quietly ignores security exceptions. 496 * 497 * @param x The method. 498 * @return <jk>true</jk> if call was successful. 499 */ 500 public static boolean setAccessible(Method x) { 501 assertArgNotNull("x", x); 502 return safeOpt(() -> { 503 x.setAccessible(true); 504 return true; 505 }).orElse(false); 506 } 507 508 /** 509 * Returns the specified type as a <c>Class</c>. 510 * 511 * <p> 512 * If it's already a <c>Class</c>, it just does a cast. 513 * <br>If it's a <c>ParameterizedType</c>, it returns the raw type. 514 * 515 * @param t The type to convert. 516 * @return The type converted to a <c>Class</c>, or <jk>null</jk> if it could not be converted. 517 */ 518 public static Class<?> toClass(Type t) { 519 if (t instanceof Class<?> c) 520 return c; 521 if (t instanceof ParameterizedType t2) { 522 // The raw type should always be a class (right?) 523 return (Class<?>)t2.getRawType(); 524 } 525 return null; 526 } 527 528 private static boolean canAddTo(Class<?> c) { 529 var b = MODIFIABLE_COLLECTION_TYPES.get(c); 530 if (b == null) { 531 var name = c.getName(); 532 b = (! name.contains("Immutable") && ! name.contains("Unmodifiable") && ! name.contains("Arrays$ArrayList")); 533 MODIFIABLE_COLLECTION_TYPES.put(c, b); 534 } 535 return b; 536 } 537}