001/* 002 * JGrapes Event Driven Framework 003 * Copyright (C) 2016-2026 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Affero General Public License as published by 007 * the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public 013 * License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jgrapes.core.annotation; 020 021import java.lang.annotation.Annotation; 022import java.lang.annotation.Documented; 023import java.lang.annotation.ElementType; 024import java.lang.annotation.Retention; 025import java.lang.annotation.RetentionPolicy; 026import java.lang.annotation.Target; 027import java.lang.reflect.Method; 028import java.util.Arrays; 029import java.util.Collections; 030import java.util.HashSet; 031import java.util.Map; 032import java.util.Set; 033import java.util.stream.Collectors; 034import org.jgrapes.core.Channel; 035import org.jgrapes.core.Channel.Default; 036import org.jgrapes.core.ClassChannel; 037import org.jgrapes.core.Component; 038import org.jgrapes.core.ComponentType; 039import org.jgrapes.core.Components; 040import org.jgrapes.core.Eligible; 041import org.jgrapes.core.Event; 042import org.jgrapes.core.HandlerScope; 043import org.jgrapes.core.Manager; 044import org.jgrapes.core.NamedChannel; 045import org.jgrapes.core.NamedEvent; 046import org.jgrapes.core.Self; 047import org.jgrapes.core.annotation.HandlerDefinition.ChannelReplacements; 048 049/** 050 * This is the basic, general purpose handler annotation provided as part of the 051 * core package. 052 * 053 * The annotated method is invoked for events that have a type (or 054 * name) matching the given `events` (or `namedEvents`) element 055 * of the annotation and that are fired on one of 056 * the `channels` (or `namedChannels`) specified in the annotation. 057 * 058 * If neither event classes nor named events are specified in the 059 * annotation, the class of the annotated method's first parameter (which 060 * must be of type {@link Event} or a derived type) is used as (single) 061 * event class (see the examples in {@link #events()} and 062 * {@link #namedEvents()}). 063 * 064 * Channel matching is performed by matching the event's channels 065 * (see {@link Event#channels()}) with the channels specified in the 066 * handler. The matching algorithm invokes 067 * {@link Eligible#isEligibleFor(Object) isEligibleFor} for each of the 068 * event's channels with the class (or name, see {@link #channels()} and 069 * {@link Handler#namedChannels()}) of each of the channels specified 070 * in the handler. 071 * 072 * If neither channel classes not named channels are specified in the 073 * handler, or `{@link Default Channel.Default}.class` is specified as one 074 * of the channel classes, the matching algorithm invokes 075 * {@link Eligible#isEligibleFor(Object) isEligibleFor} for each of 076 * the event's channels with the default criterion of the component's 077 * channel (see {@link Manager#channel()} and 078 * {@link Eligible#defaultCriterion()}) as argument. 079 * 080 * Finally, independent of any specified channels, the matching algorithm 081 * invokes {@link Eligible#isEligibleFor(Object) isEligibleFor} 082 * for each of the event's channels with the component's default criterion 083 * as argument unless {@link #excludeSelf()} is set. This results in a match 084 * if the component itself is used as one of the event's channels 085 * (see the description of {@link Eligible}). 086 * 087 * If a match is found for a given event's properties and a handler's 088 * specified attributes, the handler method is invoked. 089 * The method can have an additional optional parameter of type 090 * {@link Channel} (or a derived type). This parameter does not 091 * influence the eligibility of the method regarding a given event, 092 * it determines how the method is invoked. If the method does not 093 * have a second parameter, it is invoked once if an event 094 * matches. If the parameter exists, the method is invoked once for 095 * each of the event's channels, provided that the optional parameter's 096 * type is assignable from the event's channel. 097 * 098 * Because annotation elements accept only literals as values, they 099 * cannot be used to register handlers with properties that are only 100 * known at runtime. It is therefore possible to specify a 101 * {@link Handler} annotation with element `dynamic=true`. Such a 102 * handler must be added explicitly by invoking 103 * {@link Evaluator#add(ComponentType, String, Object)} or 104 * {@link Evaluator#add(ComponentType, String, Object, Object, int)}, 105 * thus specifying some of the handler's properties dynamically (i.e. 106 * at runtime). 107 * 108 * A special case is the usage of a channel that is only known at 109 * runtime. If there are several handlers for events on such a 110 * channel, a lot of methods will become dynamic. To avoid this, 111 * {@link Component}s support a {@link ChannelReplacements} 112 * parameter in their constructor. Using this, it is possible 113 * to specify a specially defined {@link Channel} class in the 114 * annotation that is replaced by a channel that is only known 115 * at runtime. 116 * 117 * If a method with a handler annotation is overwritten in a 118 * derived class, the annotation is overwritten as well. The 119 * annotated method of the base class is no longer invoked as 120 * handler and the method of the derived class is only invoked 121 * as handler if it defines its own handler annotation. 122 * 123 * @see Component#channel() 124 */ 125@Documented 126@Retention(RetentionPolicy.RUNTIME) 127@Target(ElementType.METHOD) 128@HandlerDefinition(evaluator = Handler.Evaluator.class) 129public @interface Handler { 130 131 /** The default value for the <code>events</code> parameter of 132 * the annotation. Indicates that the parameter is not used. */ 133 final class NoEvent extends Event<Void> { 134 } 135 136 /** The default value for the <code>channels</code> parameter of 137 * the annotation. Indicates that the parameter is not used. */ 138 final class NoChannel extends ClassChannel { 139 } 140 141 /** 142 * Specifies classes of events that the handler is to receive. 143 * 144 * ```java 145 * class SampleComponent extends Component { 146 * 147 * {@literal @}Handler 148 * public void onStart(Start event) { 149 * // Invoked for Start events on the component's channel, 150 * // event object made available 151 * } 152 * 153 * {@literal @}Handler(events=Start.class) 154 * public void onStart() { 155 * // Invoked for Start events on the component's channel, 156 * // not interested in the event object 157 * } 158 * 159 * {@literal @}Handler(events={Start.class, Stop.class}) 160 * public void onStart(Event<?> event) { 161 * // Invoked for Start and Stop events on the component's 162 * // channel, event made available (may need casting to 163 * // access specific properties) 164 * } 165 * } 166 * ``` 167 * 168 * @return the event classes 169 */ 170 @SuppressWarnings("rawtypes") 171 Class<? extends Event>[] events() default NoEvent.class; 172 173 /** 174 * Specifies names of {@link NamedEvent}s that the handler is to receive. 175 * 176 * ```java 177 * class SampleComponent extends Component { 178 * 179 * {@literal @}Handler(namedEvents="Test") 180 * public void onTest(Event<?> event) { 181 * // Invoked for (named) "Test" events (new NamedEvent("Test")) 182 * // on the component's channel, event object made available 183 * } 184 * } 185 * ``` 186 * 187 * @return the event names 188 */ 189 String[] namedEvents() default ""; 190 191 /** 192 * Specifies classes of channels that the handler listens on. If none 193 * are specified, the component's channel is used. 194 * 195 * ```java 196 * class SampleComponent extends Component { 197 * 198 * {@literal @}Handler(channels=Feedback.class) 199 * public void onStart(Start event) { 200 * // Invoked for Start events on the "Feedback" channel 201 * // (class Feedback implements Channel {...}), 202 * // event object made available 203 * } 204 * } 205 * ``` 206 * 207 * Specifying `channels=Channel.class` make the handler listen 208 * for all events, independent of the channel that they are fired on. 209 * 210 * Specifying `channels=Self.class` make the handler listen 211 * for events that are fired on the conponent. 212 * 213 * @return the channel classes 214 */ 215 Class<? extends Channel>[] channels() default NoChannel.class; 216 217 /** 218 * Specifies names of {@link NamedChannel}s that the handler listens on. 219 * 220 * ```java 221 * class SampleComponent extends Component { 222 * 223 * {@literal @}Handler(namedChannels="Feedback") 224 * public void onStart(Start event) { 225 * // Invoked for Start events on the (named) channel "Feedback" 226 * // (new NamedChannel("Feedback")), event object made available 227 * } 228 * } 229 * ``` 230 * 231 * @return the channel names 232 */ 233 String[] namedChannels() default ""; 234 235 /** 236 * Specifies a priority. The value is used to sort handlers. 237 * Handlers with higher priority are invoked first. 238 * 239 * @return the priority 240 */ 241 int priority() default 0; 242 243 /** 244 * Returns {@code true} if the annotated method defines a 245 * dynamic handler. A dynamic handler must be added to the set of 246 * handlers of a component explicitly at run time using 247 * {@link Evaluator#add(ComponentType, String, Object)} 248 * or {@link Evaluator#add(ComponentType, String, Object, Object, int)}. 249 * 250 * ```java 251 * class SampleComponent extends Component { 252 * 253 * SampleComponent() { 254 * Handler.Evaluator.add(this, "onStartDynamic", someChannel); 255 * } 256 * 257 * {@literal @}Handler(dynamic=true) 258 * public void onStartDynamic(Start event) { 259 * // Only invoked if added as handler at runtime 260 * } 261 * } 262 * ``` 263 * 264 * @return the result 265 */ 266 boolean dynamic() default false; 267 268 /** 269 * Excludes the handler from channel matching against its component's 270 * default criterion. The typical use case for this annotation is 271 * a converter component that receives events from some source channel 272 * and then fires the same kind of events with modified data using 273 * itself as the (source) channel. In this case, the events 274 * generated by the component must not be processed by the component 275 * although they are fired using the component as channel. So while it 276 * is useful to be able to target a specific component (using it as 277 * channel) in general, it isn't in this special case and can therefore 278 * be turned off with this annotation. 279 * 280 * Of course, it would also be possible to work around the ambiguity 281 * by firing the conversion results on an extra channel. But it is 282 * quite intuitive to use the component itself as (source) channel. 283 * 284 * @return true, if set 285 */ 286 boolean excludeSelf() default false; 287 288 /** 289 * This class provides the {@link Evaluator} for the 290 * {@link Handler} annotation provided by the core package. It 291 * implements the behavior as described for the annotation. 292 */ 293 class Evaluator implements HandlerDefinition.Evaluator { 294 295 @Override 296 public HandlerScope scope( 297 ComponentType component, Method method, 298 ChannelReplacements channelReplacements) { 299 Handler annotation = method.getAnnotation(Handler.class); 300 if (annotation == null || annotation.dynamic()) { 301 return null; 302 } 303 return new Scope(component, method, annotation, 304 channelReplacements, null, null); 305 } 306 307 /* 308 * (non-Javadoc) 309 * 310 * @see 311 * org.jgrapes.core.annotation.HandlerDefinition.Evaluator#getPriority() 312 */ 313 @Override 314 public int priority(Annotation annotation) { 315 return ((Handler) annotation).priority(); 316 } 317 318 /** 319 * Adds the given method of the given component as a dynamic handler for 320 * a specific event and channel. The method with the given name must be 321 * annotated as dynamic handler and must have a single parameter of type 322 * {@link Event} (or a derived type as appropriate for the event type to 323 * be handled). It can have an optional parameter of type 324 * {@link Channel}. 325 * 326 * @param component 327 * the component 328 * @param method 329 * the name of the method that implements the handler 330 * @param eventValue 331 * the event key that should be used for matching this 332 * handler with an event. This is equivalent to an 333 * <code>events</code>/<code>namedEvents</code> parameter 334 * used with a single value in the handler annotation, but 335 * here all kinds of Objects are allowed as key values. 336 * @param channelValue 337 * the channel value that should be used for matching 338 * an event's channel with this handler. This is equivalent 339 * to a `channels`/`namedChannels` parameter with a single 340 * value in the handler annotation, but 341 * here all kinds of Objects are allowed as values. As a 342 * convenience, if the actual object provided is a 343 * {@link Channel}, its default criterion is used for 344 * matching. 345 * @param priority 346 * the priority of the handler 347 */ 348 public static void add(ComponentType component, String method, 349 Object eventValue, Object channelValue, int priority) { 350 addInternal(component, method, eventValue, channelValue, priority); 351 } 352 353 /** 354 * Add a handler like 355 * {@link #add(ComponentType, String, Object, Object, int)} 356 * but take the values for event and priority from the annotation. 357 * 358 * @param component the component 359 * @param method the name of the method that implements the handler 360 * @param channelValue the channel value that should be used 361 * for matching an event's channel with this handler 362 */ 363 public static void add(ComponentType component, String method, 364 Object channelValue) { 365 addInternal(component, method, null, channelValue, null); 366 } 367 368 @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", 369 "PMD.CognitiveComplexity" }) 370 private static void addInternal(ComponentType component, String method, 371 Object eventValue, Object channelValue, Integer priority) { 372 try { 373 if (channelValue instanceof Channel) { 374 channelValue = ((Eligible) channelValue).defaultCriterion(); 375 } 376 for (Method m : component.getClass().getMethods()) { 377 if (!m.getName().equals(method)) { 378 continue; 379 } 380 for (Annotation annotation : m.getDeclaredAnnotations()) { 381 Class<?> annoType = annotation.annotationType(); 382 if (!annoType.equals(Handler.class)) { 383 continue; 384 } 385 HandlerDefinition hda 386 = annoType.getAnnotation(HandlerDefinition.class); 387 if (hda == null 388 || !Handler.class.isAssignableFrom(annoType) 389 || !((Handler) annotation).dynamic()) { 390 continue; 391 } 392 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 393 Scope scope = new Scope(component, m, 394 (Handler) annotation, Collections.emptyMap(), 395 eventValue == null ? null 396 : new Object[] { eventValue }, 397 new Object[] { channelValue }); 398 Components.manager(component).addHandler(m, scope, 399 priority == null 400 ? ((Handler) annotation).priority() 401 : priority); 402 return; 403 } 404 } 405 throw new IllegalArgumentException( 406 "No method named \"" + method + "\" with DynamicHandler" 407 + " annotation and correct parameter list."); 408 } catch (SecurityException e) { 409 throw (RuntimeException) new IllegalArgumentException() 410 .initCause(e); 411 } 412 } 413 414 /** 415 * The handler scope implementation used by the evaluator. 416 */ 417 private static class Scope implements HandlerScope { 418 419 private final Set<Object> eventCriteria = new HashSet<>(); 420 private final Set<Object> channelCriteria = new HashSet<>(); 421 422 /** 423 * Instantiates a new scope. 424 * 425 * @param component the component 426 * @param method the method 427 * @param annotation the annotation 428 * @param channelReplacements the channel replacements 429 * @param eventValues the event values 430 * @param channelValues the channel values 431 */ 432 @SuppressWarnings({ "PMD.CyclomaticComplexity", "PMD.NcssCount", 433 "PMD.NPathComplexity", "PMD.UseVarargs", 434 "PMD.AvoidDeeplyNestedIfStmts", "PMD.CollapsibleIfStatements", 435 "PMD.CognitiveComplexity" }) 436 public Scope(ComponentType component, Method method, 437 Handler annotation, 438 Map<Class<? extends Channel>, Object[]> channelReplacements, 439 Object[] eventValues, Object[] channelValues) { 440 if (!HandlerDefinition.Evaluator.checkMethodSignature(method)) { 441 throw new IllegalArgumentException("Method \"" 442 + method.toString() + "\" cannot be used as" 443 + " handler (wrong signature)."); 444 } 445 if (eventValues != null) { 446 eventCriteria.addAll(Arrays.asList(eventValues)); 447 } else { 448 // Get all event values from the handler annotation. 449 if (annotation.events()[0] != Handler.NoEvent.class) { 450 eventCriteria 451 .addAll(Arrays.asList(annotation.events())); 452 } 453 // Get all named events from the annotation and add to event 454 // keys. 455 if (!annotation.namedEvents()[0].isEmpty()) { 456 eventCriteria.addAll( 457 Arrays.asList(annotation.namedEvents())); 458 } 459 // If no event types are given, try first parameter. 460 if (eventCriteria.isEmpty()) { 461 Class<?>[] paramTypes = method.getParameterTypes(); 462 if (paramTypes.length > 0) { 463 if (Event.class.isAssignableFrom(paramTypes[0])) { 464 eventCriteria.add(paramTypes[0]); 465 } 466 } 467 } 468 } 469 470 if (channelValues != null) { 471 channelCriteria.addAll(Arrays.asList(channelValues)); 472 } else { 473 // Get channel values from the annotation. 474 boolean addDefaultChannel = false; 475 if (annotation.channels()[0] != Handler.NoChannel.class) { 476 for (Class<?> c : annotation.channels()) { 477 if (c == Self.class) { 478 if (!(component instanceof Channel)) { 479 throw new IllegalArgumentException( 480 "Canot use channel This.class in " 481 + "annotation of " + method 482 + " because " + getClass().getName() 483 + " does not implement Channel."); 484 } 485 // Will be added anyway, see below, but 486 // channelCriteria must not remain empty, 487 // else the default channel is added. 488 channelCriteria.add( 489 ((Channel) component).defaultCriterion()); 490 } else if (c == Channel.Default.class) { 491 addDefaultChannel = true; 492 } else { 493 if (channelReplacements != null 494 && channelReplacements.containsKey(c)) { 495 channelCriteria.addAll( 496 Arrays.asList(channelReplacements 497 .get(c))); 498 } else { 499 channelCriteria.add(c); 500 } 501 } 502 } 503 } 504 // Get named channels from annotation and add to channel 505 // keys. 506 if (!annotation.namedChannels()[0].isEmpty()) { 507 channelCriteria.addAll( 508 Arrays.asList(annotation.namedChannels())); 509 } 510 if (channelCriteria.isEmpty() || addDefaultChannel) { 511 channelCriteria.add(Components.manager(component) 512 .channel().defaultCriterion()); 513 } 514 } 515 // Finally, a component always handles events 516 // directed at it directly. 517 if (component instanceof Channel && !annotation.excludeSelf()) { 518 channelCriteria.add( 519 ((Channel) component).defaultCriterion()); 520 } 521 522 } 523 524 @Override 525 @SuppressWarnings("PMD.CognitiveComplexity") 526 public boolean includes(Eligible event, Eligible[] channels) { 527 for (Object eventValue : eventCriteria) { 528 if (event.isEligibleFor(eventValue)) { 529 // Found match regarding event, now try channels 530 for (Eligible channel : channels) { 531 for (Object channelValue : channelCriteria) { 532 if (channel.isEligibleFor(channelValue)) { 533 return true; 534 } 535 } 536 } 537 return false; 538 } 539 } 540 return false; 541 } 542 543 /* 544 * (non-Javadoc) 545 * 546 * @see java.lang.Object#toString() 547 */ 548 @Override 549 public String toString() { 550 StringBuilder builder = new StringBuilder(100); 551 builder.append("Scope ["); 552 if (eventCriteria != null) { 553 builder.append("handledEvents=") 554 .append(eventCriteria.stream().map(crit -> { 555 if (crit instanceof Class) { 556 return Components.className((Class<?>) crit); 557 } 558 return crit.toString(); 559 }).collect(Collectors.toSet())).append(", "); 560 } 561 if (channelCriteria != null) { 562 builder.append("handledChannels=").append(channelCriteria); 563 } 564 builder.append(']'); 565 return builder.toString(); 566 } 567 568 } 569 } 570}