001/* 002 * JGrapes Event Driven Framework 003 * Copyright (C) 2016-2018 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 License 013 * for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License along 016 * 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.CyclomaticComplexity", 369 "PMD.AvoidBranchingStatementAsLastInLoop", 370 "PMD.CognitiveComplexity" }) 371 private static void addInternal(ComponentType component, String method, 372 Object eventValue, Object channelValue, Integer priority) { 373 try { 374 if (channelValue instanceof Channel) { 375 channelValue = ((Eligible) channelValue).defaultCriterion(); 376 } 377 for (Method m : component.getClass().getMethods()) { 378 if (!m.getName().equals(method)) { 379 continue; 380 } 381 for (Annotation annotation : m.getDeclaredAnnotations()) { 382 Class<?> annoType = annotation.annotationType(); 383 if (!annoType.equals(Handler.class)) { 384 continue; 385 } 386 HandlerDefinition hda 387 = annoType.getAnnotation(HandlerDefinition.class); 388 if (hda == null 389 || !Handler.class.isAssignableFrom(annoType) 390 || !((Handler) annotation).dynamic()) { 391 continue; 392 } 393 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 394 Scope scope = new Scope(component, m, 395 (Handler) annotation, Collections.emptyMap(), 396 eventValue == null ? null 397 : new Object[] { eventValue }, 398 new Object[] { channelValue }); 399 Components.manager(component).addHandler(m, scope, 400 priority == null 401 ? ((Handler) annotation).priority() 402 : priority); 403 return; 404 } 405 } 406 throw new IllegalArgumentException( 407 "No method named \"" + method + "\" with DynamicHandler" 408 + " annotation and correct parameter list."); 409 } catch (SecurityException e) { 410 throw (RuntimeException) new IllegalArgumentException() 411 .initCause(e); 412 } 413 } 414 415 /** 416 * The handler scope implementation used by the evaluator. 417 */ 418 private static class Scope implements HandlerScope { 419 420 private final Set<Object> eventCriteria = new HashSet<>(); 421 private final Set<Object> channelCriteria = new HashSet<>(); 422 423 /** 424 * Instantiates a new scope. 425 * 426 * @param component the component 427 * @param method the method 428 * @param annotation the annotation 429 * @param channelReplacements the channel replacements 430 * @param eventValues the event values 431 * @param channelValues the channel values 432 */ 433 @SuppressWarnings({ "PMD.CyclomaticComplexity", "PMD.NcssCount", 434 "PMD.NPathComplexity", "PMD.UseVarargs", 435 "PMD.AvoidDeeplyNestedIfStmts", "PMD.CollapsibleIfStatements", 436 "PMD.CognitiveComplexity" }) 437 public Scope(ComponentType component, Method method, 438 Handler annotation, 439 Map<Class<? extends Channel>, Object[]> channelReplacements, 440 Object[] eventValues, Object[] channelValues) { 441 if (!HandlerDefinition.Evaluator.checkMethodSignature(method)) { 442 throw new IllegalArgumentException("Method \"" 443 + method.toString() + "\" cannot be used as" 444 + " handler (wrong signature)."); 445 } 446 if (eventValues != null) { // NOPMD, != is easier to read 447 eventCriteria.addAll(Arrays.asList(eventValues)); 448 } else { 449 // Get all event values from the handler annotation. 450 if (annotation.events()[0] != Handler.NoEvent.class) { 451 eventCriteria 452 .addAll(Arrays.asList(annotation.events())); 453 } 454 // Get all named events from the annotation and add to event 455 // keys. 456 if (!annotation.namedEvents()[0].isEmpty()) { 457 eventCriteria.addAll( 458 Arrays.asList(annotation.namedEvents())); 459 } 460 // If no event types are given, try first parameter. 461 if (eventCriteria.isEmpty()) { 462 Class<?>[] paramTypes = method.getParameterTypes(); 463 if (paramTypes.length > 0) { 464 if (Event.class.isAssignableFrom(paramTypes[0])) { 465 eventCriteria.add(paramTypes[0]); 466 } 467 } 468 } 469 } 470 471 if (channelValues != null) { // NOPMD, != is easier to read 472 channelCriteria.addAll(Arrays.asList(channelValues)); 473 } else { 474 // Get channel values from the annotation. 475 boolean addDefaultChannel = false; 476 if (annotation.channels()[0] != Handler.NoChannel.class) { 477 for (Class<?> c : annotation.channels()) { 478 if (c == Self.class) { 479 if (!(component instanceof Channel)) { 480 throw new IllegalArgumentException( 481 "Canot use channel This.class in " 482 + "annotation of " + method 483 + " because " + getClass().getName() 484 + " does not implement Channel."); 485 } 486 // Will be added anyway, see below, but 487 // channelCriteria must not remain empty, 488 // else the default channel is added. 489 channelCriteria.add( 490 ((Channel) component).defaultCriterion()); 491 } else if (c == Channel.Default.class) { 492 addDefaultChannel = true; 493 } else { 494 if (channelReplacements != null 495 && channelReplacements.containsKey(c)) { 496 channelCriteria.addAll( 497 Arrays.asList(channelReplacements 498 .get(c))); 499 } else { 500 channelCriteria.add(c); 501 } 502 } 503 } 504 } 505 // Get named channels from annotation and add to channel 506 // keys. 507 if (!annotation.namedChannels()[0].isEmpty()) { 508 channelCriteria.addAll( 509 Arrays.asList(annotation.namedChannels())); 510 } 511 if (channelCriteria.isEmpty() || addDefaultChannel) { 512 channelCriteria.add(Components.manager(component) 513 .channel().defaultCriterion()); 514 } 515 } 516 // Finally, a component always handles events 517 // directed at it directly. 518 if (component instanceof Channel && !annotation.excludeSelf()) { 519 channelCriteria.add( 520 ((Channel) component).defaultCriterion()); 521 } 522 523 } 524 525 @Override 526 @SuppressWarnings("PMD.CognitiveComplexity") 527 public boolean includes(Eligible event, Eligible[] channels) { 528 for (Object eventValue : eventCriteria) { 529 if (event.isEligibleFor(eventValue)) { 530 // Found match regarding event, now try channels 531 for (Eligible channel : channels) { 532 for (Object channelValue : channelCriteria) { 533 if (channel.isEligibleFor(channelValue)) { 534 return true; 535 } 536 } 537 } 538 return false; 539 } 540 } 541 return false; 542 } 543 544 /* 545 * (non-Javadoc) 546 * 547 * @see java.lang.Object#toString() 548 */ 549 @Override 550 public String toString() { 551 StringBuilder builder = new StringBuilder(100); 552 builder.append("Scope ["); 553 if (eventCriteria != null) { 554 builder.append("handledEvents=") 555 .append(eventCriteria.stream().map(crit -> { 556 if (crit instanceof Class) { 557 return Components.className((Class<?>) crit); 558 } 559 return crit.toString(); 560 }).collect(Collectors.toSet())).append(", "); 561 } 562 if (channelCriteria != null) { 563 builder.append("handledChannels=").append(channelCriteria); 564 } 565 builder.append(']'); 566 return builder.toString(); 567 } 568 569 } 570 } 571}