001package org.nasdanika.graph.processor; 002 003import java.lang.reflect.AnnotatedElement; 004import java.lang.reflect.Field; 005import java.lang.reflect.Member; 006import java.lang.reflect.Method; 007import java.lang.reflect.Modifier; 008import java.util.ArrayList; 009import java.util.Collection; 010import java.util.Collections; 011import java.util.HashSet; 012import java.util.LinkedHashMap; 013import java.util.List; 014import java.util.Map; 015import java.util.Map.Entry; 016import java.util.Objects; 017import java.util.Optional; 018import java.util.Set; 019import java.util.concurrent.CompletionStage; 020import java.util.function.BiConsumer; 021import java.util.function.Consumer; 022import java.util.function.Function; 023import java.util.function.Supplier; 024import java.util.stream.Stream; 025 026import org.nasdanika.common.NasdanikaException; 027import org.nasdanika.common.ProgressMonitor; 028import org.nasdanika.common.Reflector; 029import org.nasdanika.graph.Connection; 030import org.nasdanika.graph.Element; 031 032/** 033 * {@link Supplier} of a {@link ProcessorFactory} which reflecitvely creates processors and wires hanlders and endpoints using annotations. 034 * @author Pavel 035 * 036 * @param <P> 037 * @param <T> 038 * @param <R> 039 * @param <U> 040 * @param <S> 041 */ 042public class ReflectiveProcessorFactoryProvider<P, H, E> extends Reflector { 043 044 private List<AnnotatedElementRecord> annotatedElementRecords = new ArrayList<>(); 045 046 public ReflectiveProcessorFactoryProvider(Object... targets) { 047 for (Object target: targets) { 048 getAnnotatedElementRecords(target, Collections.singletonList(target)).forEach(annotatedElementRecords::add); 049 } 050 } 051 052 public ProcessorFactory<P> getFactory(Object... registryTargets) { 053 return new ProcessorFactory<P>() { 054 055 @Override 056 protected ProcessorInfo<P> createProcessor( 057 ProcessorConfig config, 058 boolean parallel, 059 BiConsumer<Element,BiConsumer<ProcessorInfo<P>,ProgressMonitor>> infoProvider, 060 Consumer<CompletionStage<?>> endpointWiringStageConsumer, 061 ProgressMonitor progressMonitor) { 062 063 P processor = ReflectiveProcessorFactoryProvider.this.createProcessor(config, parallel, infoProvider, endpointWiringStageConsumer, progressMonitor); 064 return ProcessorInfo.of(config, processor); 065 } 066 067 @Override 068 public Map<Element, ProcessorInfo<P>> createProcessors(Collection<ProcessorConfig> configs, boolean parallel, ProgressMonitor progressMonitor) { 069 Map<Element, ProcessorInfo<P>> registry = super.createProcessors(configs, parallel, progressMonitor); 070 for (Object registryTarget: registryTargets) { 071 BiConsumer<Element, BiConsumer<ProcessorInfo<P>,ProgressMonitor>> infoProvider = (e, bc) -> { 072 bc.accept(registry.get(e), progressMonitor); 073 }; 074 075 List<AnnotatedElementRecord> registryTargetAnnotatedElementRecords = getAnnotatedElementRecords(registryTarget, Collections.singletonList(registryTarget)).toList(); 076 wireRegistryEntry(parallel ? registryTargetAnnotatedElementRecords.parallelStream() : registryTargetAnnotatedElementRecords.stream(), configs, infoProvider); 077 wireRegistry(parallel ? registryTargetAnnotatedElementRecords.parallelStream() : registryTargetAnnotatedElementRecords.stream(), configs, infoProvider); 078 } 079 080 return registry; 081 } 082 083 }; 084 } 085 086 @SuppressWarnings("unchecked") 087 protected P createProcessor( 088 ProcessorConfig config, 089 boolean parallel, 090 BiConsumer<Element,BiConsumer<ProcessorInfo<P>,ProgressMonitor>> infoProvider, 091 Consumer<CompletionStage<?>> endpointWiringStageConsumer, 092 ProgressMonitor progressMonitor) { 093 094 // TODO - progress steps. 095 Optional<AnnotatedElementRecord> match = (parallel ? annotatedElementRecords.parallelStream() : annotatedElementRecords.stream()) 096 .filter(r -> r.test(config.getElement()) && r.getAnnotatedElement() instanceof Method && matchFactoryMethod(config, (Method) r.getAnnotatedElement())) 097 .sorted((a, b) -> compareProcessorMethods((Method) a.getAnnotatedElement(), (Method) b.getAnnotatedElement())) 098 .findFirst(); 099 100 if (match.isEmpty()) { 101 return null; 102 } 103 104 AnnotatedElementRecord matchedRecord = match.get(); 105 Method method = (Method) matchedRecord.getAnnotatedElement(); 106 P processor = (P) matchedRecord.invoke(config, parallel, infoProvider, progressMonitor); 107 if (processor == null) { 108 return processor; 109 } 110 Processor elementProcessorAnnotation = method.getAnnotation(Processor.class); 111 112 List<AnnotatedElementRecord> processorAnnotatedElementRecords = getAnnotatedElementRecords(processor, Collections.emptyList()).toList(); 113 Supplier<Stream<AnnotatedElementRecord>> processorAnnotatedElementRecordsStreamSupplier = () -> parallel ? processorAnnotatedElementRecords.parallelStream() : processorAnnotatedElementRecords.stream(); 114 115 boolean hideWired = elementProcessorAnnotation.hideWired(); 116 Map<Element, ProcessorConfig> childProcessorConfigsCopy = new LinkedHashMap<>(config.getChildProcessorConfigs()); 117 wireChildProcessor( 118 processorAnnotatedElementRecordsStreamSupplier.get(), 119 config.getChildProcessorConfigs(), 120 infoProvider, 121 childProcessorConfigsCopy::remove); 122 123 wireChildProcessors( 124 processorAnnotatedElementRecordsStreamSupplier.get(), 125 childProcessorConfigsCopy , 126 infoProvider); 127 128 ProcessorConfig parentConfig = config.getParentProcessorConfig(); 129 if (parentConfig != null) { 130 Consumer<Consumer<ProcessorInfo<P>>> parentProcessorInfoConsumerCallback = parentProcessorInfoConsumer -> { 131 infoProvider.accept(parentConfig.getElement(), (pInfo, pMonitor) -> parentProcessorInfoConsumer.accept(pInfo)); 132 }; 133 wireParentProcessor(processorAnnotatedElementRecordsStreamSupplier.get(), parentProcessorInfoConsumerCallback); 134 } 135 136 wireProcessorElement(processorAnnotatedElementRecordsStreamSupplier.get(), config.getElement()); 137 138 wireRegistryEntry( 139 processorAnnotatedElementRecordsStreamSupplier.get(), 140 config.getRegistry().values(), 141 infoProvider); 142 143 wireRegistry( 144 processorAnnotatedElementRecordsStreamSupplier.get(), 145 config.getRegistry().values(), 146 infoProvider); 147 148 if (config instanceof NodeProcessorConfig) { 149 NodeProcessorConfig<H, E> nodeProcessorConfig = (NodeProcessorConfig<H, E>) config; 150 Map<Connection, CompletionStage<E>> unwiredIncomingEndpoints = new LinkedHashMap<>(nodeProcessorConfig.getIncomingEndpoints()); // Making a copy to remove wired on completion 151 wireIncomingEndpoint( 152 processorAnnotatedElementRecordsStreamSupplier.get(), 153 nodeProcessorConfig.getIncomingEndpoints(), 154 progressMonitor) 155 .forEach(r -> { 156 if (r.consume()) { 157 unwiredIncomingEndpoints.remove(r.connection()); 158 } 159 endpointWiringStageConsumer.accept(r.result()); 160 }); 161 162 wireIncomingEndpoints(processorAnnotatedElementRecordsStreamSupplier.get(), unwiredIncomingEndpoints); 163 164 Map<Connection, Consumer<H>> incomingHandlerConsumers = nodeProcessorConfig.getIncomingHandlerConsumers(); 165 Collection<Connection> wiredHandlerIncomingConnections = wireIncomingHandler(processorAnnotatedElementRecordsStreamSupplier.get(), incomingHandlerConsumers); 166 Map<Connection, Consumer<H>> unwiredIncomingHandlerConsumers; 167 if (hideWired) { 168 unwiredIncomingHandlerConsumers = new LinkedHashMap<>(incomingHandlerConsumers); 169 unwiredIncomingHandlerConsumers.keySet().removeAll(wiredHandlerIncomingConnections); 170 } else { 171 unwiredIncomingHandlerConsumers = incomingHandlerConsumers; 172 } 173 wireIncomingHandlerConsumers(processorAnnotatedElementRecordsStreamSupplier.get(), unwiredIncomingHandlerConsumers); 174 175 Map<Connection, CompletionStage<E>> outgoingEndpoints = new LinkedHashMap<>(nodeProcessorConfig.getOutgoingEndpoints()); // Making a copy to removed wired on completion 176 wireOutgoingEndpoint( 177 processorAnnotatedElementRecordsStreamSupplier.get(), 178 outgoingEndpoints, 179 progressMonitor) 180 .forEach(r -> { 181 if (r.consume()) { 182 outgoingEndpoints.remove(r.connection()); 183 } 184 endpointWiringStageConsumer.accept(r.result()); 185 }); 186 wireOutgoingEndpoints(processorAnnotatedElementRecordsStreamSupplier.get(), outgoingEndpoints); 187 188 Map<Connection, Consumer<H>> outgoingHandlerConsumers = nodeProcessorConfig.getOutgoingHandlerConsumers(); 189 Collection<Connection> wiredHandlerOutgoingConnections = wireOutgoingHandler(processorAnnotatedElementRecordsStreamSupplier.get(), outgoingHandlerConsumers); 190 Map<Connection, Consumer<H>> unwiredOutgoingHandlerConsumers; 191 if (hideWired) { 192 unwiredOutgoingHandlerConsumers = new LinkedHashMap<>(outgoingHandlerConsumers); 193 unwiredOutgoingHandlerConsumers.keySet().removeAll(wiredHandlerOutgoingConnections); 194 } else { 195 unwiredOutgoingHandlerConsumers = outgoingHandlerConsumers; 196 } 197 wireOutgoingHandlerConsumers(processorAnnotatedElementRecordsStreamSupplier.get(), unwiredOutgoingHandlerConsumers); 198 } else if (config instanceof ConnectionProcessorConfig) { 199 ConnectionProcessorConfig<H, E> connectionProcessorConfig = (ConnectionProcessorConfig<H, E>) config; 200 Consumer<E> sourceEndpointConsumer = wireSourceEndpoint(processorAnnotatedElementRecordsStreamSupplier.get()); 201 if (sourceEndpointConsumer != null) { 202 endpointWiringStageConsumer.accept(connectionProcessorConfig.getSourceEndpoint().thenAccept(sourceEndpointConsumer)); 203 } 204 wireSourceHandler(processorAnnotatedElementRecordsStreamSupplier.get(), connectionProcessorConfig); 205 206 Consumer<E> targetEndpointConsumer = wireTargetEndpoint(processorAnnotatedElementRecordsStreamSupplier.get()); 207 if (targetEndpointConsumer != null) { 208 endpointWiringStageConsumer.accept(connectionProcessorConfig.getTargetEndpoint().thenAccept(targetEndpointConsumer)); 209 } 210 wireTargetHandler(processorAnnotatedElementRecordsStreamSupplier.get(), connectionProcessorConfig); 211 } 212 213 return (P) processor; 214 } 215 216 protected int compareProcessorMethods(Method a, Method b) { 217 int priorityCmp = b.getAnnotation(Processor.class).priority() - a.getAnnotation(Processor.class).priority(); 218 if (priorityCmp != 0) { 219 return priorityCmp; 220 } 221 222 Class<? extends Element> aType = a.getAnnotation(Processor.class).type(); 223 Class<? extends Element> bType = b.getAnnotation(Processor.class).type(); 224 if (!Objects.equals(aType, bType)) { 225 if (aType.isAssignableFrom(bType)) { 226 // b is more specific 227 return 1; 228 } 229 if (bType.isAssignableFrom(aType)) { 230 // a is more specific 231 return -1; 232 } 233 } 234 235 // Method in a sub-class is more specific 236 Class<?> adc = a.getDeclaringClass(); 237 Class<?> bdc = b.getDeclaringClass(); 238 if (adc.isAssignableFrom(bdc)) { 239 return adc == bdc ? 0 : 1; 240 } 241 242 if (bdc.isAssignableFrom(adc)) { 243 return -1; 244 } 245 246 // Taking config is more specific 247 int paramCountCmp = b.getParameterCount() - a.getParameterCount(); 248 if (paramCountCmp != 0) { 249 return paramCountCmp; 250 } 251 252 return a.getName().compareTo(b.getName()); 253 } 254 255 /** 256 * Records don't work with non-static types. 257 */ 258 protected class RegistryMatch { 259 RegistryEntry annotation; 260 AnnotatedElementRecord setterRecord; 261 ProcessorConfig config; 262 263 public RegistryMatch( 264 RegistryEntry annotation, 265 AnnotatedElementRecord setterRecord, 266 ProcessorConfig config) { 267 super(); 268 this.annotation = annotation; 269 this.setterRecord = setterRecord; 270 this.config = config; 271 } 272 273 } 274 275 protected void wireRegistryEntry( 276 Stream<AnnotatedElementRecord> processorAnnotatedElementRecords, 277 Collection<ProcessorConfig> registry, 278 BiConsumer<Element,BiConsumer<ProcessorInfo<P>,ProgressMonitor>> infoProvider) { 279 280 processorAnnotatedElementRecords 281 .filter(ae -> ae.getAnnotation(RegistryEntry.class) != null) 282 .filter(ae -> ae.mustSet(null, "Fields/methods annotated with RegistryEntry must have (parameter) type assignable from the processor type or ProcessorInfo if info is set to true: " + ae.getAnnotatedElement())) 283 .flatMap(setterRecord -> registry.stream().map(re -> new RegistryMatch(setterRecord.getAnnotation(RegistryEntry.class), setterRecord, re))) 284 .filter(rm -> matchPredicate(rm.config.getElement(), rm.annotation.value())) 285 .forEach(rm -> { 286 infoProvider.accept(rm.config.getElement(), (rpInfo, pMonitor) -> { 287 rm.setterRecord.set(rm.annotation.info() ? rpInfo : rpInfo.getProcessor()); 288 }); 289 }); 290 } 291 292 protected void wireRegistry( 293 Stream<AnnotatedElementRecord> processorAnnotatedElementRecords, 294 Collection<ProcessorConfig> registry, 295 BiConsumer<Element,BiConsumer<ProcessorInfo<P>,ProgressMonitor>> infoProvider) { 296 297 processorAnnotatedElementRecords 298 .filter(aer -> aer.getAnnotation(Registry.class) != null) 299 .filter(aer -> aer.mustSet(Map.class, "Fields/methods annotated with Registry must have (parameter) type assignable from Map: " + aer.getAnnotatedElement())) 300 .forEach(setterRecord -> { 301 // Sets a map which is populated as processors get created 302 Map<Element,ProcessorInfo<P>> r = Collections.synchronizedMap(new LinkedHashMap<>()); 303 setterRecord.set(r); 304 for (ProcessorConfig re: registry) { 305 infoProvider.accept(re.getElement(), (rp, pm) -> r.put(re.getElement(), rp)); 306 } 307 }); 308 } 309 310 protected void wireProcessorElement(Stream<AnnotatedElementRecord> processorAnnotatedElementRecords, Element element) { 311 processorAnnotatedElementRecords 312 .filter(aer -> aer.getAnnotation(ProcessorElement.class) != null) 313 .filter(aer -> aer.mustSet(element.getClass(), "Methods annotated with ProcessorElement must have one parameter compatible with the processor element type (" + element.getClass() + "): " + aer.getAnnotatedElement())) 314 .forEach(aer -> aer.set(element)); 315 } 316 317 protected void wireParentProcessor( 318 Stream<AnnotatedElementRecord> processorAnnotatedElementRecords, 319 Consumer<Consumer<ProcessorInfo<P>>> parentProcessorInfoProvider) { 320 321 processorAnnotatedElementRecords 322 .filter(aer -> aer.getAnnotation(ParentProcessor.class) != null) 323 .filter(aer -> aer.mustSet(null, "Fields/methods annotated with ParentProcessor must have (parameter) type assignable from the processor type or ProcessorInfo if value is set to true: " + aer.getAnnotatedElement())) 324 .forEach(aer -> parentProcessorInfoProvider.accept(parentProcessorInfo -> aer.set(aer.getAnnotation(ParentProcessor.class).value() ? parentProcessorInfo : parentProcessorInfo.getProcessor()))); 325 } 326 327 /** 328 * 329 * @param processorAnnotatedElementRecords 330 * @param childProcessorConfigs 331 * @param infoProvider 332 * @param wiredChildrenConsumer accepts wired children 333 */ 334 private void wireChildProcessor( 335 Stream<AnnotatedElementRecord> processorAnnotatedElementRecords, 336 Map<Element, ProcessorConfig> childProcessorConfigs, 337 BiConsumer<Element,BiConsumer<ProcessorInfo<P>,ProgressMonitor>> infoProvider, 338 Consumer<Element> wiredChildrenConsumer) { 339 340 processorAnnotatedElementRecords 341 .filter(aer -> aer.getAnnotation(ChildProcessor.class) != null) 342 .filter(aer -> aer.mustSet(null, "Fields/methods annotated with ChildProcessor must have (parameter) type assignable from the processor type or ProcessorConfig if config is set to true: " + aer.getAnnotatedElement())) 343 .forEach(aer -> { 344 for (Entry<Element, ProcessorConfig> ce: childProcessorConfigs.entrySet()) { 345 ChildProcessor childProcessorAnnotation = aer.getAnnotation(ChildProcessor.class); 346 if (matchPredicate(ce.getKey(), childProcessorAnnotation.value())) { 347 infoProvider.accept(ce.getKey(), (childProcessorInfo, pMonitor) -> { 348 aer.set(childProcessorAnnotation.info() ? childProcessorInfo : childProcessorInfo.getProcessor()); 349 wiredChildrenConsumer.accept(ce.getKey()); 350 }); 351 }; 352 } 353 }); 354 } 355 356 protected void wireChildProcessors( 357 Stream<AnnotatedElementRecord> processorAnnotatedElementRecords, 358 Map<Element, ProcessorConfig> childProcessorConfigs, 359 BiConsumer<Element,BiConsumer<ProcessorInfo<P>,ProgressMonitor>> infoProvider) { 360 361 processorAnnotatedElementRecords 362 .filter(aer -> aer.getAnnotation(ChildProcessors.class) != null) 363 .filter(aer -> aer.mustSet(Map.class, "Fields/methods annotated with ChildProcessors must have (parameter) type assignable from Map: " + aer.getAnnotatedElement())) 364 .forEach(aer -> { 365 // Sets a map which is populated as processors get created 366 Map<Element,ProcessorInfo<P>> childProcessorInfoMap = Collections.synchronizedMap(new LinkedHashMap<>()); 367 aer.set(childProcessorInfoMap); 368 for (ProcessorConfig childConfig: childProcessorConfigs.values()) { 369 infoProvider.accept(childConfig.getElement(), (childInfo, cpm) -> childProcessorInfoMap.put(childConfig.getElement(), childInfo)); 370 } 371 }); 372 } 373 374 /** 375 * Matches processor field or method and incoming connection. 376 * @return 377 */ 378 protected boolean matchIncomingHandler(AnnotatedElement handlerMember, Connection incomingConnection) { 379 IncomingHandler incomingHandlerAnnotation = handlerMember.getAnnotation(IncomingHandler.class); 380 if (incomingHandlerAnnotation == null) { 381 return false; 382 } 383 384 if (handlerMember instanceof Method) { 385 Method handlerMethod = (Method) handlerMember; 386 int pc = handlerMethod.getParameterCount(); 387 if (pc > 1) { 388 throw new NasdanikaException("A method annotated with IncomingHandler shall have zero or one parameter: " + handlerMethod); 389 } 390 if (pc == 1 && !handlerMethod.getParameterTypes()[0].isInstance(incomingConnection)) { 391 return false; 392 } 393 } 394 395 return matchPredicate(incomingConnection, incomingHandlerAnnotation.value()); 396 } 397 398 // Node wiring 399 400 /** 401 * 402 * @param processor 403 * @param incomingHandlerConsumers 404 * @param parallel 405 * @return Wired connections 406 */ 407 @SuppressWarnings("unchecked") 408 protected Collection<Connection> wireIncomingHandler( 409 Stream<AnnotatedElementRecord> processorAnnotatedElementRecords, 410 Map<Connection, Consumer<H>> incomingHandlerConsumers) { 411 412 Set<Connection> wired = Collections.synchronizedSet(new HashSet<>()); 413 414 // Streaming fields and methods and then flat mapping them to all permutations with incoming handler consumers. 415 // then filtering using matchIncomingHandler, sorting by priority, for all matching - wiring and removing from ret. 416 processorAnnotatedElementRecords 417 .filter(aer -> !Modifier.isAbstract(((Member) aer.getAnnotatedElement()).getModifiers())) 418 .flatMap(aer -> incomingHandlerConsumers.entrySet().stream().map(ihce -> new ConnectionMatch<Consumer<H>>( 419 aer, 420 ihce.getKey(), 421 ihce.getValue(), 422 ao -> ao.getAnnotation(IncomingHandler.class).priority(), 423 ao -> ao.getAnnotation(IncomingHandler.class).value()))) 424 .filter(mr -> matchIncomingHandler(mr.annotatedElementRecord.getAnnotatedElement(), mr.connection)) 425 .sorted() 426 .forEach(mr -> { 427 AnnotatedElement handlerMember = mr.annotatedElementRecord.getAnnotatedElement(); 428 Connection incomingConnection = mr.connection; 429 if (wired.add(incomingConnection)) { // Wiring once 430 if (handlerMember instanceof Field) { 431 mr.value.accept((H) mr.annotatedElementRecord.get()); 432 } else { 433 Method handlerSupplierMethod = (Method) handlerMember; 434 Object incomingHandler = handlerSupplierMethod.getParameterCount() == 0 ? mr.annotatedElementRecord.get() : mr.annotatedElementRecord.invoke(incomingConnection); 435 mr.value.accept((H) incomingHandler); 436 } 437 } 438 }); 439 440 return wired; 441 } 442 443 protected void wireIncomingHandlerConsumers( 444 Stream<AnnotatedElementRecord> processorAnnotatedElementRecords, 445 Map<Connection, Consumer<H>> incomingHandlerConsumers) { 446 447 processorAnnotatedElementRecords 448 .filter(aer -> aer.getAnnotation(IncomingHandlerConsumers.class) != null) 449 .filter(aer -> aer.mustSet(Map.class, "Fields/methods annotated with IncomingHandlersConsumers must have (parameter) type assignable from Map: " + aer.getAnnotatedElement())) 450 .forEach(aer -> aer.set(incomingHandlerConsumers)); 451 } 452 453 /** 454 * Matches processor field or method and incoming connection. 455 * @return 456 */ 457 protected boolean matchIncomingEndpoint(AnnotatedElement endpointMember, Connection incomingConnection) { 458 IncomingEndpoint incomingEndpointAnnotation = endpointMember.getAnnotation(IncomingEndpoint.class); 459 if (incomingEndpointAnnotation == null) { 460 return false; 461 } 462 463 if (endpointMember instanceof Method) { 464 Method endpointMethod = (Method) endpointMember; 465 int pc = endpointMethod.getParameterCount(); 466 if (pc == 0 || pc > 3) { 467 throw new NasdanikaException("A method annotated with IncomingEndpoint shall have 1 - 3 parameters: " + endpointMethod); 468 } 469 if (pc > 1 && !endpointMethod.getParameterTypes()[0].isInstance(incomingConnection)) { 470 return false; 471 } 472 } 473 474 return matchPredicate(incomingConnection, incomingEndpointAnnotation.value()); 475 } 476 477 protected class ConnectionMatch<T> implements Comparable<ConnectionMatch<T>> { 478 479 AnnotatedElementRecord annotatedElementRecord; 480 Connection connection; 481 T value; 482 Function<AnnotatedElement, Integer> priorityGetter; 483 Function<AnnotatedElement, String> selectorGetter; 484 485 ConnectionMatch( 486 AnnotatedElementRecord annotatedElementRecord, 487 Connection connection, 488 T value, 489 Function<AnnotatedElement, Integer> priorityGetter, 490 Function<AnnotatedElement, String> selectorGetter) { 491 super(); 492 this.annotatedElementRecord = annotatedElementRecord; 493 this.connection = connection; 494 this.value = value; 495 this.priorityGetter = priorityGetter; 496 this.selectorGetter = selectorGetter; 497 } 498 499 @Override 500 public int compareTo(ConnectionMatch<T> o) { 501 AnnotatedElement a = annotatedElementRecord.getAnnotatedElement(); 502 AnnotatedElement b = o.annotatedElementRecord.getAnnotatedElement(); 503 504 if (priorityGetter != null) { 505 Integer pa = priorityGetter.apply(a); 506 Integer pb = priorityGetter.apply(b); 507 if (!Objects.equals(pa, pb)) { 508 return pb - pa; 509 } 510 } 511 if (a instanceof Member && b instanceof Member) { 512 Class<?> adc = ((Member) a).getDeclaringClass(); 513 Class<?> bdc = ((Member) b).getDeclaringClass(); 514 if (adc.isAssignableFrom(bdc)) { 515 return adc == bdc ? 0 : 1; 516 } 517 518 if (bdc.isAssignableFrom(adc)) { 519 return -1; 520 } 521 } 522 if (selectorGetter != null) { 523 String sa = selectorGetter.apply(a); 524 String sb = selectorGetter.apply(b); 525 if (sa.length() != sb.length()) { 526 return sb.length() - sa.length(); 527 } 528 } 529 530 return a.hashCode() - b.hashCode(); 531 } 532 533 } 534 535 private record EndpointWireRecord(Connection connection, CompletionStage<Void> result, boolean consume) {}; 536 537 /** 538 * @param processor 539 * @param incomingEndpoints 540 * @param progressMonitor 541 * @return Wired incoming endpoints for collection of failures 542 */ 543 protected Stream<EndpointWireRecord> wireIncomingEndpoint( 544 Stream<AnnotatedElementRecord> processorAnnotatedElementRecords, 545 Map<Connection, CompletionStage<E>> incomingEndpoints, 546 ProgressMonitor progressMonitor) { 547 548 Set<Field> wiredFields = Collections.synchronizedSet(new HashSet<>()); // For setting a field once, setter methods may be invoked multiple times. 549 Set<Connection> consumedConnections = Collections.synchronizedSet(new HashSet<>()); // For wiring a connection once. 550 551 // Streaming fields and methods and then flat mapping them to all permutations with incoming endpoints. 552 // then filtering using matchIncomingEndpoint, sorting by priority, wiring all matching and removing from ret. 553 return processorAnnotatedElementRecords 554 .filter(aer -> !Modifier.isAbstract(((Member) aer.getAnnotatedElement()).getModifiers())) 555 .flatMap(aer -> incomingEndpoints.entrySet().stream().map(iee -> new ConnectionMatch<CompletionStage<E>>( 556 aer, 557 iee.getKey(), 558 iee.getValue(), 559 ao -> ao.getAnnotation(IncomingEndpoint.class).priority(), 560 ao -> ao.getAnnotation(IncomingEndpoint.class).value()))) 561 .filter(mr -> matchIncomingEndpoint(mr.annotatedElementRecord.getAnnotatedElement(), mr.connection)) 562 .sorted() 563 .map(mr -> { 564 AnnotatedElement endpointMember = mr.annotatedElementRecord.getAnnotatedElement(); 565 Connection incomingConnection = mr.connection; 566 IncomingEndpoint incomingEndpointAnnotation = endpointMember.getAnnotation(IncomingEndpoint.class); 567 boolean consumed = incomingEndpointAnnotation.consume() && !consumedConnections.add(incomingConnection); 568 boolean fieldWired = endpointMember instanceof Field && !wiredFields.add((Field) endpointMember); 569 if (!consumed && !fieldWired) { // Wiring an endpoint once is consumed and setting a field once 570 CompletionStage<Void> result = mr.value.thenAccept(incomingEndpoint -> { 571 if (endpointMember instanceof Field) { 572 mr.annotatedElementRecord.set(incomingEndpoint); 573 } else { 574 Method endpointMethod = (Method) endpointMember; 575 switch (endpointMethod.getParameterCount()) { 576 case 1: 577 mr.annotatedElementRecord.invoke(incomingEndpoint); 578 break; 579 case 2: 580 mr.annotatedElementRecord.invoke(incomingConnection, incomingEndpoint); 581 break; 582 case 3: 583 mr.annotatedElementRecord.invoke(incomingConnection, incomingEndpoint, progressMonitor); 584 break; 585 default: 586 throw new NasdanikaException("Incoming endpoint method shall have 1 to 3 parameters: " + endpointMethod); 587 } 588 } 589 }); 590 return new EndpointWireRecord(incomingConnection, result, incomingEndpointAnnotation.consume()); 591 } 592 return null; 593 }) 594 .filter(Objects::nonNull); 595 } 596 597 protected void wireIncomingEndpoints( 598 Stream<AnnotatedElementRecord> processorAnnotatedElementRecords, 599 Map<Connection, CompletionStage<E>> incomingEndpoints) { 600 processorAnnotatedElementRecords 601 .filter(aer -> aer.getAnnotation(IncomingEndpoints.class) != null) 602 .filter(aer -> aer.mustSet(Map.class, "Fields/methods annotated with IncomingEndpoints must have (parameter) type assignable from Map: " + aer.getAnnotatedElement())) 603 .forEach(aer -> aer.set(incomingEndpoints)); 604 } 605 606 /** 607 * Matches processor field or method and outgoing connection. 608 * @return 609 */ 610 protected boolean matchOutgoingHandler(AnnotatedElement handlerMember, Connection outgoingConnection) { 611 OutgoingHandler outgoingHandlerAnnotation = handlerMember.getAnnotation(OutgoingHandler.class); 612 if (outgoingHandlerAnnotation == null) { 613 return false; 614 } 615 616 if (handlerMember instanceof Method) { 617 Method handlerMethod = (Method) handlerMember; 618 int pc = handlerMethod.getParameterCount(); 619 if (pc > 1) { 620 throw new NasdanikaException("A method annotated with OutgoingHandler shall have zero or one parameter: " + handlerMethod); 621 } 622 if (pc == 1 && !handlerMethod.getParameterTypes()[0].isInstance(outgoingConnection)) { 623 return false; 624 } 625 } 626 627 return matchPredicate(outgoingConnection, outgoingHandlerAnnotation.value()); 628 } 629 630 /** 631 * @param processor 632 * @param outgoingHandlerConsumers 633 * @param parallel 634 * @return Wired connections 635 */ 636 @SuppressWarnings("unchecked") 637 protected Collection<Connection> wireOutgoingHandler( 638 Stream<AnnotatedElementRecord> processorAnnotatedElementRecords, 639 Map<Connection, Consumer<H>> outgoingHandlerConsumers) { 640 Set<Connection> wired = Collections.synchronizedSet(new HashSet<>()); 641 642 // Streaming fields and methods and then flat mapping them to all permutations with outgoing handler consumers. 643 // then filtering using matchOutgoingHandler, sorting by priority, wiring all matching and removing from ret. 644 processorAnnotatedElementRecords 645 .filter(aer -> !Modifier.isAbstract(((Member) aer.getAnnotatedElement()).getModifiers())) 646 .flatMap(aer -> outgoingHandlerConsumers.entrySet().stream().map(ihce -> new ConnectionMatch<Consumer<H>>( 647 aer, 648 ihce.getKey(), 649 ihce.getValue(), 650 ao -> ao.getAnnotation(OutgoingHandler.class).priority(), 651 ao -> ao.getAnnotation(OutgoingHandler.class).value()))) 652 .filter(mr -> matchOutgoingHandler(mr.annotatedElementRecord.getAnnotatedElement(), mr.connection)) 653 .sorted() 654 .forEach(mr -> { 655 AnnotatedElement handlerMember = mr.annotatedElementRecord.getAnnotatedElement(); 656 Connection outgoingConnection = mr.connection; 657 if (wired.add(outgoingConnection)) { 658 if (handlerMember instanceof Field) { 659 mr.value.accept((H) mr.annotatedElementRecord.get()); 660 } else { 661 Method handlerSupplierMethod = (Method) handlerMember; 662 Object outgoinggHandler = handlerSupplierMethod.getParameterCount() == 0 ? mr.annotatedElementRecord.get() : mr.annotatedElementRecord.invoke(outgoingConnection); 663 mr.value.accept((H) outgoinggHandler); 664 } 665 } 666 }); 667 return wired; 668 } 669 670 protected void wireOutgoingHandlerConsumers( 671 Stream<AnnotatedElementRecord> processorAnnotatedElementRecords, 672 Map<Connection, Consumer<H>> outgoingHandlerConsumers) { 673 674 processorAnnotatedElementRecords 675 .filter(aer -> aer.getAnnotation(OutgoingHandlerConsumers.class) != null) 676 .filter(aer -> aer.mustSet(Map.class, "Fields/methods annotated with OutgoingHandlersConsumers must have (parameter) type assignable from Map: " + aer.getAnnotatedElement())) 677 .forEach(aer -> aer.set(outgoingHandlerConsumers)); 678 } 679 680 /** 681 * Matches processor field or method and outgoing connection. 682 * @return 683 */ 684 protected boolean matchOutgoingEndpoint(AnnotatedElement endpointMember, Connection outgoingConnection) { 685 OutgoingEndpoint outgoingEndpointAnnotation = endpointMember.getAnnotation(OutgoingEndpoint.class); 686 if (outgoingEndpointAnnotation == null) { 687 return false; 688 } 689 690 if (endpointMember instanceof Method) { 691 Method endpointMethod = (Method) endpointMember; 692 int pc = endpointMethod.getParameterCount(); 693 if (pc == 0 || pc > 3) { 694 throw new NasdanikaException("A method annotated with OutgoingEndpoint shall have 1 - 3 parameters: " + endpointMethod); 695 } 696 if (pc > 1 && !endpointMethod.getParameterTypes()[0].isInstance(outgoingConnection)) { 697 return false; 698 } 699 } 700 701 return matchPredicate(outgoingConnection, outgoingEndpointAnnotation.value()); 702 } 703 704 protected Stream<EndpointWireRecord> wireOutgoingEndpoint( 705 Stream<AnnotatedElementRecord> processorAnnotatedElementRecords, 706 Map<Connection, CompletionStage<E>> outgoingEndpoints, 707 ProgressMonitor progressMonitor) { 708 709 Set<Field> wiredFields = Collections.synchronizedSet(new HashSet<>()); // For setting a field once, setter methods may be invoked multiple times. 710 Set<Connection> consumedConnections = Collections.synchronizedSet(new HashSet<>()); // For wiring a connection once. 711 712 // Streaming fields and methods and then flat mapping them to all permutations with outgoing endpoints. 713 // then filtering using matchOutgoingEndpoint, sorting by priority, wiring all matching and removing from ret. 714 return processorAnnotatedElementRecords 715 .filter(aer -> !Modifier.isAbstract(((Member) aer.getAnnotatedElement()).getModifiers())) 716 .flatMap(aer -> outgoingEndpoints.entrySet().stream().map(iee -> new ConnectionMatch<CompletionStage<E>>( 717 aer, 718 iee.getKey(), 719 iee.getValue(), 720 ao -> ao.getAnnotation(OutgoingEndpoint.class).priority(), 721 ao -> ao.getAnnotation(OutgoingEndpoint.class).value()))) 722 .filter(mr -> matchOutgoingEndpoint(mr.annotatedElementRecord.getAnnotatedElement(), mr.connection)) 723 .sorted() 724 .map(mr -> { 725 AnnotatedElement endpointMember = mr.annotatedElementRecord.getAnnotatedElement(); 726 Connection outgoingConnection = mr.connection; 727 OutgoingEndpoint outgoingEndpointAnnotation = endpointMember.getAnnotation(OutgoingEndpoint.class); 728 boolean consumed = outgoingEndpointAnnotation.consume() && !consumedConnections.add(outgoingConnection); 729 boolean fieldWired = endpointMember instanceof Field && !wiredFields.add((Field) endpointMember); 730 if (!consumed && !fieldWired) { // Setting a field once 731 CompletionStage<Void> result = mr.value.thenAccept(outgoingEndpoint -> { 732 if (endpointMember instanceof Field) { 733 mr.annotatedElementRecord.set(outgoingEndpoint); 734 } else { 735 Method endpointMethod = (Method) endpointMember; 736 switch (endpointMethod.getParameterCount()) { 737 case 1: 738 mr.annotatedElementRecord.invoke(outgoingEndpoint); 739 break; 740 case 2: 741 mr.annotatedElementRecord.invoke(outgoingConnection, outgoingEndpoint); 742 break; 743 case 3: 744 mr.annotatedElementRecord.invoke(outgoingConnection, outgoingEndpoint, progressMonitor); 745 break; 746 default: 747 throw new NasdanikaException("Outgoing endpoint method shall have 1 to 3 parameters: " + endpointMethod); 748 } 749 } 750 }); 751 return new EndpointWireRecord(outgoingConnection, result, outgoingEndpointAnnotation.consume()); 752 } 753 return null; 754 }) 755 .filter(Objects::nonNull); 756 } 757 758 protected void wireOutgoingEndpoints( 759 Stream<AnnotatedElementRecord> processorAnnotatedElementRecords, 760 Map<Connection, CompletionStage<E>> outgoingEndpoints) { 761 processorAnnotatedElementRecords 762 .filter(aer -> aer.getAnnotation(OutgoingEndpoints.class) != null) 763 .filter(aer -> aer.mustSet(Map.class, "Fields/methods annotated with OutgoingEndpoints must have (parameter) type assignable from Map: " + aer.getAnnotatedElement())) 764 .forEach(aer -> aer.set(outgoingEndpoints)); 765 } 766 767 // Connection wiring 768 @SuppressWarnings("unchecked") 769 protected void wireTargetHandler( 770 Stream<AnnotatedElementRecord> processorAnnotatedElementRecords, 771 ConnectionProcessorConfig<H,E> connectionProcessorConfig) { 772 Optional<AnnotatedElementRecord> getter = processorAnnotatedElementRecords 773 .filter(aer -> aer.getAnnotation(TargetHandler.class) != null) 774 .filter(aer -> aer.mustGet(null, "Cannot use " + aer.getAnnotatedElement() + " to get target connection handler")) 775 .findFirst(); 776 777 if (getter.isPresent()) { 778 connectionProcessorConfig.setTargetHandler((H) getter.get().get()); 779 } 780 } 781 782 protected Consumer<E> wireTargetEndpoint(Stream<AnnotatedElementRecord> processorAnnotatedElementRecords) { 783 784 Optional<AnnotatedElementRecord> setter = processorAnnotatedElementRecords 785 .filter(aer -> aer.getAnnotation(TargetEndpoint.class) != null) 786 .filter(aer -> aer.mustSet(null, "Cannot use " + aer.getAnnotatedElement() + " to set target connection endpoint")) 787 .findFirst(); 788 789 790 if (setter.isPresent()) { 791 return targetEndpoint -> setter.get().set(targetEndpoint); 792 } 793 return null; 794 } 795 796 @SuppressWarnings("unchecked") 797 protected void wireSourceHandler( 798 Stream<AnnotatedElementRecord> processorAnnotatedElementRecords, 799 ConnectionProcessorConfig<H,E> connectionProcessorConfig) { 800 Optional<AnnotatedElementRecord> getter = processorAnnotatedElementRecords 801 .filter(aer -> aer.getAnnotation(SourceHandler.class) != null) 802 .filter(aer -> aer.mustGet(null, "Cannot use " + aer.getAnnotatedElement() + " to get source connection handler")) 803 .findFirst(); 804 805 if (getter.isPresent()) { 806 connectionProcessorConfig.setSourceHandler((H) getter.get().get()); 807 } 808 } 809 810 protected Consumer<E> wireSourceEndpoint(Stream<AnnotatedElementRecord> processorAnnotatedElementRecords) { 811 812 Optional<AnnotatedElementRecord> setter = processorAnnotatedElementRecords 813 .filter(aer -> aer.getAnnotation(SourceEndpoint.class) != null) 814 .filter(aer -> aer.mustSet(null, "Cannot use " + aer.getAnnotatedElement() + " to set source connection endpoint")) 815 .findFirst(); 816 817 if (setter.isPresent()) { 818 return sourceEndpoint -> setter.get().set(sourceEndpoint); 819 } 820 return null; 821 } 822 823 protected boolean matchFactoryMethod(ProcessorConfig elementProcessorConfig, Method method) { 824 if (Modifier.isAbstract(method.getModifiers())) { 825 return false; 826 } 827 828 Processor elementProcessorAnnotation = method.getAnnotation(Processor.class); 829 if (elementProcessorAnnotation == null) { 830 return false; 831 } 832 833 Element element = elementProcessorConfig.getElement(); 834 if (!elementProcessorAnnotation.type().isInstance(element)) { 835 return false; 836 } 837 838 if (method.getParameterCount() != 4 || 839 !method.getParameterTypes()[1].isAssignableFrom(boolean.class) || 840 !method.getParameterTypes()[2].isAssignableFrom(BiConsumer.class) || 841 !method.getParameterTypes()[3].isAssignableFrom(ProgressMonitor.class)) { 842 throw new IllegalArgumentException("Factory method shall have 4 parameters compatible with: " 843 + "ProcessorConfig config, " 844 + "boolean parallel, " 845 + "BiConsumer<Element, BiConsumer<ProcessorInfo<P>,ProgressMonitor>> infoProvider, " 846 + "ProgressMonitor progressMonitor: " + method); 847 } 848 849 if (!method.getParameterTypes()[0].isInstance(elementProcessorConfig)) { 850 return false; 851 } 852 853 return matchPredicate(elementProcessorConfig.getElement(), elementProcessorAnnotation.value()); 854 } 855 856}