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