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