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.reflect; 018 019import static java.util.stream.Collectors.*; 020import static org.apache.juneau.commons.utils.Shorts.*; 021import static org.apache.juneau.commons.utils.StringUtils.*; 022import static org.apache.juneau.commons.utils.ThrowableUtils.*; 023 024import java.lang.reflect.*; 025import java.util.*; 026 027import org.apache.juneau.commons.inject.*; 028import org.apache.juneau.commons.utils.*; 029 030/** 031 * Lightweight utility class for introspecting information about a Java constructor. 032 * 033 * <p> 034 * This class provides a convenient wrapper around {@link Constructor} that extends the standard Java reflection 035 * API with additional functionality for constructor introspection, annotation handling, and instance creation. 036 * It extends {@link ExecutableInfo} to provide common functionality shared with methods. 037 * 038 * <h5 class='section'>Features:</h5> 039 * <ul class='spaced-list'> 040 * <li>Constructor introspection - access constructor metadata, parameters, exceptions 041 * <li>Annotation support - get annotations declared on the constructor 042 * <li>Instance creation - create new instances with type safety 043 * <li>Accessibility control - make private constructors accessible 044 * <li>Thread-safe - instances are immutable and safe for concurrent access 045 * </ul> 046 * 047 * <h5 class='section'>Use Cases:</h5> 048 * <ul class='spaced-list'> 049 * <li>Introspecting constructor metadata for code generation or analysis 050 * <li>Creating instances of classes dynamically 051 * <li>Finding annotations on constructors 052 * <li>Working with constructor parameters and exceptions 053 * <li>Building frameworks that need to instantiate objects 054 * </ul> 055 * 056 * <h5 class='section'>Usage:</h5> 057 * <p class='bjava'> 058 * <jc>// Get ConstructorInfo from a class</jc> 059 * ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>); 060 * ConstructorInfo <jv>ctor</jv> = <jv>ci</jv>.getConstructor(String.<jk>class</jk>); 061 * 062 * <jc>// Get annotations</jc> 063 * List<AnnotationInfo<MyAnnotation>> <jv>annotations</jv> = 064 * <jv>ctor</jv>.getAnnotations(MyAnnotation.<jk>class</jk>).toList(); 065 * 066 * <jc>// Create instance</jc> 067 * <jv>ctor</jv>.accessible(); <jc>// Make accessible if private</jc> 068 * MyClass <jv>obj</jv> = <jv>ctor</jv>.invoke(<js>"arg"</js>); 069 * </p> 070 * 071 * <h5 class='section'>See Also:</h5><ul> 072 * <li class='jc'>{@link ClassInfo} - Class introspection 073 * <li class='jc'>{@link MethodInfo} - Method introspection 074 * <li class='jc'>{@link FieldInfo} - Field introspection 075 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsReflection">Reflection Package</a> 076 * </ul> 077 */ 078@SuppressWarnings({ 079 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 080 "unchecked" // (T) cast of Constructor.newInstance() in the generic newInstance(): T is chosen by the caller to be the constructor's declaring type 081}) 082public final class ConstructorInfo extends ExecutableInfo implements Comparable<ConstructorInfo>, Annotatable { 083 084 /** 085 * Creates a ConstructorInfo wrapper for the specified constructor. 086 * 087 * <h5 class='section'>Example:</h5> 088 * <p class='bjava'> 089 * ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>); 090 * Constructor<?> <jv>c</jv> = MyClass.<jk>class</jk>.getConstructor(String.<jk>class</jk>); 091 * ConstructorInfo <jv>ci2</jv> = ConstructorInfo.<jsm>of</jsm>(<jv>ci</jv>, <jv>c</jv>); 092 * </p> 093 * 094 * @param declaringClass The ClassInfo for the class that declares this constructor. Must not be <jk>null</jk>. 095 * @param inner The constructor being wrapped. Must not be <jk>null</jk>. 096 * @return A new ConstructorInfo object wrapping the constructor. 097 */ 098 public static ConstructorInfo of(ClassInfo declaringClass, Constructor<?> inner) { 099 reqnn("declaringClass", declaringClass); 100 return declaringClass.getConstructor(inner); 101 } 102 103 /** 104 * Creates a ConstructorInfo wrapper for the specified constructor. 105 * 106 * <p> 107 * This convenience method automatically determines the declaring class from the constructor. 108 * 109 * <h5 class='section'>Example:</h5> 110 * <p class='bjava'> 111 * Constructor<?> <jv>c</jv> = MyClass.<jk>class</jk>.getConstructor(String.<jk>class</jk>); 112 * ConstructorInfo <jv>ci</jv> = ConstructorInfo.<jsm>of</jsm>(<jv>c</jv>); 113 * </p> 114 * 115 * @param inner The constructor being wrapped. Must not be <jk>null</jk>. 116 * @return A new ConstructorInfo object wrapping the constructor. 117 */ 118 public static ConstructorInfo of(Constructor<?> inner) { 119 reqnn("inner", inner); 120 return ClassInfo.of(inner.getDeclaringClass()).getConstructor(inner); 121 } 122 123 private final Constructor<?> inner; 124 125 /** 126 * Constructor. 127 * 128 * <p> 129 * Creates a new ConstructorInfo wrapper for the specified constructor. This constructor is protected 130 * and should not be called directly. Use the static factory methods {@link #of(Constructor)} or 131 * obtain ConstructorInfo instances from {@link ClassInfo#getConstructor(Constructor)}. 132 * 133 * @param declaringClass The ClassInfo for the class that declares this constructor. Must not be <jk>null</jk>. 134 * @param inner The constructor being wrapped. Must not be <jk>null</jk>. 135 */ 136 protected ConstructorInfo(ClassInfo declaringClass, Constructor<?> inner) { 137 super(declaringClass, inner); 138 this.inner = inner; 139 } 140 141 @Override /* Overridden from ExecutableInfo */ 142 public ConstructorInfo accessible() { 143 super.accessible(); 144 return this; 145 } 146 147 @Override 148 public int compareTo(ConstructorInfo o) { 149 int i = cmp(getNameSimple(), o.getNameSimple()); 150 if (i == 0) { 151 i = getParameterCount() - o.getParameterCount(); 152 if (i == 0) { 153 var params = getParameters(); 154 var oParams = o.getParameters(); 155 for (var j = 0; j < params.size() && i == 0; j++) { 156 i = cmp(params.get(j).getParameterType().getName(), oParams.get(j).getParameterType().getName()); 157 } 158 } 159 } 160 return i; 161 } 162 163 @Override /* Annotatable */ 164 public AnnotatableType getAnnotatableType() { return AnnotatableType.CONSTRUCTOR_TYPE; } 165 166 @Override /* Annotatable */ 167 public String getLabel() { return getDeclaringClass().getNameSimple() + "." + getNameShort(); } 168 169 /** 170 * Returns the wrapped constructor. 171 * 172 * <h5 class='section'>Example:</h5> 173 * <p class='bjava'> 174 * ConstructorInfo <jv>ci</jv> = ...; 175 * Constructor<MyClass> <jv>ctor</jv> = <jv>ci</jv>.inner(); 176 * </p> 177 * 178 * @param <T> The class type of the constructor. 179 * @return The wrapped constructor. 180 */ 181 public <T> Constructor<T> inner() { 182 return (Constructor<T>)inner; 183 } 184 185 /** 186 * Compares this ConstructorInfo with the specified object for equality. 187 * 188 * <p> 189 * Two ConstructorInfo objects are considered equal if they wrap the same underlying {@link Constructor} object. 190 * This delegates to the underlying {@link Constructor#equals(Object)} method. 191 * 192 * <p> 193 * This method makes ConstructorInfo suitable for use as keys in hash-based collections such as {@link HashMap} 194 * and {@link HashSet}. 195 * 196 * @param obj The object to compare with. 197 * @return <jk>true</jk> if the objects are equal, <jk>false</jk> otherwise. 198 */ 199 @Override 200 public boolean equals(Object obj) { 201 return obj instanceof ConstructorInfo obj2 && eq(this, obj2, (x, y) -> eq(x.inner, y.inner)); 202 } 203 204 /** 205 * Returns a hash code value for this ConstructorInfo. 206 * 207 * <p> 208 * This delegates to the underlying {@link Constructor#hashCode()} method. 209 * 210 * <p> 211 * This method makes ConstructorInfo suitable for use as keys in hash-based collections such as {@link HashMap} 212 * and {@link HashSet}. 213 * 214 * @return A hash code value for this ConstructorInfo. 215 */ 216 @Override 217 public int hashCode() { 218 return inner.hashCode(); 219 } 220 221 /** 222 * Shortcut for calling the new-instance method on the underlying constructor. 223 * 224 * <p> 225 * If the number of formal parameters required by the underlying constructor is 0, the supplied <c>args</c> array may be of length 0 or <jk>null</jk>. 226 * 227 * @param <T> The constructor class type. 228 * @param args The arguments used for the constructor call. Can be <jk>null</jk> or empty for constructors with no parameters. 229 * @return The object returned from the constructor. 230 * @throws ExecutableException Exception occurred on invoked constructor/method/field. 231 */ 232 public <T> T newInstance(Object...args) throws ExecutableException { 233 return safe(() -> { 234 try { 235 return (T)inner.newInstance(args); 236 } catch (InvocationTargetException e) { 237 throw exex(e.getTargetException()); 238 } 239 }, Shorts::exex); // HTT 240 } 241 242 /** 243 * Shortcut for calling the new-instance method on the underlying constructor using lenient argument matching. 244 * 245 * <p> 246 * Lenient matching allows arguments to be matched to parameters based on parameter types. 247 * <br>Arguments can be in any order. 248 * <br>Extra arguments are ignored. 249 * <br>Missing arguments are set to <jk>null</jk>. 250 * 251 * @param <T> The constructor class type. 252 * @param args The arguments used for the constructor call. 253 * @return The object returned from the constructor. 254 * @throws ExecutableException Exception occurred on invoked constructor/method/field. 255 */ 256 public <T> T newInstanceLenient(Object...args) throws ExecutableException { 257 return newInstance(ClassUtils.getMatchingArgs(inner.getParameterTypes(), args)); 258 } 259 260 /** 261 * Returns comma-delimited list of missing parameter types. 262 * 263 * <p> 264 * Analyzes the parameters of this constructor and checks if all required beans are available 265 * in the bean store or in the <c>otherBeans</c> parameter. 266 * 267 * <p> 268 * The following parameter types are considered optional and are not checked: 269 * <ul> 270 * <li>Parameters of type <c>Optional<T></c> 271 * <li>Parameters of type <c>T[]</c>, <c>List<T></c>, <c>Set<T></c>, or <c>Map<String,T></c> 272 * <li>The first parameter if it matches the <c>enclosingInstance</c> object type (for non-static inner classes) 273 * </ul> 274 * 275 * <p> 276 * If a parameter has a <ja>@Named</ja> or {@code @Qualifier} annotation, 277 * the method checks for a bean with that specific name in the bean store. Otherwise, it checks for an unnamed bean 278 * of the parameter type in the bean store, and if not found, checks the <c>otherBeans</c> parameter. 279 * 280 * @param beanStore The bean store to check for beans. 281 * @param enclosingInstance The outer class instance for non-static inner class constructors. 282 * If the first parameter type matches this object's type, it is used as the first parameter and not checked in the bean store. 283 * Can be <jk>null</jk> for regular classes or static inner classes. 284 * @param otherBeans Optional additional bean instances to check if not found in the bean store. 285 * These are checked after the bean store but before marking the parameter as missing. 286 * @return A comma-delimited, sorted list of missing parameter types (e.g., <js>"String,Integer"</js>), 287 * or <jk>null</jk> if all required parameters are available. 288 */ 289 public String getMissingParameterTypes(BeanStore beanStore, Object enclosingInstance, Object... otherBeans) { 290 // @formatter:off 291 return nullIfEmpty( 292 getParameters() 293 .stream() 294 .map(x -> { 295 var pt = x.getParameterType(); 296 if (x.getIndex() == 0 && nn(enclosingInstance) && pt.isInstance(enclosingInstance)) 297 return null; 298 return x.getMissingType(beanStore, otherBeans); 299 }) 300 .filter(Objects::nonNull) 301 .sorted() 302 .collect(joining(",")) 303 ); 304 // @formatter:on 305 } 306 307 /** 308 * Checks if all constructor parameters can be resolved. 309 * 310 * <p> 311 * This method performs the same checks as {@link #getMissingParameterTypes(BeanStore, Object, Object...)} but 312 * returns a boolean instead of a list of missing types. 313 * 314 * <p> 315 * The following parameter types are considered optional and are not checked: 316 * <ul> 317 * <li>Parameters of type <c>Optional<T></c> 318 * <li>Parameters of type <c>T[]</c>, <c>List<T></c>, <c>Set<T></c>, or <c>Map<String,T></c> 319 * <li>The first parameter if it matches the <c>enclosingInstance</c> object type (for non-static inner classes) 320 * </ul> 321 * 322 * <p> 323 * If a parameter has a <ja>@Named</ja> or {@code @Qualifier} annotation, 324 * the method checks for a bean with that specific name in the bean store. Otherwise, it checks for an unnamed bean 325 * of the parameter type in the bean store, and if not found, checks the <c>otherBeans</c> parameter. 326 * 327 * @param beanStore The bean store to check for beans. 328 * @param enclosingInstance The outer class instance for non-static inner class constructors. 329 * If the first parameter type matches this object's type, it is used as the first parameter and not checked in the bean store. 330 * Can be <jk>null</jk> for regular classes or static inner classes. 331 * @param otherBeans Optional additional bean instances to check if not found in the bean store. 332 * These are checked after the bean store but before marking the parameter as missing. 333 * @return <jk>true</jk> if all required parameters are available in the bean store or <c>otherBeans</c>, <jk>false</jk> otherwise. 334 */ 335 public boolean canResolveAllParameters(BeanStore beanStore, Object enclosingInstance, Object... otherBeans) { 336 return getParameters().stream().map(x -> { 337 var pt = x.getParameterType(); 338 if (x.getIndex() == 0 && nn(enclosingInstance) && pt.isInstance(enclosingInstance)) 339 return true; 340 return x.canResolve(beanStore, otherBeans); 341 }).allMatch(x -> x); 342 } 343 344 /** 345 * Resolves parameters from the bean store and invokes the constructor. 346 * 347 * <p> 348 * For each parameter in the constructor, this method: 349 * <ul> 350 * <li>If the first parameter type matches the <c>enclosingInstance</c> object type, uses the <c>enclosingInstance</c> object 351 * (for non-static inner class constructors). 352 * <li>If the parameter is a collection/array/map type, collects all beans of the element type. 353 * <li>Otherwise, looks up a single bean by type and optional qualifier name in the bean store. 354 * <li>If not found in the bean store, checks the <c>otherBeans</c> parameter. 355 * </ul> 356 * 357 * <h5 class='section'>Parameter Resolution:</h5> 358 * <ul class='spaced-list'> 359 * <li><b>Single beans</b> - Resolved using {@link BeanStore#getBean(Class)} or {@link BeanStore#getBean(Class, String)}. 360 * If not found, checks <c>otherBeans</c> for a compatible instance. 361 * Throws {@link ExecutableException} if not found in either location. 362 * <li><b>Optional beans</b> - Wrapped in <c>Optional</c>, or <c>Optional.empty()</c> if not found. 363 * Never throws an exception. 364 * <li><b>Arrays</b> - All beans of the element type are collected into an array (may be empty). 365 * Never throws an exception. 366 * <li><b>Lists</b> - All beans of the element type are collected into a <c>List</c> (may be empty). 367 * Never throws an exception. 368 * <li><b>Sets</b> - All beans of the element type are collected into a <c>LinkedHashSet</c> (may be empty). 369 * Never throws an exception. 370 * <li><b>Maps</b> - All beans of the value type are collected into a <c>LinkedHashMap</c> keyed by bean name (may be empty). 371 * Unnamed beans use an empty string as the key. Never throws an exception. 372 * </ul> 373 * 374 * <p> 375 * The <c>enclosingInstance</c> parameter is used for non-static inner class constructors. If the first parameter type 376 * matches this object's type, it is used as the first parameter value. For regular constructors, this parameter 377 * is ignored if it doesn't match the first parameter type. 378 * 379 * @param <T> The return type of the constructor. 380 * @param beanStore The bean store to resolve parameters from. 381 * @param enclosingInstance The outer class instance for non-static inner classes (can be <jk>null</jk>). 382 * @param otherBeans Optional additional bean instances to use if not found in the bean store. 383 * @return The result of invoking the constructor. 384 * @throws ExecutableException If the constructor cannot be invoked or parameter resolution fails. 385 */ 386 public <T> T inject(BeanStore beanStore, Object enclosingInstance, Object... otherBeans) { 387 var params = getParameters().stream().map(x -> x.resolveValue(beanStore, enclosingInstance, otherBeans)).toArray(); 388 return accessible().newInstance(params); 389 } 390 391 /** 392 * Resolves all constructor parameters from the bean store. 393 * 394 * <p> 395 * This is a package-private helper method used by tests. For normal usage, use {@link #inject(BeanStore, Object, Object...)}. 396 * 397 * @param beanStore The bean store to resolve beans from. 398 * @param enclosingInstance The outer class instance for non-static inner class constructors. 399 * @param otherBeans Optional additional bean instances to use if not found in the bean store. 400 * @return An array of parameter values in the same order as the constructor parameters. 401 * @throws ExecutableException If a required parameter cannot be resolved. 402 */ 403 Object[] resolveParameters(BeanStore beanStore, Object enclosingInstance, Object... otherBeans) { 404 return getParameters().stream().map(x -> x.resolveValue(beanStore, enclosingInstance, otherBeans)).toArray(); 405 } 406}