diff --git a/inject/src/main/java/io/micronaut/context/AbstractBeanResolutionContext.java b/inject/src/main/java/io/micronaut/context/AbstractBeanResolutionContext.java index b842f4e11d8..7a2121bbcd7 100644 --- a/inject/src/main/java/io/micronaut/context/AbstractBeanResolutionContext.java +++ b/inject/src/main/java/io/micronaut/context/AbstractBeanResolutionContext.java @@ -651,6 +651,30 @@ public Path pushMethodArgumentResolve(BeanDefinition declaringType, String metho 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 { @@ -921,6 +945,51 @@ public String toConsoleString(boolean 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. */ @@ -1213,7 +1282,14 @@ void outputArguments(StringBuilder baseString, Argument[] arguments, boolean ans 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(", "); } } diff --git a/inject/src/main/java/io/micronaut/context/BeanResolutionContext.java b/inject/src/main/java/io/micronaut/context/BeanResolutionContext.java index 9730ebdc6e5..efca0247760 100644 --- a/inject/src/main/java/io/micronaut/context/BeanResolutionContext.java +++ b/inject/src/main/java/io/micronaut/context/BeanResolutionContext.java @@ -373,6 +373,14 @@ interface Path extends Deque>, AutoCloseable { */ Path pushMethodArgumentResolve(BeanDefinition declaringType, String methodName, Argument argument, Argument[] arguments); + /** + * Push resolution of an event listener + * @param declaringType The declaration type + * @param eventType The event type + * @return The path + */ + Path pushEventListenerResolve(BeanDefinition declaringType, Argument eventType); + /** * Push an unresolved field onto the queue. * diff --git a/inject/src/main/java/io/micronaut/context/BeanResolutionTraceMode.java b/inject/src/main/java/io/micronaut/context/BeanResolutionTraceMode.java index 8490cf37f59..5de6f86dc67 100644 --- a/inject/src/main/java/io/micronaut/context/BeanResolutionTraceMode.java +++ b/inject/src/main/java/io/micronaut/context/BeanResolutionTraceMode.java @@ -119,18 +119,12 @@ void startTrace( .walk(s -> s.dropWhile(f -> (INTERNAL_PACKAGES.stream().anyMatch(p -> f.getClassName().startsWith(p)) || f.getDeclaringClass().isSynthetic()) && - // capture startup beans - !(f.getClassName().equals(DefaultBeanContext.class.getName()) && f.getMethodName().equals("start"))) + // capture startup beans + !(f.getClassName().equals(DefaultBeanContext.class.getName()) && f.getMethodName().equals("start"))) .limit(3) .collect(Collectors.toList()) ); - String beanName; - if (beanType.getType().isSynthetic()) { - beanName = beanDefinition.getTypeInformation().getBeanTypeString(TypeInformation.TypeFormat.ANSI_SIMPLE); - } else { - beanName = beanType - .getBeanTypeString(TypeInformation.TypeFormat.ANSI_SIMPLE); - } + String beanName = getBeanName(beanType, beanDefinition); switch (this) { case STANDARD_OUT -> { System.out.println(); @@ -152,6 +146,17 @@ void startTrace( } } + private static String getBeanName(Argument beanType, BeanDefinition beanDefinition) { + String beanName; + if (beanType.getType().isSynthetic()) { + beanName = beanDefinition.getTypeInformation().getBeanTypeString(TypeInformation.TypeFormat.ANSI_SIMPLE); + } else { + beanName = beanType + .getBeanTypeString(TypeInformation.TypeFormat.ANSI_SIMPLE); + } + return beanName; + } + void traceBeanResolved( BeanResolutionContext resolutionContext, @NonNull Argument beanType, @@ -208,8 +213,9 @@ void traceValueResolved( System.out.print(AnsiColour.formatObject(value)); if (origin != null) { System.out.println(" (Origin: " + AnsiColour.brightYellow(origin.location()) + ")"); + } else { + System.out.println(); } - System.out.println(); } } } @@ -245,20 +251,22 @@ void traceSegment(BeanResolutionContext context) { BeanResolutionContext.Path path = context.getPath(); BeanResolutionContext.Segment segment = path.peek(); if (segment != null) { - BeanDefinition declaringType = segment.getDeclaringType(); - if (declaringType == null || !declaringType.getBeanType().isSynthetic()) { - - int size = path.size(); - String prefix = ""; - if (size > 1) { - String spaces = " ".repeat(size); - prefix = spaces + RIGHT_ARROW_LOOP; - } - String content = prefix + segment.toConsoleString(AnsiColour.isSupported()); - // TODO: other output methods - switch (this) { - case STANDARD_OUT -> System.out.println(content); - } + if (segment.getDeclaringType() != null && + segment.getDeclaringType().getBeanType().isSynthetic() && + segment.getArgument().getName().startsWith("$")) { + // skip synthetic arguments + return; + } + int size = path.size(); + String prefix = ""; + if (size > 1) { + String spaces = " ".repeat(size); + prefix = spaces + RIGHT_ARROW_LOOP; + } + String content = prefix + segment.toConsoleString(AnsiColour.isSupported()); + // TODO: other output methods + switch (this) { + case STANDARD_OUT -> System.out.println(content); } } } @@ -318,25 +326,25 @@ void traceConfiguration( ).toList(); configRefs.forEach(ref -> { - String prefix = ref.stringValue(ConfigurationReader.class, "prefix").orElse(null); - if (prefix != null) { - Argument argument = ref.asArgument(); - System.out.print(" ✚ "); - System.out.print(AnsiColour.formatObject(prefix)); - System.out.print(RIGHT_ARROW); - System.out.println(TypeInformation.TypeFormat.getTypeString( - TypeInformation.TypeFormat.ANSI_SHORTENED, - argument.getType(), - argument.getTypeVariables() - )); - } - }); + String prefix = ref.stringValue(ConfigurationReader.class, "prefix").orElse(null); + if (prefix != null) { + Argument argument = ref.asArgument(); + System.out.print(" ✚ "); + System.out.print(AnsiColour.formatObject(prefix)); + System.out.print(RIGHT_ARROW); + System.out.println(TypeInformation.TypeFormat.getTypeString( + TypeInformation.TypeFormat.ANSI_SHORTENED, + argument.getType(), + argument.getTypeVariables() + )); + } + }); System.out.println(); System.out.println(AnsiColour.brightBlue("Applicable Configuration Present: ")); configRefs.stream() .flatMap(ref -> ref.stringValue(ConfigurationReader.class, "prefix").stream()) .flatMap(prefix -> { - if(prefix.endsWith(".*")) { + if (prefix.endsWith(".*")) { String eachProperty = prefix.substring(0, prefix.length() - 2); return environment.getPropertyEntries(eachProperty).stream().flatMap(entry -> { diff --git a/inject/src/main/java/io/micronaut/context/DefaultBeanContext.java b/inject/src/main/java/io/micronaut/context/DefaultBeanContext.java index c3891c08bc5..9fa23aea7fb 100644 --- a/inject/src/main/java/io/micronaut/context/DefaultBeanContext.java +++ b/inject/src/main/java/io/micronaut/context/DefaultBeanContext.java @@ -1966,7 +1966,10 @@ private List, ListenersSupplier> } List, ListenersSupplier>> eventToListeners = new ArrayList<>(typeToListener.size()); for (Map.Entry, List>> e : typeToListener.entrySet()) { - eventToListeners.add(new AbstractMap.SimpleEntry<>(e.getKey(), new EventListenerListenersSupplier<>(e.getValue()))); + eventToListeners.add(new AbstractMap.SimpleEntry<>(e.getKey(), new EventListenerListenersSupplier<>( + Argument.of(listenerType, e.getKey()), + e.getValue() + ))); } return eventToListeners; } @@ -4131,12 +4134,14 @@ public int hashCode() { private final class EventListenerListenersSupplier implements ListenersSupplier { private final List> listenersDefinitions; + private final Argument eventType; // The supplier can be triggered concurrently. // We allow for the listeners collection to be initialized multiple times. @SuppressWarnings("java:S3077") private volatile List listeners; - public EventListenerListenersSupplier(List> listenersDefinitions) { + EventListenerListenersSupplier(Argument eventType, List> listenersDefinitions) { + this.eventType = eventType; this.listenersDefinitions = listenersDefinitions; } @@ -4148,10 +4153,20 @@ public Iterable get(BeanResolutionContext beanResolutionContext) { T listener; if (beanResolutionContext == null) { try (BeanResolutionContext context = newResolutionContext(listenersDefinition, null)) { - listener = resolveBeanRegistration(context, listenersDefinition).bean; + try (BeanResolutionContext.Path ignored = context.getPath().pushEventListenerResolve( + listenersDefinition, + eventType + )) { + listener = resolveBeanRegistration(context, listenersDefinition).bean; + } } } else { - listener = resolveBeanRegistration(beanResolutionContext, listenersDefinition).bean; + try (BeanResolutionContext.Path ignored = beanResolutionContext.getPath().pushEventListenerResolve( + listenersDefinition, + eventType + )) { + listener = resolveBeanRegistration(beanResolutionContext, listenersDefinition).bean; + } } listeners.add(listener); } diff --git a/inject/src/main/java/io/micronaut/inject/BeanDefinition.java b/inject/src/main/java/io/micronaut/inject/BeanDefinition.java index 0b36c43f088..2f6300f48c7 100644 --- a/inject/src/main/java/io/micronaut/inject/BeanDefinition.java +++ b/inject/src/main/java/io/micronaut/inject/BeanDefinition.java @@ -607,6 +607,7 @@ default Argument getGenericBeanType() { if (includeArguments) { beanDescription.append(typeFormat.isAnsi() ? AnsiColour.brightCyan("(") : "("); + for (int i = 0; i < arguments.length; i++) { Argument argument = arguments[i]; if (argument.getName().startsWith("$")) { @@ -623,6 +624,12 @@ default Argument getGenericBeanType() { .append(typeFormat.isAnsi() ? AnsiColour.brightBlue(argumentName) : argumentName); if (i != arguments.length - 1) { + Argument next = arguments[i + 1]; + if (getBeanType().isSynthetic() && + next.getName().startsWith("$")) { + // skip synthetic arguments + break; + } beanDescription.append(", "); } } diff --git a/inject/src/main/java/io/micronaut/inject/qualifiers/InterceptorBindingQualifier.java b/inject/src/main/java/io/micronaut/inject/qualifiers/InterceptorBindingQualifier.java index 88481508d97..a38118d5640 100644 --- a/inject/src/main/java/io/micronaut/inject/qualifiers/InterceptorBindingQualifier.java +++ b/inject/src/main/java/io/micronaut/inject/qualifiers/InterceptorBindingQualifier.java @@ -21,6 +21,7 @@ import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.naming.NameUtils; import io.micronaut.core.util.CollectionUtils; import io.micronaut.core.util.ObjectUtils; import io.micronaut.inject.BeanType; @@ -177,8 +178,8 @@ public String toString() { if (CollectionUtils.isEmpty(supportedAnnotationNames) && CollectionUtils.isEmpty(supportedInterceptorTypes)) { return "@InterceptorBinding(NONE)"; } else { - return supportedAnnotationNames.keySet().stream().map((name) -> "@InterceptorBinding(" + name + ")").collect(Collectors.joining(" ")) + - supportedInterceptorTypes.stream().map((name) -> "@InterceptorBinding(interceptorType = " + name + ")").collect(Collectors.joining(" ")); + return supportedAnnotationNames.keySet().stream().map((name) -> "@InterceptorBinding(" + NameUtils.getShortenedName(name) + ")").collect(Collectors.joining(" ")) + + supportedInterceptorTypes.stream().map((type) -> "@InterceptorBinding(interceptorType = " + NameUtils.getShortenedName(type.getTypeName()) + ")").collect(Collectors.joining(" ")); } }