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}