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