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