001package org.nasdanika.html.ecore; 002 003import java.util.ArrayList; 004import java.util.Collection; 005import java.util.Collections; 006import java.util.Comparator; 007import java.util.HashSet; 008import java.util.List; 009import java.util.Map; 010import java.util.Set; 011import java.util.function.BiFunction; 012import java.util.function.BooleanSupplier; 013import java.util.function.Function; 014import java.util.function.Predicate; 015import java.util.function.Supplier; 016import java.util.stream.Collectors; 017 018import org.eclipse.emf.common.util.EList; 019import org.eclipse.emf.common.util.TreeIterator; 020import org.eclipse.emf.common.util.URI; 021import org.eclipse.emf.ecore.EAttribute; 022import org.eclipse.emf.ecore.EClass; 023import org.eclipse.emf.ecore.EClassifier; 024import org.eclipse.emf.ecore.EGenericType; 025import org.eclipse.emf.ecore.EModelElement; 026import org.eclipse.emf.ecore.ENamedElement; 027import org.eclipse.emf.ecore.EOperation; 028import org.eclipse.emf.ecore.EPackage; 029import org.eclipse.emf.ecore.EReference; 030import org.eclipse.emf.ecore.EStructuralFeature; 031import org.eclipse.emf.ecore.resource.Resource; 032import org.eclipse.emf.ecore.resource.ResourceSet; 033import org.nasdanika.common.Context; 034import org.nasdanika.common.DiagramGenerator; 035import org.nasdanika.common.MarkdownHelper; 036import org.nasdanika.common.ProgressMonitor; 037import org.nasdanika.common.Util; 038import org.nasdanika.emf.DiagramTextGenerator; 039import org.nasdanika.emf.DiagramTextGenerator.RelationshipDirection; 040import org.nasdanika.emf.EmfUtil; 041import org.nasdanika.emf.EmfUtil.EModelElementDocumentation; 042import org.nasdanika.emf.MermaidTextGenerator; 043import org.nasdanika.emf.PlantUmlTextGenerator; 044import org.nasdanika.emf.persistence.EObjectLoader; 045import org.nasdanika.html.Fragment; 046import org.nasdanika.html.HTMLFactory; 047import org.nasdanika.html.Tag; 048import org.nasdanika.html.TagName; 049import org.nasdanika.html.bootstrap.BootstrapFactory; 050import org.nasdanika.html.bootstrap.Table; 051import org.nasdanika.html.model.app.Action; 052import org.nasdanika.html.model.app.AppFactory; 053import org.nasdanika.html.model.app.SectionStyle; 054import org.nasdanika.html.model.app.gen.DynamicTableBuilder; 055import org.nasdanika.ncore.util.NcoreUtil; 056 057public class EClassActionSupplier extends EClassifierActionSupplier<EClass> { 058 059 private BooleanSupplier isGenerateLoadSpecification; 060 private Supplier<String> diagramDialectSupplier; 061 062 public EClassActionSupplier( 063 EClass value, 064 Context context, 065 java.util.function.Function<EPackage,String> ePackagePathComputer, 066 java.util.function.Function<String, String> javadocResolver, 067 java.util.function.Function<String, Object> ePackageResolver, 068 Predicate<EModelElement> elementPredicate, 069 BiFunction<ENamedElement, String, String> labelProvider, 070 BooleanSupplier isGenerateLoadSpecification, 071 Supplier<String> diagramDialectSupplier) { 072 super(value, context, ePackagePathComputer, javadocResolver, ePackageResolver, elementPredicate, labelProvider); 073 this.elementPredicate = elementPredicate; 074 this.isGenerateLoadSpecification = isGenerateLoadSpecification; 075 this.diagramDialectSupplier = diagramDialectSupplier; 076 } 077 078 @Override 079 public org.nasdanika.html.model.app.Action execute(EClass contextEClass, ProgressMonitor progressMonitor) { 080 Action action = super.execute(contextEClass, progressMonitor); 081 082 action.setSectionStyle(SectionStyle.HEADER); 083 084 // Diagram 085 String diagramMode = NcoreUtil.getNasdanikaAnnotationDetail(eObject, "diagram", "navigation"); 086 String diagram = generateDiagram(1, RelationshipDirection.both, true, true, progressMonitor); 087 if (!Util.isBlank(diagram)) { 088 switch (diagramMode) { 089 case "content": 090 addContent(action, diagram); 091 break; 092 case "none": 093 break; 094 case "navigation": { 095 Action diagramAction = AppFactory.eINSTANCE.createAction(); 096 action.getNavigation().add(diagramAction); 097 diagramAction.setText("Diagram"); 098 diagramAction.setIcon("fas fa-project-diagram"); 099 diagramAction.setLocation(eObject.getName() + "-diagram.html"); 100 addContent(diagramAction, diagram); 101 break; 102 } 103 case "anonymous": { 104 Action diagramAction = AppFactory.eINSTANCE.createAction(); 105 action.getAnonymous().add(diagramAction); 106 diagramAction.setText("Diagram"); 107 diagramAction.setIcon("fas fa-project-diagram"); 108 diagramAction.setLocation(eObject.getName() + "-diagram.html"); 109 addContent(diagramAction, diagram); 110 break; 111 } 112 default: 113 throw new IllegalArgumentException("Unsupported diagram annotation value '" + diagramMode +"' on EClass " + eObject); 114 } 115 } 116 117 // Generic supertypes 118 List<EGenericType> eGenericSuperTypes = eObject.getEGenericSuperTypes().stream().filter(gst -> elementPredicate.test(gst.getEClassifier())).collect(Collectors.toList()); 119 if (!eGenericSuperTypes.isEmpty()) { 120 HTMLFactory htmlFactory = context.get(HTMLFactory.class); 121 Fragment gstf = htmlFactory.fragment(TagName.a.create(TagName.h3.create("Supertypes")).attribute("name", "supertypes")); 122 123 Tag list = TagName.ul.create(); 124 gstf.content(list); 125 126 for (EGenericType superType: eGenericSuperTypes) { 127 Tag listItem = TagName.li.create(); 128 list.content(listItem); 129 genericType(superType, eObject, listItem.getContent()::add, progressMonitor); 130 } 131 addContent(action, gstf.toString()); 132 } 133 134 // Subtypes 135 Collection<EClass> eSubTypes = getSubTypes(eObject).stream().filter(elementPredicate).sorted(eNamedElementComparator).collect(Collectors.toList()); 136 if (!eSubTypes.isEmpty()) { 137 HTMLFactory htmlFactory = context.get(HTMLFactory.class); 138 Fragment gstf = htmlFactory.fragment(TagName.a.create(TagName.h3.create("Subtypes")).attribute("name", "subtypes")); 139 140 Tag list = TagName.ul.create(); 141 gstf.content(list); 142 143 for (EClass subType: eSubTypes) { 144 list.content(TagName.li.create(link(subType, eObject))); 145 } 146 addContent(action, gstf.toString()); 147 } 148 149 // Referrers 150 Collection<EClass> referrers = getReferrers().stream().filter(elementPredicate).sorted(eNamedElementComparator).collect(Collectors.toList()); 151 if (!referrers.isEmpty()) { 152 HTMLFactory htmlFactory = context.get(HTMLFactory.class); 153 Fragment gstf = htmlFactory.fragment(TagName.a.create(TagName.h3.create("Referrers")).attribute("name", "referrers")); 154 155 Tag list = TagName.ul.create(); 156 gstf.content(list); 157 158 for (EClass referrer: referrers) { 159 list.content(TagName.li.create(link(referrer, eObject))); 160 } 161 addContent(action, gstf.toString()); 162 } 163 164 // Uses 165 Collection<EClass> uses = getUses().stream().filter(elementPredicate).sorted(eNamedElementComparator).collect(Collectors.toList()); 166 if (!uses.isEmpty()) { 167 HTMLFactory htmlFactory = context.get(HTMLFactory.class); 168 Fragment gstf = htmlFactory.fragment(TagName.a.create(TagName.h3.create("Uses")).attribute("name", "uses")); 169 170 Tag list = TagName.ul.create(); 171 gstf.content(list); 172 173 for (EClass use: uses) { 174 list.content(TagName.li.create(link(use, eObject))); 175 } 176 addContent(action, gstf.toString()); 177 } 178 179 List<EAttribute> allAttributes = eObject.getEAllAttributes().stream().filter(elementPredicate).sorted(eNamedElementComparator).collect(Collectors.toList()); 180 List<EReference> allReferences = eObject.getEAllReferences().stream().filter(elementPredicate).sorted(eNamedElementComparator).collect(Collectors.toList()); 181 List<EOperation> allOperations = eObject.getEAllOperations().stream().filter(elementPredicate).sorted(eNamedElementComparator).collect(Collectors.toList()); 182 List<EGenericType> allGenericSupertypes = eObject.getEAllGenericSuperTypes().stream().filter(gst -> elementPredicate.test(gst.getEClassifier())).collect(Collectors.toList()); 183 184 if (allAttributes.size() + allReferences.size() + allOperations.size() + allGenericSupertypes.size() != 0) { 185 Action allGroup = AppFactory.eINSTANCE.createAction(); 186 allGroup.setText("All"); 187 allGroup.setUuid(action.getUuid() + "-all"); 188 action.getNavigation().add(allGroup); 189 190 generateAllAttributes(allAttributes, allGroup, progressMonitor); 191 generateAllReferences(allReferences, allGroup, progressMonitor); 192 generateAllOperations(allOperations, allGroup, progressMonitor); 193 generateAllGenericSupertypes(allGenericSupertypes, allGroup, progressMonitor); 194 } 195 196 // No load specification for EMap entries. 197 if (isGenerateLoadSpecification.getAsBoolean() && Map.Entry.class != instanceClass) { 198 generateLoadSpecification(action, eNamedElementComparator, progressMonitor); 199 } 200 201 List<EAttribute> sortedAttributes = eObject.getEAttributes().stream().filter(elementPredicate).sorted(eNamedElementComparator).collect(Collectors.toList()); 202 203 EList<Action> sections = action.getSections(); 204 if (!sortedAttributes.isEmpty()) { 205 Action attributeSummaryCategory = AppFactory.eINSTANCE.createAction(); 206 attributeSummaryCategory.setText("Attribute summary"); 207 attributeSummaryCategory.setName("attribute-summary"); 208 attributeSummaryCategory.setSectionStyle(SectionStyle.HEADER); 209 sections.add(attributeSummaryCategory); 210 attributeSummaryCategory.getContent().add(buildDynamicAttributesTable(sortedAttributes, progressMonitor)); 211 } 212 213 List<EReference> sortedReferences = eObject.getEReferences().stream().filter(elementPredicate).sorted(eNamedElementComparator).collect(Collectors.toList()); 214 if (!sortedReferences.isEmpty()) { 215 Action referenceSummaryCategory = AppFactory.eINSTANCE.createAction(); 216 referenceSummaryCategory.setText("Reference summary"); 217 referenceSummaryCategory.setName("reference-summary"); 218 referenceSummaryCategory.setSectionStyle(SectionStyle.HEADER); 219 sections.add(referenceSummaryCategory); 220 referenceSummaryCategory.getContent().add(buildDynamicReferencesTable(sortedReferences, progressMonitor)); 221 } 222 223 if (!sortedAttributes.isEmpty()) { 224 Action attributeDetailsCategory = AppFactory.eINSTANCE.createAction(); 225 attributeDetailsCategory.setText("Attribute details"); 226 attributeDetailsCategory.setName("attribute-details"); 227 attributeDetailsCategory.setSectionStyle(SectionStyle.HEADER); 228 sections.add(attributeDetailsCategory); 229 EList<Action> attributes = attributeDetailsCategory.getSections(); 230 231 for (EStructuralFeature sf: sortedAttributes) { 232 attributes.add(adaptChild(sf).execute(null, progressMonitor)); 233 } 234 } 235 236 if (!sortedReferences.isEmpty()) { 237 Action referenceDetailsCategory = AppFactory.eINSTANCE.createAction(); 238 referenceDetailsCategory.setText("Reference details"); 239 referenceDetailsCategory.setName("reference-details"); 240 referenceDetailsCategory.setSectionStyle(SectionStyle.HEADER); 241 sections.add(referenceDetailsCategory); 242 EList<Action> references = referenceDetailsCategory.getSections(); 243 for (EStructuralFeature sf: sortedReferences) { 244 references.add(adaptChild(sf).execute(null, progressMonitor)); 245 } 246 } 247 248 List<EOperation> sortedOperations = eObject.getEOperations().stream().filter(elementPredicate).sorted(eNamedElementComparator).collect(Collectors.toList()); 249 if (!sortedOperations.isEmpty()) { 250 Action operationsCategory = AppFactory.eINSTANCE.createAction(); 251 operationsCategory.setText("Operations"); 252 operationsCategory.setName("operations"); 253 operationsCategory.setSectionStyle(SectionStyle.HEADER); 254 sections.add(operationsCategory); 255 EList<Action> operations = operationsCategory.getSections(); 256 for (EOperation eOp: sortedOperations) { 257 operations.add(adaptChild(eOp).execute(null, progressMonitor)); 258 } 259 } 260 261 if (eObject.isInterface()) { 262 action.setIcon(ICONS_BASE + "EInterface.gif"); 263 } 264 265 return action; 266 } 267 268 private void generateAllGenericSupertypes(List<EGenericType> allGenericSupertypes, Action allGroup, ProgressMonitor progressMonitor) { 269 if (!allGenericSupertypes.isEmpty()) { 270 String inheritanceDiagram = generateInheritanceDiagram(0, RelationshipDirection.both, true, true, progressMonitor); 271 if (!Util.isBlank(inheritanceDiagram)) { 272 Action allSupertypesAction = AppFactory.eINSTANCE.createAction(); 273 allSupertypesAction.setText("Supertypes"); 274 allSupertypesAction.setLocation(eObject.getName() + "-all-supertypes.html"); 275 allSupertypesAction.setSectionStyle(SectionStyle.HEADER); 276 allGroup.getChildren().add(allSupertypesAction); 277 278 Tag list = TagName.ul.create(); 279 280 for (EGenericType superType: allGenericSupertypes) { 281 Tag listItem = TagName.li.create(); 282 list.content(listItem); 283 genericType(superType, eObject, listItem.getContent()::add, progressMonitor); 284 } 285 addContent(allSupertypesAction, list.toString()); 286 addContent(allSupertypesAction, inheritanceDiagram); 287 } 288 } 289 } 290 291 protected String generateInheritanceDiagram( 292 int depth, 293 RelationshipDirection relationshipDirection, 294 boolean appendAttributes, 295 boolean appendOperations, 296 ProgressMonitor monitor) { 297 298 StringBuilder sb = new StringBuilder(); 299 DiagramTextGenerator gen = getDiagramTextGenerator(sb, appendAttributes, appendOperations); 300 if (gen == null) { 301 return null; 302 } 303 List<EClass> diagramElements = new ArrayList<>(); 304 diagramElements.add(eObject); 305 diagramElements.addAll(eObject.getEAllSuperTypes()); 306 gen.appendWithRelationships(diagramElements, relationshipDirection, depth); 307 308 return context.get(DiagramGenerator.class).generateUmlDiagram(sb.toString()); 309 } 310 311 312 private void generateAllOperations(List<EOperation> allOperations, Action allGroup, ProgressMonitor progressMonitor) { 313 if (!allOperations.isEmpty()) { 314 Action allOperationsAction = AppFactory.eINSTANCE.createAction(); 315 allOperationsAction.setText("Operations"); 316 allOperationsAction.setLocation(eObject.getName() + "-all-operations.html"); 317 allOperationsAction.setSectionStyle(SectionStyle.HEADER); 318 allGroup.getChildren().add(allOperationsAction); 319 320 EList<Action> operations = allOperationsAction.getSections(); 321 for (EOperation eOp: allOperations) { 322 operations.add(adaptChild(eOp).execute(eObject, progressMonitor)); 323 } 324 } 325 } 326 327 private void generateAllReferences(List<EReference> allReferences, Action allGroup, ProgressMonitor progressMonitor) { 328 if (!allReferences.isEmpty()) { 329 Action allReferencesAction = AppFactory.eINSTANCE.createAction(); 330 allReferencesAction.setText("References"); 331 allReferencesAction.setLocation(eObject.getName() + "-all-references.html"); 332 allReferencesAction.setSectionStyle(SectionStyle.HEADER); 333 allGroup.getChildren().add(allReferencesAction); 334 335 allReferencesAction.getContent().add(buildDynamicReferencesTable(allReferences, progressMonitor)); 336 } 337 } 338 339 private void generateAllAttributes(List<EAttribute> allAttributes, Action allGroup, ProgressMonitor progressMonitor) { 340 if (!allAttributes.isEmpty()) { 341 Action allAttributesAction = AppFactory.eINSTANCE.createAction(); 342 allAttributesAction.setText("Attributes"); 343 allAttributesAction.setLocation(eObject.getName() + "-all-attributes.html"); 344 allAttributesAction.setSectionStyle(SectionStyle.HEADER); 345 allGroup.getChildren().add(allAttributesAction); 346 347 allAttributesAction.getContent().add(buildDynamicAttributesTable(allAttributes, progressMonitor)); 348 } 349 } 350 351 protected org.nasdanika.html.model.html.Tag buildDynamicAttributesTable(List<EAttribute> attributes, ProgressMonitor progressMonitor) { 352 @SuppressWarnings("unchecked") 353 DynamicTableBuilder<EAttribute> attributesTableBuilder = new DynamicTableBuilder<>(); 354 attributesTableBuilder 355 .addStringColumnBuilder("name", true, true, "Name", attr -> link(attr, eObject)) 356 .addStringColumnBuilder("type", true, true, "Type", attr -> { 357 EGenericType genericType = attr.getEGenericType(); 358 if (genericType == null) { 359 return null; 360 } 361 StringBuilder sb = new StringBuilder(); 362 genericType(genericType, eObject, sb::append, progressMonitor); 363 return sb.toString(); 364 }) 365 .addStringColumnBuilder("cardinality", true, true, "Cardinality", EModelElementActionSupplier::cardinality) 366 .addBooleanColumnBuilder("changeable", true, true, "Changeable", EStructuralFeature::isChangeable) 367 .addBooleanColumnBuilder("derived", true, true, "Derived", EStructuralFeature::isDerived) 368 .addStringColumnBuilder("declaring-class", true, true, "Declaring Class", attr -> link(attr.getEContainingClass(), eObject)) 369 .addStringColumnBuilder("description", true, false, "Description", this::getEModelElementFirstDocSentence); 370 // Other things not visible? 371 372 org.nasdanika.html.model.html.Tag attributesTable = attributesTableBuilder.build(attributes, eObject.getEPackage().getNsURI().hashCode() + "-" + eObject.getName() + "-attributes", "attributes-table", progressMonitor); 373 return attributesTable; 374 } 375 376 protected org.nasdanika.html.model.html.Tag buildDynamicReferencesTable(List<EReference> references, ProgressMonitor progressMonitor) { 377 @SuppressWarnings("unchecked") 378 DynamicTableBuilder<EReference> referencesTableBuilder = new DynamicTableBuilder<>(); 379 referencesTableBuilder 380 .addStringColumnBuilder("name", true, true, "Name", ref -> link(ref, eObject)) 381 .addStringColumnBuilder("type", true, true, "Type", ref -> { 382 EGenericType genericType = ref.getEGenericType(); 383 if (genericType == null) { 384 return null; 385 } 386 StringBuilder sb = new StringBuilder(); 387 genericType(genericType, eObject, sb::append, progressMonitor); 388 return sb.toString(); 389 }) 390 .addStringColumnBuilder("cardinality", true, true, "Cardinality", EModelElementActionSupplier::cardinality) 391 .addBooleanColumnBuilder("changeable", true, true, "Changeable", EStructuralFeature::isChangeable) 392 .addBooleanColumnBuilder("derived", true, true, "Derived", EStructuralFeature::isDerived) 393 .addStringColumnBuilder("declaring-class", true, true, "Declaring Class", ref -> link(ref.getEContainingClass(), eObject)) 394 .addStringColumnBuilder("opposite", true, true, "Opposite", ref -> { 395 EReference opposite = NcoreUtil.getOpposite(ref); 396 return opposite == null ? null : link(opposite, eObject); 397 }) 398 .addStringColumnBuilder("description", true, false, "Description", this::getEModelElementFirstDocSentence); 399 // Other things not visible? 400 401 org.nasdanika.html.model.html.Tag referencesTable = referencesTableBuilder.build(references, eObject.getEPackage().getNsURI().hashCode() + "-" + eObject.getName() + "-references", "references-table", progressMonitor); 402 return referencesTable; 403 } 404 405 private void generateLoadSpecification( 406 Action action, 407 Comparator<ENamedElement> namedElementComparator, 408 ProgressMonitor progressMonitor) { 409 410 // Load specification 411 if (!eObject.isAbstract() && "true".equals(NcoreUtil.getNasdanikaAnnotationDetail(eObject, EObjectLoader.IS_LOADABLE, "true"))) { 412 Action loadSpecificationAction = AppFactory.eINSTANCE.createAction(); 413 loadSpecificationAction.setText("Load specification"); 414 loadSpecificationAction.setLocation(eObject.getName() + "-load-specification.html"); 415 action.getNavigation().add(loadSpecificationAction); 416 417 EModelElementDocumentation loadDoc = EmfUtil.getLoadDocumentation(eObject); 418 if (loadDoc != null) { 419 loadSpecificationAction.getContent().add(interpolatedMarkdown(loadDoc.getDocumentation(), loadDoc.getLocation(), progressMonitor)); 420 } 421 422 Predicate<EStructuralFeature> predicate = sf -> sf.isChangeable() && "true".equals(NcoreUtil.getNasdanikaAnnotationDetail(sf, EObjectLoader.IS_LOADABLE, "true")); 423 List<EStructuralFeature> sortedFeatures = eObject.getEAllStructuralFeatures().stream().filter(predicate.and(elementPredicate)).sorted(namedElementComparator).collect(Collectors.toList()); 424 425 Function<EStructuralFeature, String> keyExtractor = sf -> NcoreUtil.getNasdanikaAnnotationDetail(sf, EObjectLoader.LOAD_KEY, NcoreUtil.getFeatureKey(eObject, sf)); 426 Predicate<EStructuralFeature> homogenousPredicate = sf -> "true".equals(NcoreUtil.getNasdanikaAnnotationDetail(sf, EObjectLoader.IS_HOMOGENOUS)) || NcoreUtil.getNasdanikaAnnotationDetail(sf, EObjectLoader.REFERENCE_TYPE) != null; 427 Predicate<EStructuralFeature> strictContainmentPredicate = homogenousPredicate.and(sf -> "true".equals(NcoreUtil.getNasdanikaAnnotationDetail(sf, EObjectLoader.IS_STRICT_CONTAINMENT))); 428 Function<EStructuralFeature, Object[]> exclusiveWithExtractor = sf -> EObjectLoader.getExclusiveWith(eObject, sf, EObjectLoader.LOAD_KEY_PROVIDER); 429 430 @SuppressWarnings("unchecked") 431 DynamicTableBuilder<EStructuralFeature> loadSpecificationTableBuilder = new DynamicTableBuilder<>(); 432 loadSpecificationTableBuilder 433 .addStringColumnBuilder("key", true, true, "Key", sf -> { 434 String key = keyExtractor.apply(sf); 435 return TagName.a.create(key).attribute("href", "#key-section-" + key).attribute("style", "font-weight:bold", EObjectLoader.isDefaultFeature(eObject, sf)).toString(); 436 }) 437 .addStringColumnBuilder("type", true, true, "Type", attr -> { 438 EGenericType genericType = attr.getEGenericType(); 439 if (genericType == null) { 440 return null; 441 } 442 StringBuilder sb = new StringBuilder(); 443 genericType(genericType, eObject, sb::append, progressMonitor); 444 return sb.toString(); 445 }) 446 .addStringColumnBuilder("cardinality", true, false, "Cardinality", EModelElementActionSupplier::cardinality) 447 .addBooleanColumnBuilder("homogenous", true, false, "Homogenous", homogenousPredicate) 448 .addBooleanColumnBuilder("strict-containment", true, false, "Strict Containment", strictContainmentPredicate) 449 .addStringColumnBuilder("exclusive-with", true, false, "Exclusive With", sf -> { 450 Object[] exclusiveWith = exclusiveWithExtractor.apply(sf); 451 if (exclusiveWith.length == 0) { 452 return null; 453 } 454 Tag ul = TagName.ul.create(); 455 for (Object exw: exclusiveWith) { 456 ul.content(TagName.li.create(exw)); 457 } 458 return ul.toString(); 459 }) 460 .addStringColumnBuilder("description", true, false, "Description", this::getEStructuralFeatureFirstLoadDocSentence); 461 // Other things not visible? 462 463 org.nasdanika.html.model.html.Tag loadSpecificationTable = loadSpecificationTableBuilder.build(sortedFeatures, eObject.getEPackage().getNsURI().hashCode() + "-" + eObject.getName() + "-load-specification", "load-specification-table", progressMonitor); 464 465 for (EStructuralFeature sf: sortedFeatures) { 466 Action featureAction = AppFactory.eINSTANCE.createAction(); 467 String key = keyExtractor.apply(sf); 468 featureAction.setText(key); 469 String sectionAnchor = "key-section-" + key; 470 471 featureAction.setName(sectionAnchor); 472 loadSpecificationAction.getSections().add(featureAction); 473 474 // Properties table 475 Table table = context.get(BootstrapFactory.class).table(); 476 table.toHTMLElement().style().width("auto"); 477 478 genericType(sf.getEGenericType(), eObject, ETypedElementActionSupplier.addRow(table, "Type")::add, progressMonitor); 479 480 boolean isDefaultFeature = EObjectLoader.isDefaultFeature(eObject, sf); 481 if (isDefaultFeature) { 482 ETypedElementActionSupplier.addRow(table, "Default").add("true"); 483 } 484 485 boolean isHomogenous = homogenousPredicate.test(sf); 486 if (isHomogenous) { 487 ETypedElementActionSupplier.addRow(table, "Homogenous").add("true"); 488 } 489 490 boolean isStrictContainment = strictContainmentPredicate.test(sf); 491 if (isStrictContainment) { 492 ETypedElementActionSupplier.addRow(table, "Strict containment").add("true"); 493 } 494 495 Object[] exclusiveWith = exclusiveWithExtractor.apply(sf); 496 if (exclusiveWith.length != 0) { 497 Tag ul = TagName.ul.create(); 498 for (Object exw: exclusiveWith) { 499 ul.content(TagName.li.create(exw)); 500 } 501 ETypedElementActionSupplier.addRow(table, "Exclusive with").add(ul); 502 } 503 504 addContent(featureAction, table.toString()); 505 506 EModelElementDocumentation featureLoadDoc = getFeatureLoadDoc(sf); 507 if (featureLoadDoc != null) { 508 featureAction.getContent().add(interpolatedMarkdown(context.interpolateToString(featureLoadDoc.getDocumentation()), featureLoadDoc.getLocation(), progressMonitor)); 509 } 510 } 511 512 loadSpecificationAction.getContent().add(loadSpecificationTable); 513 } 514 } 515 516 protected EModelElementDocumentation getFeatureLoadDoc(EStructuralFeature sf) { 517 EModelElementDocumentation featureLoadDoc = EmfUtil.getLoadDocumentation(sf); 518 return featureLoadDoc == null ? EmfUtil.getDocumentation(sf) : featureLoadDoc; 519 } 520 521 protected String getEStructuralFeatureFirstLoadDocSentence(EStructuralFeature sf) { 522 EModelElementDocumentation documentation = getFeatureLoadDoc(sf); 523 if (documentation == null) { 524 return null; 525 } 526 527 MarkdownHelper markdownHelper = new MarkdownHelper() { 528 529 @Override 530 protected URI getResourceBase() { 531 return documentation.getLocation(); 532 } 533 534 @Override 535 protected DiagramGenerator getDiagramGenerator() { 536 return context == null ? super.getDiagramGenerator() : context.get(DiagramGenerator.class, super.getDiagramGenerator()); 537 } 538 539 }; 540 541 String ret = markdownHelper.firstPlainTextSentence(documentation.getDocumentation()); 542 return String.join(" ", ret.split("\\R")); // Replacing new lines, shall they be in the first sentence, with spaces. 543 } 544 545 546 protected DiagramTextGenerator getDiagramTextGenerator(StringBuilder sb, boolean appendAttributes, boolean appendOperations) { 547 String dialect = diagramDialectSupplier.get(); 548 if (Util.isBlank(dialect)) { 549 return null; 550 } 551 switch (dialect) { 552 case DiagramGenerator.UML_DIALECT: 553 return new PlantUmlTextGenerator(sb, elementPredicate, ec -> path(ec, eObject), this::getEModelElementFirstDocSentence, labelProvider) { 554 555 @Override 556 protected Collection<EClass> getSubTypes(EClass eClass) { 557 return EClassActionSupplier.this.getSubTypes(eClass); 558 } 559 560 @Override 561 protected Collection<EClass> getReferrers(EClass eClass) { 562 return EClassActionSupplier.this.getReferrers(eClass); 563 } 564 565 @Override 566 protected Collection<EClass> getUses(EClassifier eClassifier) { 567 return EClassActionSupplier.this.getUses(eClassifier); 568 } 569 570 @Override 571 protected boolean isAppendAttributes(EClass eClass) { 572 return appendAttributes; 573 } 574 575 @Override 576 protected boolean isAppendOperations(EClass eClass) { 577 return appendOperations; 578 } 579 580 @Override 581 protected String qualifiedName(EClassifier eClassifier) { 582 Class<?> ic = getInstanceClass(eClassifier, ePackageResolver); 583 return ic == null ? super.qualifiedName(eClassifier) : ic.getName(); 584 } 585 586 }; 587 case DiagramGenerator.MERMAID_DIALECT: 588 return new MermaidTextGenerator(sb, elementPredicate, ec -> path(ec, eObject), this::getEModelElementFirstDocSentence) { 589 590 @Override 591 protected Collection<EClass> getSubTypes(EClass eClass) { 592 return EClassActionSupplier.this.getSubTypes(eClass); 593 } 594 595 @Override 596 protected Collection<EClass> getReferrers(EClass eClass) { 597 return EClassActionSupplier.this.getReferrers(eClass); 598 } 599 600 @Override 601 protected Collection<EClass> getUses(EClassifier eClassifier) { 602 return EClassActionSupplier.this.getUses(eClassifier); 603 } 604 605 @Override 606 protected boolean isAppendAttributes(EClass eClass) { 607 return appendAttributes; 608 } 609 610 @Override 611 protected boolean isAppendOperations(EClass eClass) { 612 return appendOperations; 613 } 614 615 }; 616 default: 617 throw new UnsupportedOperationException("Unsupported dialect: " + dialect); 618 } 619 } 620 621 protected String generateDiagram( 622 int depth, 623 DiagramTextGenerator.RelationshipDirection relationshipDirection, 624 boolean appendAttributes, 625 boolean appendOperations, 626 ProgressMonitor monitor) { 627 628 DiagramGenerator diagramGenerator = context.get(DiagramGenerator.class); 629 630 StringBuilder sb = new StringBuilder(); 631 632 DiagramTextGenerator gen = getDiagramTextGenerator(sb, appendAttributes, appendOperations); 633 if (gen == null) { 634 return null; 635 } 636 gen.appendWithRelationships(Collections.singleton(eObject), relationshipDirection, depth); 637 638 return diagramGenerator.generateUmlDiagram(sb.toString()); 639 } 640 641 /** 642 * Override to return a list of sub-types of given EClass. 643 * This implementation returns all sub-types found in the resource set. 644 * @param eClass 645 * @return 646 */ 647 protected Collection<EClass> getSubTypes(EClass eClass) { 648 TreeIterator<?> acit; 649 Resource eResource = eClass.eResource(); 650 if (eResource == null) { 651 EPackage ePackage = eClass.getEPackage(); 652 if (ePackage == null) { 653 return Collections.emptySet(); 654 } 655 acit = ePackage.eAllContents(); 656 } else { 657 ResourceSet resourceSet = eResource.getResourceSet(); 658 acit = resourceSet == null ? eResource.getAllContents() : resourceSet.getAllContents(); 659 } 660 Set<EClass> ret = new HashSet<>(); 661 acit.forEachRemaining(obj -> { 662 if (obj instanceof EClass && ((EClass) obj).getESuperTypes().contains(eClass)) { 663 ret.add((EClass) obj); 664 } 665 }); 666 return ret; 667 } 668 669 /** 670 * @return Referrers to this class 671 */ 672 protected Collection<EClass> getReferrers() { 673 return getReferrers(eObject); 674 } 675 676}