Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.micronaut.context.AbstractBeanResolutionContext Maven / Gradle / Ivy
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.context;
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.env.CachedEnvironment;
import io.micronaut.context.annotation.InjectScope;
import io.micronaut.context.env.ConfigurationPath;
import io.micronaut.context.exceptions.CircularDependencyException;
import io.micronaut.context.exceptions.DependencyInjectionException;
import io.micronaut.context.scope.CustomScope;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.bind.annotation.Bindable;
import io.micronaut.core.convert.ArgumentConversionContext;
import io.micronaut.core.convert.ConversionContext;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.naming.Named;
import io.micronaut.core.type.Argument;
import io.micronaut.core.type.ArgumentCoercible;
import io.micronaut.core.type.TypeInformation;
import io.micronaut.core.type.TypeInformation.TypeFormat;
import io.micronaut.core.util.AnsiColour;
import io.micronaut.core.util.ObjectUtils;
import io.micronaut.inject.*;
import io.micronaut.inject.proxy.InterceptedBean;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Default implementation of the {@link BeanResolutionContext} interface.
*
* @author Graeme Rocher
* @since 1.2.3
*/
@Internal
public abstract class AbstractBeanResolutionContext implements BeanResolutionContext {
private static final String CONSTRUCTOR_METHOD_NAME = "";
protected final DefaultBeanContext context;
protected final BeanDefinition> rootDefinition;
protected final Path path;
private final @NonNull BeanResolutionTraceMode traceMode;
private final boolean traceEnabled;
private Map attributes;
private Qualifier> qualifier;
private List> dependentBeans;
private BeanRegistration> dependentFactory;
private ConfigurationPath configurationPath;
/**
* @param context The bean context
* @param rootDefinition The bean root definition
*/
@Internal
protected AbstractBeanResolutionContext(DefaultBeanContext context, BeanDefinition> rootDefinition) {
this.context = context;
this.rootDefinition = rootDefinition;
this.path = new DefaultPath();
this.traceMode = context.traceMode;
this.traceEnabled = rootDefinition != null && isTraceEnabled(rootDefinition.getBeanType().getTypeName(), context.tracePatterns);
}
@Override
public ConfigurationPath getConfigurationPath() {
if (configurationPath != null) {
return configurationPath;
} else {
this.configurationPath = ConfigurationPath.newPath();
return configurationPath;
}
}
@Override
public ConfigurationPath setConfigurationPath(ConfigurationPath configurationPath) {
ConfigurationPath old = this.configurationPath;
this.configurationPath = configurationPath;
return old;
}
@Override
public void valueResolved(Argument> argument, Qualifier> qualifier, String property, Object value) {
if (traceEnabled) {
traceMode.getTracer().ifPresent(tracer ->
tracer.traceValueResolved(
this,
(Argument) argument,
property,
value
)
);
}
}
private boolean isTraceEnabled(@NonNull String typeName, @NonNull Set tracePatterns) {
return traceMode != BeanResolutionTraceMode.NONE &&
(tracePatterns.isEmpty() || tracePatterns.stream().anyMatch(typeName::matches));
}
@Override
public Object resolvePropertyValue(Argument> argument, String stringValue, String cliProperty, boolean isPlaceholder) {
ApplicationContext applicationContext = (ApplicationContext) context;
Argument> argumentType = argument;
Class> wrapperType = null;
Class> type = argument.getType();
if (type == Optional.class) {
wrapperType = Optional.class;
argumentType = argument.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
} else if (type == OptionalInt.class) {
wrapperType = OptionalInt.class;
argumentType = Argument.INT;
} else if (type == OptionalLong.class) {
wrapperType = OptionalLong.class;
argumentType = Argument.LONG;
} else if (type == OptionalDouble.class) {
wrapperType = OptionalDouble.class;
argumentType = Argument.DOUBLE;
}
ArgumentConversionContext> conversionContext = wrapperType != null ? ConversionContext.of(argumentType) : ConversionContext.of(argument);
Optional> value;
if (isPlaceholder) {
value = applicationContext.resolvePlaceholders(stringValue).flatMap(v -> applicationContext.getConversionService().convert(v, conversionContext));
} else {
stringValue = substituteWildCards(stringValue);
value = applicationContext.getProperty(stringValue, conversionContext);
if (value.isEmpty() && cliProperty != null) {
value = applicationContext.getProperty(cliProperty, conversionContext);
}
}
if (traceEnabled) {
String propertyName = stringValue;
Object propertyValue = value.orElse(null);
traceMode.getTracer().ifPresent(tracer ->
tracer.traceValueResolved(
this,
(Argument super Object>) argument,
propertyName,
propertyValue
));
}
if (argument.isOptional()) {
if (value.isEmpty()) {
return value;
} else {
Object convertedOptional = value.get();
if (convertedOptional instanceof Optional) {
return convertedOptional;
} else {
return value;
}
}
} else {
if (wrapperType != null) {
final Object v = value.orElse(null);
if (OptionalInt.class == wrapperType) {
return v instanceof Integer i ? OptionalInt.of(i) : OptionalInt.empty();
} else if (OptionalLong.class == wrapperType) {
return v instanceof Long l ? OptionalLong.of(l) : OptionalLong.empty();
} else if (OptionalDouble.class == wrapperType) {
return v instanceof Double d ? OptionalDouble.of(d) : OptionalDouble.empty();
}
}
if (value.isPresent()) {
return value.get();
} else {
if (argument.isDeclaredNullable()) {
return null;
}
String finalStringValue = stringValue;
return argument.getAnnotationMetadata().getValue(Bindable.class, "defaultValue", argument)
.orElseThrow(() -> DependencyInjectionException.missingProperty(this, conversionContext, finalStringValue));
}
}
}
private String substituteWildCards(String valString) {
ConfigurationPath configurationPath = getConfigurationPath();
if (configurationPath.isNotEmpty()) {
return configurationPath.resolveValue(valString);
}
return valString;
}
@NonNull
@Override
public T getBean(@NonNull Argument beanType, @Nullable Qualifier qualifier) {
T bean = context.getBean(this, beanType, qualifier);
if (traceEnabled) {
traceMode.getTracer().ifPresent(tracer -> {
tracer.traceBeanResolved(
this,
beanType,
qualifier,
bean
);
String disabledBeanMessage = context.resolveDisabledBeanMessage(
this,
beanType,
qualifier
);
if (disabledBeanMessage != null) {
tracer.traceBeanDisabled(
AbstractBeanResolutionContext.this,
beanType,
qualifier,
disabledBeanMessage
);
}
});
}
return bean;
}
@NonNull
@Override
public Collection getBeansOfType(@NonNull Argument beanType, @Nullable Qualifier qualifier) {
Collection beans = context.getBeansOfType(this, beanType, qualifier);
if (traceEnabled) {
traceBeanCollection(beanType, qualifier, beans);
}
return beans;
}
private void traceBeanCollection(Argument beanType, Qualifier qualifier, Collection beans) {
traceMode.getTracer().ifPresent(tracer -> {
for (T bean : beans) {
tracer.traceBeanResolved(this, beanType, qualifier, bean);
}
String disabledBeanMessage = context.resolveDisabledBeanMessage(
this,
beanType,
qualifier
);
if (disabledBeanMessage != null) {
tracer.traceBeanDisabled(
AbstractBeanResolutionContext.this,
beanType,
qualifier,
disabledBeanMessage
);
}
});
}
@NonNull
@Override
public Stream streamOfType(@NonNull Argument beanType, @Nullable Qualifier qualifier) {
return context.streamOfType(this, beanType, qualifier);
}
@Override
public Map mapOfType(Argument beanType, Qualifier qualifier) {
Map beanMap = context.mapOfType(this, beanType, qualifier);
if (traceEnabled) {
traceBeanCollection(beanType, qualifier, beanMap.values());
}
return beanMap;
}
@NonNull
@Override
public Optional findBean(@NonNull Argument beanType, @Nullable Qualifier qualifier) {
Optional resolved = context.findBean(this, beanType, qualifier);
if (traceEnabled) {
traceMode.getTracer().ifPresent(tracer -> {
tracer.traceBeanResolved(
this,
beanType,
qualifier,
resolved.orElse(null)
);
String disabledBeanMessage = context.resolveDisabledBeanMessage(
this,
beanType,
qualifier
);
if (disabledBeanMessage != null) {
tracer.traceBeanDisabled(
AbstractBeanResolutionContext.this,
beanType,
qualifier,
disabledBeanMessage
);
}
});
}
return resolved;
}
@NonNull
@Override
public Collection> getBeanRegistrations(@NonNull Argument beanType, @Nullable Qualifier qualifier) {
Collection> registrations = context.getBeanRegistrations(this, beanType, qualifier);
if (traceEnabled) {
traceBeanCollection(
beanType,
qualifier,
registrations.stream().map(BeanRegistration::getBean).collect(Collectors.toList())
);
}
return registrations;
}
/**
* Copy the state from a previous resolution context.
*
* @param context The previous context
*/
public void copyStateFrom(@NonNull AbstractBeanResolutionContext context) {
path.addAll(context.path);
qualifier = context.qualifier;
if (context.attributes != null) {
getAttributesOrCreate().putAll(context.attributes);
}
}
@Override
public void addDependentBean(BeanRegistration beanRegistration) {
if (beanRegistration.getBeanDefinition() == rootDefinition) {
// Don't add self
return;
}
if (dependentBeans == null) {
dependentBeans = new ArrayList<>(3);
}
dependentBeans.add(beanRegistration);
}
@Override
public void destroyInjectScopedBeans() {
final CustomScope> injectScope = context.getCustomScopeRegistry()
.findScope(InjectScope.class.getName())
.orElse(null);
if (injectScope instanceof LifeCycle> cycle) {
cycle.stop();
}
}
@NonNull
@Override
public List> getAndResetDependentBeans() {
if (dependentBeans == null) {
return Collections.emptyList();
}
final List> registrations = Collections.unmodifiableList(dependentBeans);
dependentBeans = null;
return registrations;
}
@Override
public void markDependentAsFactory() {
if (dependentBeans != null) {
if (dependentBeans.isEmpty()) {
return;
}
if (dependentBeans.size() != 1) {
throw new IllegalStateException("Expected only one bean dependent!");
}
dependentFactory = dependentBeans.remove(0);
}
}
@Override
public BeanRegistration> getAndResetDependentFactoryBean() {
BeanRegistration> result = this.dependentFactory;
this.dependentFactory = null;
return result;
}
@Override
public List> popDependentBeans() {
List> result = this.dependentBeans;
this.dependentBeans = null;
return result;
}
@Override
public void pushDependentBeans(List> dependentBeans) {
if (this.dependentBeans != null && !this.dependentBeans.isEmpty()) {
throw new IllegalStateException("Found existing dependent beans!");
}
this.dependentBeans = dependentBeans;
}
@Override
public final BeanContext getContext() {
return context;
}
@Override
public final BeanDefinition getRootDefinition() {
return rootDefinition;
}
@Override
public final Path getPath() {
return path;
}
@Override
public final Object setAttribute(CharSequence key, Object value) {
return getAttributesOrCreate().put(key, value);
}
/**
* @param key The key
* @return The attribute value
*/
@Override
public final Object getAttribute(CharSequence key) {
if (attributes == null) {
return null;
}
return attributes.get(key);
}
@Override
public final Object removeAttribute(CharSequence key) {
if (attributes != null && key != null) {
return attributes.remove(key);
}
return null;
}
@Override
public Map getAttributes() {
return attributes;
}
@Override
public void setAttributes(Map attributes) {
this.attributes = attributes;
}
@Nullable
@Override
public Qualifier> getCurrentQualifier() {
return qualifier;
}
@Override
public void setCurrentQualifier(@Nullable Qualifier> qualifier) {
this.qualifier = qualifier;
}
@Override
public Optional get(CharSequence name, ArgumentConversionContext conversionContext) {
if (attributes == null) {
return Optional.empty();
}
Object value = attributes.get(name);
if (value != null && conversionContext.getArgument().getType().isInstance(value)) {
return Optional.of((T) value);
}
return Optional.empty();
}
@Override
public Optional get(CharSequence name, Class requiredType) {
if (attributes == null) {
return Optional.empty();
}
Object value = attributes.get(name);
if (requiredType.isInstance(value)) {
return Optional.of((T) value);
}
return Optional.empty();
}
protected void onNewSegment(Segment, ?> segment) {
//no-op
}
@NonNull
private Map getAttributesOrCreate() {
if (attributes == null) {
attributes = new LinkedHashMap<>(2);
}
return attributes;
}
/**
* Class that represents a default path.
*/
class DefaultPath extends LinkedList> implements Path {
public static final String RIGHT_ARROW = "\\---> ";
public static final String RIGHT_ARROW_EMOJI = " ↪️ ";
private static final String CIRCULAR_ERROR_MSG = "Circular dependency detected";
DefaultPath() {
}
@Override
public String toConsoleString(boolean ansiSupported) {
Iterator> i = descendingIterator();
String ls = CachedEnvironment.getProperty("line.separator");
StringBuilder pathString = new StringBuilder().append(ls);
String spaces = "";
while (i.hasNext()) {
pathString.append(i.next().toString());
if (i.hasNext()) {
pathString
.append(ls)
.append(spaces)
.append(ansiSupported ? RIGHT_ARROW_EMOJI : RIGHT_ARROW);
spaces += " ";
}
}
return pathString.toString();
}
@Override
public String toString() {
return toConsoleString(false);
}
@Override
public String toCircularString() {
return toConsoleCircularString(false);
}
@SuppressWarnings("MagicNumber")
@Override
public String toConsoleCircularString(boolean ansiSupported) {
Iterator> i = descendingIterator();
StringBuilder pathString = new StringBuilder();
String ls = CachedEnvironment.getProperty("line.separator");
// Try finding an actual cycle, cycleI is index where the cycle starts
int cycleIndex = lastIndexOf(iterator().next());
if (cycleIndex > 0) {
cycleIndex = size() - cycleIndex;
} else {
cycleIndex = 0;
}
String spaces = "";
int index = 0;
// The last element ends the cycle and is repeated in the path, so we skip it
// and point to an already present element instead
while (i.hasNext() && index < size() - 1) {
String segmentString = i.next().toString();
if (index == cycleIndex) {
pathString.append(ls).append(spaces).append("^").append(" ")
.append(ansiSupported ? RIGHT_ARROW_EMOJI : RIGHT_ARROW);
spaces = spaces + "| ";
} else if (index != 0) {
pathString
.append(ls)
.append(spaces)
.append(ansiSupported ? RIGHT_ARROW_EMOJI : RIGHT_ARROW);
}
pathString.append(segmentString);
spaces = spaces + " ";
++index;
}
String dashes = String.join("", Collections.nCopies(spaces.length() - spaces.indexOf("|") - 1, "-"));
pathString
.append(ls).append(spaces).append("|")
.append(ls).append(spaces, 0, spaces.indexOf("|"))
.append("+").append(dashes).append("+");
return pathString.toString();
}
@Override
public Optional> currentSegment() {
return Optional.ofNullable(peek());
}
@Override
public Path pushConstructorResolve(BeanDefinition declaringType, Argument argument) {
ConstructorInjectionPoint> constructor = declaringType.getConstructor();
if (constructor instanceof MethodInjectionPoint, ?> methodInjectionPoint) {
return pushConstructorResolve(declaringType, methodInjectionPoint.getName(), argument, constructor.getArguments());
}
return pushConstructorResolve(declaringType, CONSTRUCTOR_METHOD_NAME, argument, constructor.getArguments());
}
@Override
public Path pushConstructorResolve(BeanDefinition declaringType, String methodName, Argument argument, Argument[] arguments) {
try {
if (CONSTRUCTOR_METHOD_NAME.equals(methodName)) {
ConstructorSegment constructorSegment = new ConstructorArgumentSegment(declaringType, (Qualifier) getCurrentQualifier(), methodName, argument, arguments);
detectCircularDependency(declaringType, argument, constructorSegment);
} else {
Segment, ?> previous = peek();
MethodSegment, ?> methodSegment = new MethodArgumentSegment(declaringType, (Qualifier) getCurrentQualifier(), methodName, argument, arguments, previous instanceof MethodSegment ms ? ms : null);
if (contains(methodSegment)) {
push(methodSegment);
throw new CircularDependencyException(AbstractBeanResolutionContext.this, argument, CIRCULAR_ERROR_MSG);
} else {
push(methodSegment);
}
}
} finally {
traceResolution();
}
return this;
}
@Override
public Path pushBeanCreate(BeanDefinition> declaringType, Argument> beanType) {
if (traceEnabled) {
traceMode.getTracer().ifPresent(tracer -> {
tracer.traceBeanCreation(
AbstractBeanResolutionContext.this,
declaringType,
beanType
);
});
}
return pushConstructorResolve(declaringType, beanType);
}
private void traceResolution() {
if (traceEnabled) {
getPath().currentSegment().ifPresent(segment -> traceMode.getTracer().ifPresent(tracer -> {
tracer.traceInjectBean(
AbstractBeanResolutionContext.this,
segment
);
}));
}
}
@Override
public Path pushMethodArgumentResolve(BeanDefinition declaringType, MethodInjectionPoint methodInjectionPoint, Argument argument) {
try {
Segment, ?> previous = peek();
MethodSegment, ?> methodSegment = new MethodArgumentSegment(declaringType, (Qualifier) getCurrentQualifier(), methodInjectionPoint.getName(), argument,
methodInjectionPoint.getArguments(), previous instanceof MethodSegment ms ? ms : null);
if (contains(methodSegment)) {
push(methodSegment);
throw new CircularDependencyException(AbstractBeanResolutionContext.this, methodInjectionPoint, argument, CIRCULAR_ERROR_MSG);
} else {
push(methodSegment);
}
} finally {
traceResolution();
}
return this;
}
@Override
public Path pushMethodArgumentResolve(BeanDefinition declaringType, String methodName, Argument argument, Argument[] arguments) {
try {
Segment, ?> previous = peek();
MethodSegment, ?> methodSegment = new MethodArgumentSegment(declaringType, (Qualifier) getCurrentQualifier(), methodName, argument, arguments, previous instanceof MethodSegment ms ? ms : null);
if (contains(methodSegment)) {
push(methodSegment);
throw new CircularDependencyException(AbstractBeanResolutionContext.this, declaringType, methodName, argument, CIRCULAR_ERROR_MSG);
} else {
push(methodSegment);
}
} finally {
traceResolution();
}
return this;
}
@Override
public Path pushEventListenerResolve(BeanDefinition> declaringType, Argument> eventType) {
try {
EventListenerSegment, ?> segment = new EventListenerSegment<>(
declaringType,
eventType
);
if (contains(segment)) {
push(segment);
throw new CircularDependencyException(
AbstractBeanResolutionContext.this,
eventType,
CIRCULAR_ERROR_MSG
);
} else {
push(segment);
}
} finally {
traceResolution();
}
return this;
}
@Override
public Path pushFieldResolve(BeanDefinition declaringType, FieldInjectionPoint fieldInjectionPoint) {
try {
FieldSegment, ?> fieldSegment = new FieldSegment<>(declaringType, getCurrentQualifier(), fieldInjectionPoint.asArgument());
if (contains(fieldSegment)) {
push(fieldSegment);
throw new CircularDependencyException(AbstractBeanResolutionContext.this, fieldInjectionPoint, CIRCULAR_ERROR_MSG);
} else {
push(fieldSegment);
}
} finally {
traceResolution();
}
return this;
}
@Override
public Path pushFieldResolve(BeanDefinition declaringType, Argument fieldAsArgument) {
try {
FieldSegment, ?> fieldSegment = new FieldSegment<>(declaringType, getCurrentQualifier(), fieldAsArgument);
if (contains(fieldSegment)) {
push(fieldSegment);
throw new CircularDependencyException(AbstractBeanResolutionContext.this, declaringType, fieldAsArgument.getName(), CIRCULAR_ERROR_MSG);
} else {
push(fieldSegment);
}
} finally {
traceResolution();
}
return this;
}
@Override
public Path pushAnnotationResolve(BeanDefinition beanDefinition, Argument annotationMemberBeanAsArgument) {
try {
AnnotationSegment annotationSegment = new AnnotationSegment(beanDefinition, getCurrentQualifier(), annotationMemberBeanAsArgument);
if (contains(annotationSegment)) {
push(annotationSegment);
throw new CircularDependencyException(AbstractBeanResolutionContext.this, beanDefinition, annotationMemberBeanAsArgument.getName(), CIRCULAR_ERROR_MSG);
} else {
push(annotationSegment);
}
} finally {
traceResolution();
}
return this;
}
private void detectCircularDependency(BeanDefinition declaringType, Argument argument, Segment constructorSegment) {
if (contains(constructorSegment)) {
Segment last = peek();
if (last != null) {
BeanDefinition declaringBean = last.getDeclaringType();
// if the currently injected segment is a constructor argument and the type to be constructed is the
// same as the candidate, then filter out the candidate to avoid a circular injection problem
if (!declaringBean.equals(declaringType)) {
if (declaringType instanceof ProxyBeanDefinition> proxyBeanDefinition) {
// take into account proxies
if (!proxyBeanDefinition.getTargetDefinitionType().equals(declaringBean.getClass())) {
push(constructorSegment);
throw new CircularDependencyException(AbstractBeanResolutionContext.this, argument, CIRCULAR_ERROR_MSG);
} else {
push(constructorSegment);
}
} else if (declaringBean instanceof ProxyBeanDefinition> proxyBeanDefinition) {
// take into account proxies
if (!proxyBeanDefinition.getTargetDefinitionType().equals(declaringType.getClass())) {
push(constructorSegment);
throw new CircularDependencyException(AbstractBeanResolutionContext.this, argument, CIRCULAR_ERROR_MSG);
} else {
push(constructorSegment);
}
} else {
push(constructorSegment);
throw new CircularDependencyException(AbstractBeanResolutionContext.this, argument, CIRCULAR_ERROR_MSG);
}
} else {
push(constructorSegment);
}
} else {
push(constructorSegment);
throw new CircularDependencyException(AbstractBeanResolutionContext.this, argument, CIRCULAR_ERROR_MSG);
}
} else {
push(constructorSegment);
}
}
@Override
public void push(Segment, ?> segment) {
super.push(segment);
AbstractBeanResolutionContext.this.onNewSegment(segment);
}
@Override
public void close() {
if (traceEnabled) {
traceMode.getTracer().ifPresent(tracer -> {
if (isEmpty()) {
tracer.traceBeanCreated(
AbstractBeanResolutionContext.this,
rootDefinition
);
} else {
Segment, ?> segment = peek();
if (segment != null) {
tracer.traceInjectComplete(
AbstractBeanResolutionContext.this,
segment
);
}
}
});
}
Path.super.close();
}
}
/**
* A segment that represents a method argument.
*/
public static final class ConstructorArgumentSegment extends ConstructorSegment implements ArgumentInjectionPoint {
public ConstructorArgumentSegment(BeanDefinition declaringType, Qualifier qualifier, String methodName, Argument argument, Argument[] arguments) {
super(declaringType, qualifier, methodName, argument, arguments);
}
@Override
public BeanDefinition getDeclaringBean() {
return getDeclaringType();
}
@Override
public Qualifier getDeclaringBeanQualifier() {
return getDeclaringTypeQualifier();
}
}
/**
* A segment that represents a constructor.
*/
public static class ConstructorSegment extends AbstractSegment implements ArgumentInjectionPoint {
private static final String ANN_ADAPTER = "io.micronaut.aop.Adapter";
private final String methodName;
private final Argument[] arguments;
/**
* @param declaringBeanDefinition The declaring class
* @param qualifier The qualifier
* @param methodName The methodName
* @param argument The argument
* @param arguments The arguments
*/
ConstructorSegment(BeanDefinition declaringBeanDefinition, Qualifier qualifier, String methodName, Argument argument, Argument[] arguments) {
super(declaringBeanDefinition, qualifier, declaringBeanDefinition.getBeanType().getName(), argument);
this.methodName = methodName;
this.arguments = arguments;
}
@Override
public String toString() {
return toConsoleString(false);
}
@NonNull
public String toConsoleString(boolean ansiSupported) {
StringBuilder baseString;
BeanDefinition declaringType = getDeclaringType();
TypeInformation typeInformation = declaringType.getTypeInformation();
if (declaringType.hasDeclaredStereotype(ANN_ADAPTER)) {
ExecutableMethod method = declaringType.getExecutableMethods().iterator().next();
// Not great, but to produce accurate debug output we have to reach into AOP internals
Class> beanType = method.classValue(ANN_ADAPTER, "adaptedBean").orElse(declaringType.getBeanType());
String beanMethod = method.stringValue(ANN_ADAPTER, "adaptedMethod").orElse("unknown");
baseString = new StringBuilder(TypeFormat.getBeanTypeString(
ansiSupported ? TypeFormat.ANSI_SHORTENED : TypeFormat.SHORTENED,
beanType,
declaringType.asArgument().getTypeVariables(),
declaringType.getAnnotationMetadata()
)).append(MEMBER_SEPARATOR);
baseString.append(beanMethod);
} else if (CONSTRUCTOR_METHOD_NAME.equals(methodName)) {
baseString = new StringBuilder(
ansiSupported ? AnsiColour.magentaBold("new ") : "new "
);
baseString.append(typeInformation.getBeanTypeString(
ansiSupported ? TypeFormat.ANSI_SHORTENED : TypeFormat.SHORTENED
));
} else {
baseString = new StringBuilder(typeInformation.getBeanTypeString(
ansiSupported ? TypeFormat.ANSI_SHORTENED : TypeFormat.SHORTENED
)).append(MEMBER_SEPARATOR);
baseString.append(methodName);
}
outputArguments(baseString, arguments, ansiSupported);
return baseString.toString();
}
@Override
public InjectionPoint getInjectionPoint() {
return this;
}
@NonNull
@Override
public CallableInjectionPoint getOuterInjectionPoint() {
return getDeclaringType().getConstructor();
}
@Override
public BeanDefinition getDeclaringBean() {
return ConstructorSegment.this.getDeclaringType();
}
@Override
public Qualifier getDeclaringBeanQualifier() {
return ConstructorSegment.this.getDeclaringTypeQualifier();
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return getArgument().getAnnotationMetadata();
}
}
/**
* A segment that represents a method argument.
*/
public static final class MethodArgumentSegment extends MethodSegment implements ArgumentInjectionPoint {
private final MethodSegment outer;
public MethodArgumentSegment(BeanDefinition declaringType,
Qualifier qualifier,
String methodName,
Argument argument,
Argument[] arguments,
MethodSegment outer) {
super(declaringType, qualifier, methodName, argument, arguments);
this.outer = outer;
}
@Override
public CallableInjectionPoint getOuterInjectionPoint() {
if (outer == null) {
throw new IllegalStateException("Outer argument inaccessible");
}
return outer;
}
@Override
public String toString() {
return toConsoleString(false);
}
@Override
public String toConsoleString(boolean ansiSupported) {
BeanDefinition> declaringBean = getDeclaringBean();
if (declaringBean.hasAnnotation(Factory.class)) {
String beanDescription = declaringBean.getBeanDescription(
ansiSupported ? TypeFormat.ANSI_SHORTENED : TypeFormat.SHORTENED,
false
);
var baseString = new StringBuilder(beanDescription);
String methodName = getName();
if (!CONSTRUCTOR_METHOD_NAME.equals(methodName)) {
String memberSeparator = ansiSupported ? AnsiColour.CYAN_BOLD + MEMBER_SEPARATOR + AnsiColour.RESET : MEMBER_SEPARATOR;
baseString.append(memberSeparator);
baseString.append(methodName);
}
outputArguments(baseString, getArguments(), ansiSupported);
return baseString.toString();
} else {
return super.toConsoleString(ansiSupported);
}
}
}
/**
* Represents a segment that is an event listener.
*
* @param The bean type
* @param The event type
*/
public static class EventListenerSegment extends AbstractSegment implements CallableInjectionPoint {
/**
* @param declaringClass The declaring class
* @param eventType The argument
*/
EventListenerSegment(
BeanDefinition declaringClass,
Argument eventType) {
super(declaringClass, null, eventType.getName(), eventType);
}
@Override
public String toConsoleString(boolean ansiSupported) {
if (ansiSupported) {
String event = getArgument().getTypeString(TypeFormat.ANSI_SIMPLE);
return event + " ➡️ " +
getDeclaringBean().getBeanDescription(TypeFormat.ANSI_SHORTENED);
} else {
String event = getArgument().getTypeString(TypeFormat.SIMPLE);
return event + " -> " +
getDeclaringBean().getBeanDescription(TypeFormat.SHORTENED);
}
}
@Override
public InjectionPoint getInjectionPoint() {
return this;
}
@Override
public Argument>[] getArguments() {
return new Argument[]{getArgument()};
}
@Override
public BeanDefinition getDeclaringBean() {
return getDeclaringType();
}
}
/**
* A segment that represents a method.
*/
public static class MethodSegment extends AbstractSegment implements CallableInjectionPoint {
private final Argument[] arguments;
/**
* @param declaringType The declaring type
* @param qualifier The qualifier
* @param methodName The method name
* @param argument The argument
* @param arguments The arguments
*/
MethodSegment(BeanDefinition declaringType, Qualifier qualifier, String methodName, Argument argument, Argument[] arguments) {
super(declaringType, qualifier, methodName, argument);
this.arguments = arguments;
}
@Override
public String toString() {
return toConsoleString(false);
}
@Override
public String toConsoleString(boolean ansiSupported) {
StringBuilder baseString = new StringBuilder(
getDeclaringType().getTypeInformation().getBeanTypeString(
ansiSupported ? TypeFormat.ANSI_SHORTENED : TypeFormat.SHORTENED
)
);
String memberSeparator = ansiSupported ? AnsiColour.CYAN_BOLD + MEMBER_SEPARATOR + AnsiColour.RESET : MEMBER_SEPARATOR;
baseString.append(memberSeparator);
baseString.append(getName());
outputArguments(baseString, arguments, ansiSupported);
return baseString.toString();
}
@Override
public InjectionPoint getInjectionPoint() {
return this;
}
@Override
public BeanDefinition getDeclaringBean() {
return getDeclaringType();
}
@Override
public Argument>[] getArguments() {
return arguments;
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return getArgument().getAnnotationMetadata();
}
@Override
public Qualifier getDeclaringBeanQualifier() {
return getDeclaringTypeQualifier();
}
}
/**
* A segment that represents a field.
*/
public static final class FieldSegment extends AbstractSegment implements InjectionPoint, ArgumentCoercible, ArgumentInjectionPoint {
/**
* @param declaringClass The declaring class
* @param qualifier The qualifier
* @param argument The argument
*/
FieldSegment(BeanDefinition declaringClass, Qualifier qualifier, Argument argument) {
super(declaringClass, qualifier, argument.getName(), argument);
}
@Override
public String toString() {
return toConsoleString(false);
}
@Override
public String toConsoleString(boolean ansiSupported) {
String beanDescription = getDeclaringType().getBeanDescription(
ansiSupported ? TypeFormat.ANSI_SHORTENED : TypeFormat.SHORTENED,
false
);
StringBuilder baseString = new StringBuilder(beanDescription);
String memberSeparator = ansiSupported ? AnsiColour.CYAN_BOLD + MEMBER_SEPARATOR + AnsiColour.RESET : MEMBER_SEPARATOR;
baseString.append(memberSeparator);
baseString.append(getName());
return baseString.toString();
}
@Override
public InjectionPoint getInjectionPoint() {
return this;
}
@Override
public BeanDefinition getDeclaringBean() {
return getDeclaringType();
}
@Override
public CallableInjectionPoint getOuterInjectionPoint() {
throw new UnsupportedOperationException("Outer injection point not retrievable from here");
}
@Override
public Argument asArgument() {
return getArgument();
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return getArgument().getAnnotationMetadata();
}
@Override
public Qualifier getDeclaringBeanQualifier() {
return getDeclaringTypeQualifier();
}
}
/**
* A segment that represents annotation.
*
* @since 3.3.0
*/
public static final class AnnotationSegment extends AbstractSegment implements InjectionPoint {
/**
* @param beanDefinition The bean definition
* @param qualifier The qualifier
* @param argument The argument
*/
AnnotationSegment(BeanDefinition beanDefinition, Qualifier qualifier, Argument argument) {
super(beanDefinition, qualifier, argument.getName(), argument);
}
@Override
public String toString() {
return getName();
}
@Override
public InjectionPoint getInjectionPoint() {
return this;
}
@Override
public BeanDefinition getDeclaringBean() {
return getDeclaringType();
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return getArgument().getAnnotationMetadata();
}
@Override
public Qualifier getDeclaringBeanQualifier() {
return getDeclaringTypeQualifier();
}
}
/**
* Abstract class for a Segment.
*/
protected abstract static class AbstractSegment implements Segment, Named {
/**
* The separator between a type and its member when printing to user.
*/
protected static final String MEMBER_SEPARATOR = "#";
private final BeanDefinition declaringComponent;
@Nullable
private final Qualifier qualifier;
private final String name;
private final Argument argument;
/**
* @param declaringClass The declaring class
* @param qualifier The qualifier
* @param name The name
* @param argument The argument
*/
AbstractSegment(BeanDefinition declaringClass, Qualifier qualifier, String name, Argument argument) {
this.declaringComponent = declaringClass;
this.qualifier = qualifier;
this.name = name;
this.argument = argument;
}
/**
* A common method for retrieving a name for type. The default behavior is to use the shortened type name.
*
* @param type The type
* @return The name to be shown to user
*/
protected String getTypeName(Class> type) {
if (InterceptedBean.class.isAssignableFrom(type)) {
Class>[] interfaces = type.getInterfaces();
Set interfaceNames = Arrays.stream(interfaces)
.map(Class::getName)
.collect(Collectors.toSet());
if (type.isInterface() && interfaceNames.contains("io.micronaut.aop.Introduced")) {
return NameUtils.getShortenedName(
interfaces[0].getTypeName()
);
} else {
return NameUtils.getShortenedName(
type.getSuperclass().getTypeName()
);
}
} else {
return NameUtils.getShortenedName(type.getTypeName());
}
}
@Override
public String getName() {
return name;
}
@Override
public BeanDefinition getDeclaringType() {
return declaringComponent;
}
@Override
public Qualifier getDeclaringTypeQualifier() {
return qualifier == null ? declaringComponent.getDeclaredQualifier() : qualifier;
}
@Override
public Argument getArgument() {
return argument;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
AbstractSegment that = (AbstractSegment) o;
return declaringComponent.equals(that.declaringComponent) && name.equals(that.name) && argument.equals(that.argument);
}
@Override
public int hashCode() {
return ObjectUtils.hash(declaringComponent, name, argument);
}
/**
* @param baseString The base string
* @param arguments The arguments
* @param ansiSupported Whether ANSI colour is supported
*/
void outputArguments(StringBuilder baseString, Argument[] arguments, boolean ansiSupported) {
baseString.append(ansiSupported ? AnsiColour.brightCyan("(") : "(");
for (int i = 0; i < arguments.length; i++) {
Argument> argument = arguments[i];
boolean isInjectedArgument = argument.equals(getArgument());
if (isInjectedArgument) {
if (ansiSupported) {
baseString.append(AnsiColour.BLUE_UNDERLINED);
}
baseString.append('[');
}
String beanTypeString = argument.getBeanTypeString(
ansiSupported && !isInjectedArgument ? TypeFormat.ANSI_SIMPLE : TypeFormat.SIMPLE
);
baseString.append(beanTypeString)
.append(' ')
.append(ansiSupported && !isInjectedArgument ? AnsiColour.brightBlue(argument.getName()) : argument.getName());
if (isInjectedArgument) {
baseString.append(']');
if (ansiSupported) {
baseString.append(AnsiColour.RESET);
}
}
if (i != arguments.length - 1) {
Argument> next = arguments[i + 1];
if (getDeclaringType().getBeanType().isSynthetic() &&
next.getName().startsWith("$")) {
// skip synthetic arguments
break;
}
baseString.append(", ");
}
}
baseString.append(ansiSupported ? AnsiColour.brightCyan(")") : ")");
}
}
}