CachedConstructorAnalyzer.java
/*
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.jersey.inject.weld.internal.injector;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.logging.Logger;
import javax.enterprise.inject.InjectionException;
import javax.inject.Inject;
import org.glassfish.jersey.internal.LocalizationMessages;
import org.glassfish.jersey.internal.util.collection.LazyValue;
import org.glassfish.jersey.internal.util.collection.Value;
import org.glassfish.jersey.internal.util.collection.Values;
/**
* Processes a provided class and selects the valid constructor with the largest number of parameters. Constructor is cached
* for a later retrieve.
*/
public class CachedConstructorAnalyzer<T> {
private static final Logger LOGGER = Logger.getLogger(CachedConstructorAnalyzer.class.getName());
private final LazyValue<Constructor<T>> constructor;
private final Collection<Class<? extends Annotation>> resolverAnnotations;
/**
* Creates a new constructor analyzer which accepts the class that is analyzed.
*
* @param clazz analyzed class.
* @param annotations all annotations used for an injecting.
*/
public CachedConstructorAnalyzer(Class<T> clazz, Collection<Class<? extends Annotation>> annotations) {
this.resolverAnnotations = annotations;
this.constructor = Values.lazy((Value<Constructor<T>>) () -> getConstructorInternal(clazz));
}
public Constructor<T> getConstructor() {
return constructor.get();
}
public boolean hasCompatibleConstructor() {
try {
return constructor.get() != null;
} catch (InjectionException ex) {
// Compatible constructor was not found.
return false;
}
}
/**
* Select the proper constructor of the given {@code clazz}.
*
* @return compatible and largest constructor.
*/
@SuppressWarnings("unchecked")
private Constructor<T> getConstructorInternal(Class<T> clazz) {
if (clazz.isLocalClass()) {
throw new InjectionException(
LocalizationMessages.INJECTION_ERROR_LOCAL_CLASS_NOT_SUPPORTED(clazz.getName()));
}
if (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers())) {
throw new InjectionException(
LocalizationMessages.INJECTION_ERROR_NONSTATIC_MEMBER_CLASS_NOT_SUPPORTED(clazz.getName()));
}
// At this point, we simply need to find the constructor with the largest number of parameters
Constructor<?>[] constructors = AccessController.doPrivileged(
(PrivilegedAction<Constructor<?>[]>) clazz::getDeclaredConstructors);
Constructor<?> selected = null;
int selectedSize = 0;
int maxParams = -1;
for (Constructor<?> constructor : constructors) {
Class<?>[] params = constructor.getParameterTypes();
if (params.length >= maxParams && isCompatible(constructor)) {
if (params.length > maxParams) {
maxParams = params.length;
selectedSize = 0;
}
selected = constructor;
selectedSize++;
}
}
if (selectedSize == 0) {
throw new InjectionException(LocalizationMessages.INJECTION_ERROR_SUITABLE_CONSTRUCTOR_NOT_FOUND(clazz.getName()));
}
if (selectedSize > 1) {
// Found {0} constructors with {1} parameters in {2} class. Selecting the first found constructor: {3}
LOGGER.warning(LocalizationMessages.MULTIPLE_MATCHING_CONSTRUCTORS_FOUND(
selectedSize, maxParams, clazz.getName(), selected.toGenericString()));
}
return (Constructor<T>) selected;
}
/**
* Checks whether the constructor is valid for injection that means that all parameters has an injection annotation.
*
* @param constructor constructor to inject.
* @return True if element contains at least one inject annotation.
*/
@SuppressWarnings("MagicConstant")
private boolean isCompatible(Constructor<?> constructor) {
if (constructor.getAnnotation(Inject.class) != null) {
// JSR-330 applicable
return true;
}
int paramSize = constructor.getParameterTypes().length;
if (paramSize != 0 && resolverAnnotations.isEmpty()) {
return false;
}
if (!Modifier.isPublic(constructor.getModifiers())) {
// return true for a default constructor, return false otherwise.
return paramSize == 0
&& (constructor.getDeclaringClass().getModifiers()
& (Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE)) == constructor.getModifiers();
}
for (Annotation[] paramAnnotations : constructor.getParameterAnnotations()) {
boolean found = false;
for (Annotation paramAnnotation : paramAnnotations) {
if (resolverAnnotations.contains(paramAnnotation.annotationType())) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
}
}