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}