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