001
002package org.nasdanika.html.model.app.graph.emf;
003
004import java.lang.reflect.InvocationTargetException;
005import java.lang.reflect.Method;
006import java.util.ArrayList;
007import java.util.Collection;
008import java.util.Collections;
009import java.util.LinkedHashMap;
010import java.util.List;
011import java.util.Map;
012import java.util.Map.Entry;
013import java.util.Objects;
014import java.util.concurrent.atomic.AtomicInteger;
015import java.util.stream.Collectors;
016import java.util.stream.Stream;
017
018import org.apache.commons.text.StringEscapeUtils;
019import org.eclipse.emf.common.notify.Adapter;
020import org.eclipse.emf.common.util.URI;
021import org.eclipse.emf.ecore.EObject;
022import org.eclipse.emf.ecore.EOperation;
023import org.eclipse.emf.ecore.EReference;
024import org.nasdanika.common.CollectionCompoundConsumer;
025import org.nasdanika.common.Consumer;
026import org.nasdanika.common.Context;
027import org.nasdanika.common.ExecutionException;
028import org.nasdanika.common.Function;
029import org.nasdanika.common.MapCompoundSupplier;
030import org.nasdanika.common.MarkdownHelper;
031import org.nasdanika.common.ProgressMonitor;
032import org.nasdanika.common.Status;
033import org.nasdanika.common.Supplier;
034import org.nasdanika.common.Supplier.FunctionResult;
035import org.nasdanika.common.SupplierFactory;
036import org.nasdanika.common.Util;
037import org.nasdanika.exec.content.ContentFactory;
038import org.nasdanika.exec.content.Text;
039import org.nasdanika.graph.Connection;
040import org.nasdanika.graph.emf.EClassConnection;
041import org.nasdanika.graph.emf.EObjectNode;
042import org.nasdanika.graph.emf.EOperationConnection;
043import org.nasdanika.graph.emf.EReferenceConnection;
044import org.nasdanika.graph.processor.ChildProcessors;
045import org.nasdanika.graph.processor.IncomingEndpoint;
046import org.nasdanika.graph.processor.IncomingHandler;
047import org.nasdanika.graph.processor.NodeProcessorConfig;
048import org.nasdanika.graph.processor.OutgoingEndpoint;
049import org.nasdanika.graph.processor.OutgoingHandler;
050import org.nasdanika.graph.processor.ParentProcessor;
051import org.nasdanika.graph.processor.ProcessorElement;
052import org.nasdanika.graph.processor.ProcessorInfo;
053import org.nasdanika.html.Tag;
054import org.nasdanika.html.model.app.Action;
055import org.nasdanika.html.model.app.AppFactory;
056import org.nasdanika.html.model.app.Label;
057import org.nasdanika.html.model.app.Link;
058import org.nasdanika.html.model.app.gen.AppAdapterFactory;
059import org.nasdanika.html.model.app.graph.WidgetFactory;
060import org.nasdanika.html.model.bootstrap.BootstrapElement;
061import org.nasdanika.html.model.bootstrap.BootstrapFactory;
062import org.nasdanika.html.model.bootstrap.Modal;
063import org.nasdanika.html.model.html.HtmlFactory;
064import org.nasdanika.ncore.NamedElement;
065import org.nasdanika.ncore.util.SemanticInfo;
066
067/**
068 * Base class for node processors.
069 * Groups connections by reference, creates a consumer per reference (builder), chains the labels supplier with the consumers.
070 * @author Pavel
071 *
072 */
073public class EObjectNodeProcessor<T extends EObject> implements WidgetFactory {
074        
075        private static final String HELP_DECORATOR_ICON = "far fa-question-circle";
076
077        private static final String HELP_DECORATOR_STYLE = "vertical-align:super;font-size:x-small;margin-left:0.2em";
078
079        private static AtomicInteger counter = new AtomicInteger();
080        
081        private int id = counter.incrementAndGet();
082
083        /**
084         * Generated unique ID for grouping and comparing/ordering.
085         *  E.g. deciding which processor of two is responsible for combining opposite references (Ecore level and Nasdanika level)
086         *  or grouping all cross-references into one for graph generation.
087         * @return
088         */
089        public int getId() {
090                return id;
091        }
092
093        public static Selector<EObject> TARGET_SELECTOR = (widgetFactory, base, progressMonitor) -> {
094                return ((EObjectNodeProcessor<?>) widgetFactory).getTarget();
095        };              
096        
097        protected java.util.function.Function<ProgressMonitor, Action> prototypeProvider;
098        protected NodeProcessorConfig<WidgetFactory, WidgetFactory> config;
099        protected Context context;
100        protected URI uri;
101        
102        public EObjectNodeProcessor(
103                        NodeProcessorConfig<WidgetFactory, WidgetFactory> config, 
104                        Context context, 
105                        java.util.function.Function<ProgressMonitor, Action> prototypeProvider) {               
106                this.config = config;
107                this.context = context;
108                this.prototypeProvider = prototypeProvider;
109                this.uri = URI.createURI("index.html");
110        }
111        
112        protected Map<EObjectNode, ProcessorInfo<Object>> childProcessors;
113        
114        @ChildProcessors
115        public void setChildProcessors(Map<EObjectNode, ProcessorInfo<Object>> childProcessors) {
116                this.childProcessors = childProcessors;
117        }
118        
119        protected ConnectionProcessor parentProcessor;
120        
121        @ParentProcessor
122        public void setParentProcessor(ConnectionProcessor parentProcessor) {
123                this.parentProcessor = parentProcessor;
124        }
125
126        protected EObjectNode node;
127        
128        @ProcessorElement
129        public void setNode(EObjectNode node) {
130                this.node = node;
131        }
132        
133        @SuppressWarnings("unchecked")
134        public T getTarget() {
135                return (T) node.get();
136        }
137        
138        public NodeProcessorConfig<WidgetFactory, WidgetFactory> getConfig() {
139                return config;
140        }
141        
142        protected Map<EReferenceConnection, WidgetFactory> incomingReferenceEndpoints = new LinkedHashMap<>();
143        protected Map<EReferenceConnection, WidgetFactory> outgoingReferenceEndpoints = new LinkedHashMap<>();
144        
145        @IncomingEndpoint
146        public void setIncomingRefernceEndpoint(EReferenceConnection connection, WidgetFactory endpoint) {
147                incomingReferenceEndpoints.put(connection, endpoint);
148        }
149                
150        @OutgoingEndpoint
151        public void setOutgoingRefernceEndpoint(EReferenceConnection connection, WidgetFactory endpoint) {
152                outgoingReferenceEndpoints.put(connection, endpoint);
153        }
154        
155        protected Map<EOperationConnection, WidgetFactory> incomingOperationEndpoints = new LinkedHashMap<>();
156        protected Map<EOperationConnection, WidgetFactory> outgoingOperationEndpoints = new LinkedHashMap<>();
157        
158        @IncomingEndpoint
159        public void setIncominOperationgEndpoint(EOperationConnection connection, WidgetFactory endpoint) {
160                incomingOperationEndpoints.put(connection, endpoint);
161        }
162                
163        @OutgoingEndpoint
164        public void setOutgoingOperationEndpoint(EOperationConnection connection, WidgetFactory endpoint) {
165                outgoingOperationEndpoints.put(connection, endpoint);
166        }
167                
168        @IncomingHandler
169        public WidgetFactory getIncomingHandler(Connection connection) {                
170                return this;
171        }
172        
173        @OutgoingHandler
174        public WidgetFactory getOutgoingHandler(Connection connection) {
175                return this;
176        }
177
178        protected Collection<Label> createLabels(ProgressMonitor progressMonitor) {             
179                return Collections.singleton(createAction(progressMonitor));
180        }
181        
182        /**
183         * Creates action for the node.
184         * @param progressMonitor
185         * @return
186         */
187        protected Label createAction(ProgressMonitor progressMonitor) {
188                Action action = createAction(getTarget(), progressMonitor);             
189                if (action.getDecorator() == null && eClassWidgetFactory != null) {                     
190                        Label helpDecorator = eClassWidgetFactory.createHelpDecorator(progressMonitor);
191                        action.setDecorator(helpDecorator);
192                }
193                return action;
194        }
195
196        /**
197         * @return Supplier of labels with object's own information, without references.
198         * Reference-related information is added by reference consumers/builders.  
199         */
200        protected Supplier<Collection<Label>> doCreateLabelsSupplier() {
201                return new Supplier<Collection<Label>>() {
202
203                        @Override
204                        public double size() {
205                                return 1;
206                        }
207
208                        @Override
209                        public String name() {
210                                return "Labels supplier for " + getTarget();
211                        }
212
213                        @Override
214                        public Collection<Label> execute(ProgressMonitor progressMonitor) {
215                                return createLabels(progressMonitor);
216                        }
217                        
218                };              
219        }       
220        
221//      /**
222//       * Override to filter out references which should not contribute to label building.
223//       * @param eReference
224//       * @return
225//       */
226//      protected boolean isBuilderReference(EReference eReference) {
227//              return true; // TODO - from configuration and possibly annotations
228//      }
229        
230        /**
231         * Comparator for reference sorting
232         * @param a
233         * @param b
234         * @return
235         */
236        protected int compareIncomingReferences(EReference a, EReference b) {
237                return a.getName().compareTo(b.getName());
238        }
239        
240        /**
241         * Comparator for reference sorting
242         * @param a
243         * @param b
244         * @return
245         */
246        protected int compareOutgoingReferences(EReference a, EReference b) {
247                return a.getName().compareTo(b.getName());
248        }
249        
250        /**
251         * Comparator for operation binding sorting
252         * @return
253         */
254        protected int compareIncomingOperations(EOperation aOp, List<Object> aArgs, EOperation bOp, List<Object> bArgs) {
255                return aOp.getName().compareTo(bOp.getName());
256        }
257        
258        /**
259         * Comparator for operator binding sorting
260         * @return
261         */
262        protected int compareOutgoingOperations(EOperation aOp, List<Object> aArgs, EOperation bOp, List<Object> bArgs) {
263                return aOp.getName().compareTo(bOp.getName());
264        }       
265        
266        protected List<Consumer<Collection<Label>>> getReferenceLabelBuilders() {               
267                List<Consumer<Collection<Label>>> ret = new ArrayList<>();
268                
269                Map<EReference, List<Entry<EReferenceConnection, WidgetFactory>>> groupedOutgoingReferenceEndpoints = org.nasdanika.common.Util.groupBy(outgoingReferenceEndpoints.entrySet(), e -> e.getKey().getReference());
270                groupedOutgoingReferenceEndpoints
271                        .entrySet()
272                        .stream()
273                        .sorted((a, b) -> compareOutgoingReferences(a.getKey(), b.getKey()))
274                        .map(e -> createOutgoingReferenceLabelConsumer(e.getKey(), e.getValue()))
275                        .filter(Objects::nonNull)
276                        .forEach(ret::add);
277                
278                Map<EReference, List<Entry<EReferenceConnection, WidgetFactory>>> groupedIncomingReferenceEndpoints = org.nasdanika.common.Util.groupBy(incomingReferenceEndpoints.entrySet(), e -> e.getKey().getReference());
279                groupedIncomingReferenceEndpoints
280                        .entrySet()
281                        .stream()
282                        .sorted((a, b) -> compareIncomingReferences(a.getKey(), b.getKey()))
283                        .map(e -> createIncomingReferenceLabelConsumer(e.getKey(), e.getValue()))
284                        .filter(Objects::nonNull)
285                        .forEach(ret::add);
286                
287                return ret;
288        }
289        
290        protected List<Consumer<Collection<Label>>> getOperationLabelBuilders() {               
291                List<Consumer<Collection<Label>>> ret = new ArrayList<>();              
292                record Binding(EOperation operation, List<Object> arguments) {};
293
294                Map<Binding, List<Entry<EOperationConnection, WidgetFactory>>> groupedOutgoingOperationEndpoints = org.nasdanika.common.Util.groupBy(outgoingOperationEndpoints.entrySet(), e -> new Binding(e.getKey().getOperation(), e.getKey().getArguments()));
295                groupedOutgoingOperationEndpoints
296                        .entrySet()
297                        .stream()
298                        .sorted((a, b) -> compareOutgoingOperations(a.getKey().operation(), a.getKey().arguments(), b.getKey().operation(), b.getKey().arguments()))
299                        .map(e -> createOutgoingOperationLabelConsumer(e.getKey().operation(), e.getKey().arguments(), e.getValue()))
300                        .filter(Objects::nonNull)
301                        .forEach(ret::add);
302                
303                Map<Binding, List<Entry<EOperationConnection, WidgetFactory>>> groupedIncomingOperationEndpoints = org.nasdanika.common.Util.groupBy(incomingOperationEndpoints.entrySet(), e -> new Binding(e.getKey().getOperation(), e.getKey().getArguments()));
304                groupedIncomingOperationEndpoints
305                        .entrySet()
306                        .stream()
307                        .sorted((a, b) -> compareIncomingOperations(a.getKey().operation(), a.getKey().arguments(), b.getKey().operation(), b.getKey().arguments()))
308                        .map(e -> createIncomingOperationLabelConsumer(e.getKey().operation(), e.getKey().arguments(), e.getValue()))
309                        .filter(Objects::nonNull)
310                        .forEach(ret::add);
311                                
312                return ret;
313        }
314        
315        // --- Label building methods ---
316        
317        /**
318         * 
319         * @param eReference
320         * @return true if lables suppliers shall be called to create labels/actions. 
321         * This implementation returns false. 
322         */
323        protected boolean isCallIncomingReferenceLabelsSuppliers(EReference eReference) {
324                return false;
325        }
326        
327        /**
328         * 
329         * @param eReference
330         * @return true if lables suppliers shall be called to create labels/actions. 
331         * This implementation returns true for containment references, i.e. actions for child objects shall be created. 
332         */
333        protected boolean isCallOutgoingReferenceLabelsSuppliers(EReference eReference) {
334                return eReference != null && eReference.isContainment();
335        }
336                
337        /**
338         * 
339         * @param eOperation
340         * @return true if lables suppliers shall be called to create labels/actions. 
341         * This implementation returns false. 
342         */
343        protected boolean isCallIncomingOperationLabelsSuppliers(EOperation eOperation, List<Object> arguments) {
344                return false;
345        }
346        
347        /**
348         * 
349         * @param eOperation
350         * @return true if lables suppliers shall be called to create labels/actions. 
351         * This implementation returns false. 
352         */
353        protected boolean isCallOutgoingOperationLabelsSuppliers(EOperation eOperation, List<Object> arguments) {
354                return false;
355        }       
356                
357        /**
358         * 
359         * @return
360         */
361        protected Consumer<Collection<Label>> createIncomingReferenceLabelConsumer(
362                        EReference eReference, 
363                        List<Entry<EReferenceConnection, WidgetFactory>> referenceIncomingEndpoints) {
364                
365                @SuppressWarnings("resource")
366                MapCompoundSupplier<EReferenceConnection, Collection<Label>> endpointLabelsSupplier = new MapCompoundSupplier<>("Incoming reference endpoints supplier");
367                if (isCallIncomingReferenceLabelsSuppliers(eReference)) {
368                        for (Entry<EReferenceConnection, WidgetFactory> e: referenceIncomingEndpoints) {
369                                endpointLabelsSupplier.put(e.getKey(), e.getValue().createLabelsSupplier());
370                        }
371                }
372                
373                Consumer<Supplier.FunctionResult<Collection<Label>, Map<EReferenceConnection, Collection<Label>>>> referenceLabelBuilder = createIncomingReferenceLabelBuilder(eReference, referenceIncomingEndpoints);                                         
374                Function<Collection<Label>, FunctionResult<Collection<Label>, Map<EReferenceConnection, Collection<Label>>>> endpointLabelsFunction = endpointLabelsSupplier.asFunction();
375                return endpointLabelsFunction.then(referenceLabelBuilder);
376        }
377        
378        /**
379         * Builds target labels
380         * @param eReference
381         * @return
382         */
383        protected Consumer<Supplier.FunctionResult<Collection<Label>, Map<EReferenceConnection, Collection<Label>>>> createIncomingReferenceLabelBuilder(
384                        EReference eReference,
385                        List<Entry<EReferenceConnection, WidgetFactory>> referenceIncomingEndpoints) {
386                
387                return new Consumer<Supplier.FunctionResult<Collection<Label>,Map<EReferenceConnection,Collection<Label>>>>() {
388                        
389                        @Override
390                        public double size() {
391                                return 1;
392                        }
393                        
394                        @Override
395                        public String name() {
396                                return "Incoming reference label builder";
397                        }
398                        
399                        @Override
400                        public void execute(FunctionResult<Collection<Label>, Map<EReferenceConnection, Collection<Label>>> arg, ProgressMonitor progressMonitor) {
401                                buildIncomingReference(eReference, referenceIncomingEndpoints, arg.argument(), arg.result(), progressMonitor);
402                        }
403                };
404                
405        }
406        
407        /**
408         * Called by builder/consumer's execute();
409         * @param eReference
410         * @param referenceIncomingEndpoints
411         * @param labels
412         * @param incomingLabels
413         * @param progressMonitor
414         */
415        protected void buildIncomingReference(
416                        EReference eReference,
417                        List<Entry<EReferenceConnection, WidgetFactory>> referenceIncomingEndpoints,
418                        Collection<Label> labels,
419                        Map<EReferenceConnection, Collection<Label>> incomingLabels,
420                        ProgressMonitor progressMonitor) {
421
422                for (Method method: getClass().getMethods()) {
423                        IncomingReferenceBuilder irb = method.getAnnotation(IncomingReferenceBuilder.class);
424                        if (irb != null 
425                                        && eReference.getFeatureID() == irb.referenceID()
426                                        && eReference.getEContainingClass().getClassifierID() == irb.classID()
427                                        && eReference.getEContainingClass().getEPackage().getNsURI().equals(irb.nsURI())) {
428                                
429                                if (method.getParameterCount() != 5 ||
430                                                !method.getParameterTypes()[0].isInstance(eReference) ||
431                                                !method.getParameterTypes()[1].isInstance(referenceIncomingEndpoints) ||
432                                                !method.getParameterTypes()[2].isInstance(labels) ||
433                                                !method.getParameterTypes()[3].isInstance(incomingLabels) ||
434                                                !method.getParameterTypes()[4].isAssignableFrom(ProgressMonitor.class)) {
435                                        throw new IllegalArgumentException("Incoming reference builder method shall have 5 parameters compatible with: "
436                                                        + "EReference eReference, "
437                                                        + "List<Entry<EReferenceConnection, WidgetFactory>> referenceIncomingEndpoints, "
438                                                        + "Collection<Label> labels, "
439                                                        + "Map<EReferenceConnection, Collection<Label>> incomingLabels, "
440                                                        + "ProgressMonitor progressMonitor: " + method);
441                                }
442                                try {
443                                        method.invoke(this, eReference, referenceIncomingEndpoints, labels, incomingLabels, progressMonitor);
444                                } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
445                                        throw new ExecutionException(e);
446                                }                                       
447                        }
448                }
449        }
450        
451        /**
452         * 
453         * @return
454         */
455        protected Consumer<Collection<Label>> createIncomingOperationLabelConsumer(
456                        EOperation eOperation,
457                        List<Object> arguments,
458                        List<Entry<EOperationConnection, WidgetFactory>> operationIncomingEndpoints) {
459                
460                @SuppressWarnings("resource")
461                MapCompoundSupplier<EOperationConnection, Collection<Label>> endpointLabelsSupplier = new MapCompoundSupplier<>("Incoming operation endpoints supplier");
462                if (isCallIncomingOperationLabelsSuppliers(eOperation, arguments)) {
463                        for (Entry<EOperationConnection, WidgetFactory> e: operationIncomingEndpoints) {
464                                endpointLabelsSupplier.put(e.getKey(), e.getValue().createLabelsSupplier());
465                        }
466                }
467                
468                Consumer<Supplier.FunctionResult<Collection<Label>, Map<EOperationConnection, Collection<Label>>>> operationLabelBuilder = createIncomingOperationLabelBuilder(eOperation, arguments, operationIncomingEndpoints);                                              
469                Function<Collection<Label>, FunctionResult<Collection<Label>, Map<EOperationConnection, Collection<Label>>>> endpointLabelsFunction = endpointLabelsSupplier.asFunction();
470                return endpointLabelsFunction.then(operationLabelBuilder);
471        }
472        
473        /**
474         * Builds target labels
475         * @param eReference
476         * @return
477         */
478        protected Consumer<Supplier.FunctionResult<Collection<Label>, Map<EOperationConnection, Collection<Label>>>> createIncomingOperationLabelBuilder(
479                        EOperation eOperation,
480                        List<Object> arguments,
481                        List<Entry<EOperationConnection, WidgetFactory>> operationIncomingEndpoints) {
482                
483                return new Consumer<Supplier.FunctionResult<Collection<Label>,Map<EOperationConnection,Collection<Label>>>>() {
484                        
485                        @Override
486                        public double size() {
487                                return 1;
488                        }
489                        
490                        @Override
491                        public String name() {
492                                return "Incoming operation label builder";
493                        }
494                        
495                        @Override
496                        public void execute(FunctionResult<Collection<Label>, Map<EOperationConnection, Collection<Label>>> arg, ProgressMonitor progressMonitor) {
497                                buildIncomingOperation(eOperation, arguments, operationIncomingEndpoints, arg.argument(), arg.result(), progressMonitor);
498                        }
499                };
500                
501        }
502        
503        /**
504         * Called by builder/consumer's execute();
505         */
506        protected void buildIncomingOperation(
507                        EOperation eOperation,
508                        List<Object> arguments,
509                        List<Entry<EOperationConnection, WidgetFactory>> operationIncomingEndpoints,
510                        Collection<Label> labels,
511                        Map<EOperationConnection, Collection<Label>> incomingLabels,
512                        ProgressMonitor progressMonitor) {
513                
514                for (Method method: getClass().getMethods()) {
515                        IncomingOperationBuilder iob = method.getAnnotation(IncomingOperationBuilder.class);
516                        if (iob != null 
517                                        && eOperation.getOperationID() == iob.operationID()
518                                        && eOperation.getEContainingClass().getClassifierID() == iob.classID()
519                                        && eOperation.getEContainingClass().getEPackage().getNsURI().equals(iob.nsURI())) {
520
521                                if (method.getParameterCount() != 6 ||
522                                                !method.getParameterTypes()[0].isInstance(eOperation) ||
523                                                !method.getParameterTypes()[1].isInstance(arguments) ||
524                                                !method.getParameterTypes()[3].isInstance(operationIncomingEndpoints) ||
525                                                !method.getParameterTypes()[4].isInstance(labels) ||
526                                                !method.getParameterTypes()[5].isInstance(incomingLabels) ||
527                                                !method.getParameterTypes()[6].isAssignableFrom(ProgressMonitor.class)) {
528                                        throw new IllegalArgumentException("Incoming operation builder method shall have 6 parameters compatible with: "
529                                                        + "EOperation eOperation, "
530                                                        + "List<Object> arguments, "
531                                                        + "List<Entry<EOperationConnection, WidgetFactory>> operationIncomingEndpoints, "
532                                                        + "Collection<Label> labels, "
533                                                        + "Map<EOperationConnection, Collection<Label>> incomingLabels, "
534                                                        + "ProgressMonitor progressMonitor: " + method);
535                                }
536                                
537                                try {
538                                        method.invoke(this, eOperation, arguments, operationIncomingEndpoints, labels, incomingLabels, progressMonitor);
539                                } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
540                                        throw new ExecutionException(e);
541                                }                                       
542                        }
543                }
544        }
545        
546        /**
547         * 
548         * @return
549         */
550        protected Consumer<Collection<Label>> createOutgoingOperationLabelConsumer(
551                        EOperation eOperation,
552                        List<Object> arguments,
553                        List<Entry<EOperationConnection, WidgetFactory>> operationOutgoingEndpoints) {
554                
555                @SuppressWarnings("resource")
556                MapCompoundSupplier<EOperationConnection, Collection<Label>> endpointLabelsSupplier = new MapCompoundSupplier<>("Outgoing operation endpoints supplier");
557                if (isCallOutgoingOperationLabelsSuppliers(eOperation, arguments)) {
558                        for (Entry<EOperationConnection, WidgetFactory> e: operationOutgoingEndpoints) {
559                                endpointLabelsSupplier.put(e.getKey(), e.getValue().createLabelsSupplier());
560                        }
561                }
562                
563                Consumer<Supplier.FunctionResult<Collection<Label>, Map<EOperationConnection, Collection<Label>>>> operationLabelBuilder = createOutgoingOperationLabelBuilder(eOperation, arguments, operationOutgoingEndpoints);                                              
564                Function<Collection<Label>, FunctionResult<Collection<Label>, Map<EOperationConnection, Collection<Label>>>> endpointLabelsFunction = endpointLabelsSupplier.asFunction();
565                return endpointLabelsFunction.then(operationLabelBuilder);
566        }
567        
568        /**
569         * Builds target labels
570         * @param eReference
571         * @return
572         */
573        protected Consumer<Supplier.FunctionResult<Collection<Label>, Map<EOperationConnection, Collection<Label>>>> createOutgoingOperationLabelBuilder(
574                        EOperation eOperation,
575                        List<Object> arguments,
576                        List<Entry<EOperationConnection, WidgetFactory>> operationOutgoingEndpoints) {
577                
578                return new Consumer<Supplier.FunctionResult<Collection<Label>,Map<EOperationConnection,Collection<Label>>>>() {
579                        
580                        @Override
581                        public double size() {
582                                return 1;
583                        }
584                        
585                        @Override
586                        public String name() {
587                                return "Outgoing operation label builder";
588                        }
589                        
590                        @Override
591                        public void execute(FunctionResult<Collection<Label>, Map<EOperationConnection, Collection<Label>>> arg, ProgressMonitor progressMonitor) {
592                                buildIncomingOperation(eOperation, arguments, operationOutgoingEndpoints, arg.argument(), arg.result(), progressMonitor);
593                        }
594                };
595                
596        }
597        
598        /**
599         * Called by builder/consumer's execute();
600         */
601        protected void buildOutgoingOperation(
602                        EOperation eOperation,
603                        List<Object> arguments,
604                        List<Entry<EOperationConnection, WidgetFactory>> operationOutgoingEndpoints,
605                        Collection<Label> labels,
606                        Map<EOperationConnection, Collection<Label>> outgoingLabels,
607                        ProgressMonitor progressMonitor) {
608                
609                for (Method method: getClass().getMethods()) {
610                        OutgoingOperationBuilder oob = method.getAnnotation(OutgoingOperationBuilder.class);
611                        if (oob != null && eOperation.getOperationID() == oob.value()) {
612                                
613                                if (method.getParameterCount() != 6 ||
614                                                !method.getParameterTypes()[0].isInstance(eOperation) ||
615                                                !method.getParameterTypes()[1].isInstance(arguments) ||
616                                                !method.getParameterTypes()[3].isInstance(operationOutgoingEndpoints) ||
617                                                !method.getParameterTypes()[4].isInstance(labels) ||
618                                                !method.getParameterTypes()[5].isInstance(outgoingLabels) ||
619                                                !method.getParameterTypes()[6].isAssignableFrom(ProgressMonitor.class)) {
620                                        throw new IllegalArgumentException("Outgoing operation builder method shall have 6 parameters compatible with: "
621                                                        + "EOperation eOperation, "
622                                                        + "List<Object> arguments, "
623                                                        + "List<Entry<EOperationConnection, WidgetFactory>> operationOutgoingEndpoints, "
624                                                        + "Collection<Label> labels, "
625                                                        + "Map<EOperationConnection, Collection<Label>> outgoingLabels, "
626                                                        + "ProgressMonitor progressMonitor: " + method);
627                                }
628                                try {
629                                        method.invoke(this, eOperation, arguments, operationOutgoingEndpoints, labels, outgoingLabels, progressMonitor);
630                                } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
631                                        throw new ExecutionException(e);
632                                }                                       
633                        }
634                }
635        }
636                
637        /**
638         * 
639         * @return
640         */
641        protected Consumer<Collection<Label>> createOutgoingReferenceLabelConsumer(
642                        EReference eReference, 
643                        List<Entry<EReferenceConnection, WidgetFactory>> referenceOutgoingEndpoints) {
644                
645                @SuppressWarnings("resource")
646                MapCompoundSupplier<EReferenceConnection, Collection<Label>> endpointLabelsSupplier = new MapCompoundSupplier<>("Outgoing endpoints supplier");
647                if (isCallOutgoingReferenceLabelsSuppliers(eReference)) {
648                        for (Entry<EReferenceConnection, WidgetFactory> e: referenceOutgoingEndpoints) {
649                                endpointLabelsSupplier.put(e.getKey(), e.getValue().createLabelsSupplier());
650                        }
651                }
652                
653                Consumer<Supplier.FunctionResult<Collection<Label>, Map<EReferenceConnection, Collection<Label>>>> referenceLabelBuilder = createOutgoingReferenceLabelBuilder(eReference, referenceOutgoingEndpoints);                                         
654                Function<Collection<Label>, FunctionResult<Collection<Label>, Map<EReferenceConnection, Collection<Label>>>> endpointLabelsFunction = endpointLabelsSupplier.asFunction();
655                return endpointLabelsFunction.then(referenceLabelBuilder);
656        }
657        
658        /**
659         * Builds target labels
660         * @param eReference
661         * @return
662         */
663        protected Consumer<Supplier.FunctionResult<Collection<Label>, Map<EReferenceConnection, Collection<Label>>>> createOutgoingReferenceLabelBuilder(
664                        EReference eReference,
665                        List<Entry<EReferenceConnection, WidgetFactory>> referenceOutgoingEndpoints) {
666                
667                
668                return new Consumer<Supplier.FunctionResult<Collection<Label>,Map<EReferenceConnection,Collection<Label>>>>() {
669                        
670                        @Override
671                        public double size() {
672                                return 1;
673                        }
674                        
675                        @Override
676                        public String name() {
677                                return "Outgoing reference label builder";
678                        }
679                        
680                        @Override
681                        public void execute(
682                                        FunctionResult<Collection<Label>, Map<EReferenceConnection, Collection<Label>>> arg, 
683                                        ProgressMonitor progressMonitor) {
684                                
685                                buildOutgoingReference(
686                                                eReference, 
687                                                referenceOutgoingEndpoints, 
688                                                arg.argument(), 
689                                                arg.result(), 
690                                                progressMonitor);
691                        }
692                };
693                
694        }
695        
696        /**
697         * Called by builder/consumer's execute();
698         * @param eReference
699         * @param referenceOutgoingEndpoints
700         * @param labels
701         * @param incomingLabels
702         * @param progressMonitor
703         */
704        protected void buildOutgoingReference(
705                        EReference eReference,
706                        List<Entry<EReferenceConnection, WidgetFactory>> referenceOutgoingEndpoints,
707                        Collection<Label> labels,
708                        Map<EReferenceConnection, Collection<Label>> outgoingLabels,
709                        ProgressMonitor progressMonitor) {
710                
711                for (Method method: getClass().getMethods()) {
712                        OutgoingReferenceBuilder orb = method.getAnnotation(OutgoingReferenceBuilder.class);
713                        if (orb != null && eReference.getFeatureID() == orb.value()) {
714                                if (method.getParameterCount() != 5 ||
715                                                !method.getParameterTypes()[0].isInstance(eReference) ||
716                                                !method.getParameterTypes()[1].isInstance(referenceOutgoingEndpoints) ||
717                                                !method.getParameterTypes()[2].isInstance(labels) ||
718                                                !method.getParameterTypes()[3].isInstance(outgoingLabels) ||
719                                                !method.getParameterTypes()[4].isAssignableFrom(ProgressMonitor.class)) {
720                                        throw new IllegalArgumentException("Outgoing reference builder method shall have 5 parameters compatible with: "
721                                                        + "EReference eReference, "
722                                                        + "List<Entry<EReferenceConnection, WidgetFactory>> referenceOutgoingEndpoints, "
723                                                        + "Collection<Label> labels, "
724                                                        + "Map<EReferenceConnection, Collection<Label>> outgoingLabels, "
725                                                        + "ProgressMonitor progressMonitor: " + method);
726                                }
727                                try {
728                                        method.invoke(this, eReference, referenceOutgoingEndpoints, labels, outgoingLabels, progressMonitor);
729                                } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
730                                        throw new ExecutionException(e);
731                                }                                       
732                        }
733                }
734                
735                addReferenceChildren(eReference, labels, outgoingLabels, progressMonitor);              
736        }
737
738        protected void addReferenceChildren(EReference eReference, Collection<Label> labels,
739                        Map<EReferenceConnection, Collection<Label>> outgoingLabels, ProgressMonitor progressMonitor) {
740                for (Label tLabel: labels) {
741                        Label refLabel = createLabel(eReference, progressMonitor);
742                        for (Entry<EReferenceConnection, Collection<Label>> re: outgoingLabels.entrySet()) {
743                                refLabel.getChildren().addAll(re.getValue());
744                        }
745                        if (!refLabel.getChildren().isEmpty()) {
746                                tLabel.getChildren().add(refLabel);
747                        }
748                }
749        }
750        
751        private WidgetFactory eClassWidgetFactory;
752        
753        @OutgoingEndpoint
754        public final void setEClassEndpoint(EClassConnection connection, WidgetFactory eClassWidgetFactory) {
755                this.eClassWidgetFactory = eClassWidgetFactory;
756        }       
757        
758        
759        /**
760         * Creates and configures an action for eObject. 
761         * Override to create from prototypes.
762         * @param eObject
763         * @return
764         */
765        protected Action createAction(EObject eObject, ProgressMonitor progressMonitor) {
766                Action action = newAction(eObject, progressMonitor);
767                configureLabel(eObject, action, progressMonitor);               
768                return action;
769        }
770
771        /**
772         * Creates a new action using a factory. 
773         * Override to create from prototypes.
774         * @return
775         */
776        protected Action newAction(EObject eObject, ProgressMonitor progressMonitor) {
777                if (prototypeProvider != null) {
778                        Action prototype = prototypeProvider.apply(progressMonitor);
779                        if (prototype != null) {
780                                return prototype;
781                        }                               
782                }
783                return AppFactory.eINSTANCE.createAction();
784        }
785        
786        /**
787         * Creates and configures a link for eObject. 
788         * Override to create from prototypes.
789         * @param eObject
790         * @return
791         */
792        protected Link createLink(EObject eObject, String path, ProgressMonitor progressMonitor) {
793                Link link = AppFactory.eINSTANCE.createLink();
794                configureLabel(eObject, link, progressMonitor);
795                if (Util.isBlank(path)) {
796                        link.setLocation(uri.toString());
797                } else {
798                        link.setLocation(URI.createURI(path).resolve(uri).toString());
799                }
800                return link;
801        }
802        
803        /**
804         * Creates and configures a label for eObject. 
805         * Override to create from prototypes.
806         * @param eObject
807         * @return
808         */
809        protected Label createLabel(EObject eObject, ProgressMonitor progressMonitor) {
810                Label label = AppFactory.eINSTANCE.createLabel();
811                configureLabel(eObject, label, progressMonitor);
812                return label;           
813        }
814        
815        /**
816         * Configures label.
817         * @param eObject
818         * @param label
819         */
820        protected void configureLabel(EObject eObject, Label label, ProgressMonitor progressMonitor) {
821                if (eObject instanceof NamedElement && Util.isBlank(label.getText())) {
822                        label.setText(StringEscapeUtils.escapeHtml4(((NamedElement) eObject).getName()));
823                }
824                if (label instanceof Link && uri != null) {
825                        ((Link) label).setLocation(uri.toString());
826                }
827
828                new SemanticInfo(eObject).annotate(label);
829        }
830        
831        protected String render(Object object, ProgressMonitor progressMonitor) {
832                if (object instanceof EObject) {
833                        Adapter adapter = AppAdapterFactory.INSTANCE.adapt((EObject) object, SupplierFactory.Provider.class);
834                        if (adapter instanceof SupplierFactory.Provider) {
835                                SupplierFactory<Tag> supplierFactory = ((SupplierFactory.Provider) adapter).getFactory(Tag.class);
836                                Supplier<Tag> supplier = supplierFactory.create(context);
837                                Tag tag = supplier.call(progressMonitor, null, Status.FAIL, Status.ERROR);
838                                return tag.toString();
839                        }
840                        // Adapt to just supplier factory here and see what comes out - string, stream, ...?                    
841                } else if (object instanceof Iterable) {
842                        StringBuilder ret = new StringBuilder();
843                        ((Iterable<?>) object).forEach(e -> ret.append(render(e, progressMonitor)));
844                        return ret.toString();
845                }
846                if (object instanceof Stream) {
847                        return ((Stream<?>) object).map(e -> render(e, progressMonitor)).collect(Collectors.joining());
848                }
849
850                return object == null ? "" : object.toString();
851        }
852        
853        // --- WidgetFactory methods ---
854        
855        @Override
856        public void resolve(URI base, ProgressMonitor progressMonitor) {        
857                uri = uri.resolve(base);
858                for (WidgetFactory oe: outgoingReferenceEndpoints.values()) {
859                        oe.resolve(uri, progressMonitor);
860                }
861                for (WidgetFactory ie: incomingReferenceEndpoints.values()) {
862                        ie.resolve(uri, progressMonitor);
863                }
864        }
865        
866        @Override
867        public String createWidgetString(Object selector, URI base, ProgressMonitor progressMonitor) {
868                return render(createWidget(selector, base, progressMonitor), progressMonitor);
869        }
870        
871        @Override
872        public String createLinkString(URI base, ProgressMonitor progressMonitor) {
873                return render(createLink(base, progressMonitor), progressMonitor);
874        }
875        
876        @Override
877        public Object createLink(URI base, ProgressMonitor progressMonitor) {
878                Link link = createLink(getTarget(), null, progressMonitor);
879                link.rebase(null, base);
880                return link;
881        }
882        
883        /**
884         * Override to configure the modal. E.g. center, change size, make scrollable, etc.
885         * @param helpModal
886         */
887        protected void configureHelpModal(Modal helpModal) {
888//              helpModal.setSize("large");
889//              helpModal.setCentered(true);            
890        }
891        
892        /**
893         * Convenience method for creating help decorators. 
894         * Returns null if the tooltip, location and contents are blank/empty.
895         * If the contents is empty returns a label or a link with a help icon and a tooltip.
896         * Otherwise returns a link with a tooltip and a modal. The modal header links to the location if it is not empty.
897         * @param tooltip Tooltip to show
898         * @param location If not blank
899         * @param title Modal header text
900         * @param icon
901         * @param contents
902         * @param modalConfigurator
903         * @return
904         */
905        public static Label createHelpDecorator(
906                        String tooltip,
907                        String location,
908                        String title,
909                        String icon,
910                        Collection<EObject> contents,
911                        java.util.function.Consumer<Modal> modalConfigurator) {
912                
913                if (contents == null || contents.isEmpty()) {
914                        if (Util.isBlank(location)) {
915                                if (Util.isBlank(tooltip)) {
916                                        return null;
917                                }
918                                Label helpDecorator = AppFactory.eINSTANCE.createLabel();                               
919                                helpDecorator.setIcon(HELP_DECORATOR_ICON);
920                                helpDecorator.getAttributes().put("style", createText(HELP_DECORATOR_STYLE));                           
921                                helpDecorator.setTooltip(tooltip);
922                                return helpDecorator;                           
923                        }
924                }
925                
926                Link helpDecorator = AppFactory.eINSTANCE.createLink();
927                helpDecorator.setIcon(HELP_DECORATOR_ICON);
928                helpDecorator.getAttributes().put("style", createText(HELP_DECORATOR_STYLE));                           
929                helpDecorator.setTooltip(tooltip);
930                if (contents == null || contents.isEmpty()) {
931                        helpDecorator.setLocation(location);
932                } else {
933                        Modal helpModal = BootstrapFactory.eINSTANCE.createModal();
934                
935                        if (!Util.isBlank(title)) {
936                                BootstrapElement header = BootstrapFactory.eINSTANCE.createBootstrapElement();
937                                org.nasdanika.html.model.html.Tag h2 = HtmlFactory.eINSTANCE.createTag();
938                                h2.setName("H2");
939                                header.getContent().add(h2);
940                                if (Util.isBlank(location)) {                                   
941                                        // Label
942                                        Label tLabel = AppFactory.eINSTANCE.createLabel();
943                                        tLabel.setText(title);
944                                        tLabel.setIcon(icon);
945                                        h2.getContent().add(tLabel);                                    
946                                } else {
947                                        // Link
948                                        Link typeLink = AppFactory.eINSTANCE.createLink();
949                                        typeLink.setText(title);
950                                        typeLink.setIcon(icon);
951                                        typeLink.setLocation(location);
952                                        h2.getContent().add(typeLink);
953                                }
954                                helpModal.setHeader(header);
955                        }
956                        
957                        BootstrapElement body = BootstrapFactory.eINSTANCE.createBootstrapElement();
958                        body.getContent().addAll(contents);             
959                        helpModal.setBody(body);
960                        if (modalConfigurator != null) {
961                                modalConfigurator.accept(helpModal);
962                        }
963                        helpDecorator.setModal(helpModal);
964                }
965                
966                return helpDecorator;
967        }
968        
969        public static Label createHelpDecorator(
970                        String tooltip,
971                        String location,
972                        String title,
973                        String icon,
974                        String contents,
975                        java.util.function.Consumer<Modal> modalConfigurator) {
976                return createHelpDecorator(
977                                tooltip, 
978                                location, 
979                                title, 
980                                icon, 
981                                Util.isBlank(contents) ? Collections.emptyList() : Collections.singleton(createText(contents)), 
982                                modalConfigurator);
983        }
984                
985        public Label createMarkdownHelpDecorator(
986                        String tooltip,
987                        String location,
988                        String title,
989                        String icon,
990                        String markdown,
991                        java.util.function.Consumer<Modal> modalConfigurator) {
992                return createHelpDecorator(
993                                tooltip, 
994                                location, 
995                                title, 
996                                icon, 
997                                context.get(MarkdownHelper.class, MarkdownHelper.INSTANCE).markdownToHtml(markdown),  
998                                modalConfigurator);
999        }
1000        
1001        protected Collection<EObject> createHelpContents(URI base, ProgressMonitor progressMonitor) {
1002                return Collections.emptyList();
1003        }
1004        
1005        @Override
1006        public Label createHelpDecorator(URI base, ProgressMonitor progressMonitor) {
1007                Link link = createLink(getTarget(), null, progressMonitor);
1008                link.rebase(null, base);
1009                
1010                Collection<EObject> helpContents =  createHelpContents(base, progressMonitor);
1011                
1012                return createHelpDecorator(
1013                                link.getTooltip(),
1014                                link.getLocation(),
1015                                link.getText(),
1016                                link.getIcon(), 
1017                                helpContents, 
1018                                this::configureHelpModal);
1019        }       
1020                
1021        @Override
1022        public Supplier<Collection<Label>> createLabelsSupplier() {
1023                List<Consumer<Collection<Label>>> referenceLabelBuilders = getReferenceLabelBuilders();
1024                List<Consumer<Collection<Label>>> operationLabelBuilders = getOperationLabelBuilders();
1025                if (referenceLabelBuilders == null && operationLabelBuilders == null || referenceLabelBuilders.isEmpty() && operationLabelBuilders.isEmpty()) {
1026                        return doCreateLabelsSupplier();
1027                }
1028                @SuppressWarnings("resource")
1029                CollectionCompoundConsumer<Collection<Label>> collectionCompoundConsumer = new CollectionCompoundConsumer<Collection<Label>>("Reference Label Builder");
1030                referenceLabelBuilders.forEach(collectionCompoundConsumer::add);
1031                operationLabelBuilders.forEach(collectionCompoundConsumer::add);
1032                return doCreateLabelsSupplier().then(collectionCompoundConsumer.asFunction());
1033        }
1034        
1035        @Override
1036        public String createLabelString(ProgressMonitor progressMonitor) {
1037                return render(createLabel(progressMonitor), progressMonitor);
1038        }
1039        
1040        @Override
1041        public Object createLabel(ProgressMonitor progressMonitor) {
1042                return createLabel(getTarget(), progressMonitor);
1043        }
1044        
1045        // --- Convenience methods --
1046        /**
1047         * Adds textual content.
1048         * @param content
1049         */
1050        protected static void addContent(Action action, String content) {
1051                Text text = ContentFactory.eINSTANCE.createText();
1052                text.setContent(content);
1053                action.getContent().add(text);
1054        }
1055        
1056        /**
1057         * Convenience method to create Text and set content in one shot.
1058         * @param content
1059         * @return
1060         */
1061        public static Text createText(String content) {
1062                Text text = ContentFactory.eINSTANCE.createText();
1063                text.setContent(content);
1064                return text;
1065        }       
1066                
1067}