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. This results in a match if 084 * 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 hanler 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 * This class provides the {@link Evaluator} for the 270 * {@link Handler} annotation provided by the core package. It 271 * implements the behavior as described for the annotation. 272 */ 273 class Evaluator implements HandlerDefinition.Evaluator { 274 275 @Override 276 public HandlerScope scope( 277 ComponentType component, Method method, 278 ChannelReplacements channelReplacements) { 279 Handler annotation = method.getAnnotation(Handler.class); 280 if (annotation == null || annotation.dynamic()) { 281 return null; 282 } 283 return new Scope(component, method, annotation, 284 channelReplacements, null, null); 285 } 286 287 /* 288 * (non-Javadoc) 289 * 290 * @see 291 * org.jgrapes.core.annotation.HandlerDefinition.Evaluator#getPriority() 292 */ 293 @Override 294 public int priority(Annotation annotation) { 295 return ((Handler) annotation).priority(); 296 } 297 298 /** 299 * Adds the given method of the given component as a dynamic handler for 300 * a specific event and channel. The method with the given name must be 301 * annotated as dynamic handler and must have a single parameter of type 302 * {@link Event} (or a derived type as appropriate for the event type to 303 * be handled). It can have an optional parameter of type 304 * {@link Channel}. 305 * 306 * @param component 307 * the component 308 * @param method 309 * the name of the method that implements the handler 310 * @param eventValue 311 * the event key that should be used for matching this 312 * handler with an event. This is equivalent to an 313 * <code>events</code>/<code>namedEvents</code> parameter 314 * used with a single value in the handler annotation, but 315 * here all kinds of Objects are allowed as key values. 316 * @param channelValue 317 * the channel value that should be used for matching 318 * an event's channel with this handler. This is equivalent 319 * to a `channels`/`namedChannels` parameter with a single 320 * value in the handler annotation, but 321 * here all kinds of Objects are allowed as values. As a 322 * convenience, if the actual object provided is a 323 * {@link Channel}, its default criterion is used for 324 * matching. 325 * @param priority 326 * the priority of the handler 327 */ 328 public static void add(ComponentType component, String method, 329 Object eventValue, Object channelValue, int priority) { 330 addInternal(component, method, eventValue, channelValue, priority); 331 } 332 333 /** 334 * Add a handler like 335 * {@link #add(ComponentType, String, Object, Object, int)} 336 * but take the values for event and priority from the annotation. 337 * 338 * @param component the component 339 * @param method the name of the method that implements the handler 340 * @param channelValue the channel value that should be used 341 * for matching an event's channel with this handler 342 */ 343 public static void add(ComponentType component, String method, 344 Object channelValue) { 345 addInternal(component, method, null, channelValue, null); 346 } 347 348 @SuppressWarnings({ "PMD.CyclomaticComplexity", 349 "PMD.AvoidBranchingStatementAsLastInLoop", 350 "PMD.CognitiveComplexity" }) 351 private static void addInternal(ComponentType component, String method, 352 Object eventValue, Object channelValue, Integer priority) { 353 try { 354 if (channelValue instanceof Channel) { 355 channelValue = ((Eligible) channelValue).defaultCriterion(); 356 } 357 for (Method m : component.getClass().getMethods()) { 358 if (!m.getName().equals(method)) { 359 continue; 360 } 361 for (Annotation annotation : m.getDeclaredAnnotations()) { 362 Class<?> annoType = annotation.annotationType(); 363 if (!(annoType.equals(Handler.class))) { 364 continue; 365 } 366 HandlerDefinition hda 367 = annoType.getAnnotation(HandlerDefinition.class); 368 if (hda == null 369 || !Handler.class.isAssignableFrom(annoType) 370 || !((Handler) annotation).dynamic()) { 371 continue; 372 } 373 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 374 Scope scope = new Scope(component, m, 375 (Handler) annotation, Collections.emptyMap(), 376 eventValue == null ? null 377 : new Object[] { eventValue }, 378 new Object[] { channelValue }); 379 Components.manager(component).addHandler(m, scope, 380 priority == null 381 ? ((Handler) annotation).priority() 382 : priority); 383 return; 384 } 385 } 386 throw new IllegalArgumentException( 387 "No method named \"" + method + "\" with DynamicHandler" 388 + " annotation and correct parameter list."); 389 } catch (SecurityException e) { 390 throw (RuntimeException) (new IllegalArgumentException() 391 .initCause(e)); 392 } 393 } 394 395 /** 396 * The handler scope implementation used by the evaluator. 397 */ 398 private static class Scope implements HandlerScope { 399 400 private final Set<Object> eventCriteria = new HashSet<>(); 401 private final Set<Object> channelCriteria = new HashSet<>(); 402 403 /** 404 * Instantiates a new scope. 405 * 406 * @param component the component 407 * @param method the method 408 * @param annotation the annotation 409 * @param channelReplacements the channel replacements 410 * @param eventValues the event values 411 * @param channelValues the channel values 412 */ 413 @SuppressWarnings({ "PMD.CyclomaticComplexity", "PMD.NcssCount", 414 "PMD.NPathComplexity", "PMD.UseVarargs", 415 "PMD.AvoidDeeplyNestedIfStmts", "PMD.CollapsibleIfStatements", 416 "PMD.CognitiveComplexity" }) 417 public Scope(ComponentType component, Method method, 418 Handler annotation, 419 Map<Class<? extends Channel>, Object> channelReplacements, 420 Object[] eventValues, Object[] channelValues) { 421 if (!HandlerDefinition.Evaluator.checkMethodSignature(method)) { 422 throw new IllegalArgumentException("Method \"" 423 + method.toString() + "\" cannot be used as" 424 + " handler (wrong signature)."); 425 } 426 if (eventValues != null) { // NOPMD, != is easier to read 427 eventCriteria.addAll(Arrays.asList(eventValues)); 428 } else { 429 // Get all event values from the handler annotation. 430 if (annotation.events()[0] != Handler.NoEvent.class) { 431 eventCriteria 432 .addAll(Arrays.asList(annotation.events())); 433 } 434 // Get all named events from the annotation and add to event 435 // keys. 436 if (!annotation.namedEvents()[0].isEmpty()) { 437 eventCriteria.addAll( 438 Arrays.asList(annotation.namedEvents())); 439 } 440 // If no event types are given, try first parameter. 441 if (eventCriteria.isEmpty()) { 442 Class<?>[] paramTypes = method.getParameterTypes(); 443 if (paramTypes.length > 0) { 444 if (Event.class.isAssignableFrom(paramTypes[0])) { 445 eventCriteria.add(paramTypes[0]); 446 } 447 } 448 } 449 } 450 451 if (channelValues != null) { // NOPMD, != is easier to read 452 channelCriteria.addAll(Arrays.asList(channelValues)); 453 } else { 454 // Get channel values from the annotation. 455 boolean addDefaultChannel = false; 456 if (annotation.channels()[0] != Handler.NoChannel.class) { 457 for (Class<?> c : annotation.channels()) { 458 if (c == Self.class) { 459 if (!(component instanceof Channel)) { 460 throw new IllegalArgumentException( 461 "Canot use channel This.class in " 462 + "annotation of " + method 463 + " because " + getClass().getName() 464 + " does not implement Channel."); 465 } 466 // Will be added anyway, see below, but 467 // channelCriteria must not remain empty, 468 // else the default channel is added. 469 channelCriteria.add( 470 ((Channel) component).defaultCriterion()); 471 } else if (c == Channel.Default.class) { 472 addDefaultChannel = true; 473 } else { 474 channelCriteria.add(channelReplacements == null 475 ? c 476 : channelReplacements.getOrDefault(c, c)); 477 } 478 } 479 } 480 // Get named channels from annotation and add to channel 481 // keys. 482 if (!annotation.namedChannels()[0].isEmpty()) { 483 channelCriteria.addAll( 484 Arrays.asList(annotation.namedChannels())); 485 } 486 if (channelCriteria.isEmpty() || addDefaultChannel) { 487 channelCriteria.add(Components.manager(component) 488 .channel().defaultCriterion()); 489 } 490 } 491 // Finally, a component always handles events 492 // directed at it directly. 493 if (component instanceof Channel) { 494 channelCriteria.add( 495 ((Channel) component).defaultCriterion()); 496 } 497 498 } 499 500 @Override 501 @SuppressWarnings("PMD.CognitiveComplexity") 502 public boolean includes(Eligible event, Eligible[] channels) { 503 for (Object eventValue : eventCriteria) { 504 if (event.isEligibleFor(eventValue)) { 505 // Found match regarding event, now try channels 506 for (Eligible channel : channels) { 507 for (Object channelValue : channelCriteria) { 508 if (channel.isEligibleFor(channelValue)) { 509 return true; 510 } 511 } 512 } 513 return false; 514 } 515 } 516 return false; 517 } 518 519 /* 520 * (non-Javadoc) 521 * 522 * @see java.lang.Object#toString() 523 */ 524 @Override 525 public String toString() { 526 StringBuilder builder = new StringBuilder(); 527 builder.append("Scope ["); 528 if (eventCriteria != null) { 529 builder.append("handledEvents=") 530 .append(eventCriteria.stream().map(crit -> { 531 if (crit instanceof Class) { 532 return Components.className((Class<?>) crit); 533 } 534 return crit.toString(); 535 }).collect(Collectors.toSet())); 536 builder.append(", "); 537 } 538 if (channelCriteria != null) { 539 builder.append("handledChannels="); 540 builder.append(channelCriteria); 541 } 542 builder.append(']'); 543 return builder.toString(); 544 } 545 546 } 547 } 548}