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.BooleanSupplier; 012import java.util.function.Predicate; 013import java.util.function.Supplier; 014import java.util.stream.Collectors; 015 016import org.eclipse.emf.common.util.EList; 017import org.eclipse.emf.common.util.TreeIterator; 018import org.eclipse.emf.ecore.EAttribute; 019import org.eclipse.emf.ecore.EClass; 020import org.eclipse.emf.ecore.EClassifier; 021import org.eclipse.emf.ecore.EGenericType; 022import org.eclipse.emf.ecore.ENamedElement; 023import org.eclipse.emf.ecore.EOperation; 024import org.eclipse.emf.ecore.EPackage; 025import org.eclipse.emf.ecore.EReference; 026import org.eclipse.emf.ecore.EStructuralFeature; 027import org.eclipse.emf.ecore.resource.Resource; 028import org.eclipse.emf.ecore.resource.ResourceSet; 029import org.nasdanika.common.Context; 030import org.nasdanika.common.DiagramGenerator; 031import org.nasdanika.common.ProgressMonitor; 032import org.nasdanika.common.Util; 033import org.nasdanika.emf.DiagramTextGenerator; 034import org.nasdanika.emf.EmfUtil; 035import org.nasdanika.emf.EmfUtil.EModelElementDocumentation; 036import org.nasdanika.emf.MermaidTextGenerator; 037import org.nasdanika.emf.PlantUmlTextGenerator; 038import org.nasdanika.emf.DiagramTextGenerator.RelationshipDirection; 039import org.nasdanika.emf.persistence.EObjectLoader; 040import org.nasdanika.html.Fragment; 041import org.nasdanika.html.HTMLFactory; 042import org.nasdanika.html.Tag; 043import org.nasdanika.html.TagName; 044import org.nasdanika.html.bootstrap.BootstrapFactory; 045import org.nasdanika.html.bootstrap.Table; 046import org.nasdanika.html.model.app.Action; 047import org.nasdanika.html.model.app.AppFactory; 048import org.nasdanika.html.model.app.SectionStyle; 049import org.nasdanika.ncore.util.NcoreUtil; 050 051public class EClassActionSupplier extends EClassifierActionSupplier<EClass> { 052 053 private BooleanSupplier isGenerateLoadSpecification; 054 private Supplier<String> diagramDialectSupplier; 055 056 public EClassActionSupplier( 057 EClass value, 058 Context context, 059 java.util.function.Function<EPackage,String> ePackagePathComputer, 060 java.util.function.Function<String, String> javadocResolver, 061 BooleanSupplier isGenerateLoadSpecification, 062 Supplier<String> diagramDialectSupplier) { 063 super(value, context, ePackagePathComputer, javadocResolver); 064 this.isGenerateLoadSpecification = isGenerateLoadSpecification; 065 this.diagramDialectSupplier = diagramDialectSupplier; 066 } 067 068 @Override 069 public org.nasdanika.html.model.app.Action execute(EClass contextEClass, ProgressMonitor progressMonitor) throws Exception { 070 Action action = super.execute(contextEClass, progressMonitor); 071 072 action.setSectionStyle(SectionStyle.HEADER); 073 074 // Diagram 075 String diagramMode = NcoreUtil.getNasdanikaAnnotationDetail(eObject, "diagram", "navigation"); 076 String diagram = generateDiagram(1, RelationshipDirection.both, true, true, progressMonitor); 077 if (!Util.isBlank(diagram)) { 078 switch (diagramMode) { 079 case "content": 080 addContent(action, diagram); 081 break; 082 case "none": 083 break; 084 case "navigation": { 085 Action diagramAction = AppFactory.eINSTANCE.createAction(); 086 action.getNavigation().add(diagramAction); 087 diagramAction.setText("Diagram"); 088 diagramAction.setIcon("fas fa-project-diagram"); 089 diagramAction.setLocation(eObject.getName() + "-diagram.html"); 090 addContent(diagramAction, diagram); 091 break; 092 } 093 case "anonymous": { 094 Action diagramAction = AppFactory.eINSTANCE.createAction(); 095 action.getAnonymous().add(diagramAction); 096 diagramAction.setText("Diagram"); 097 diagramAction.setIcon("fas fa-project-diagram"); 098 diagramAction.setLocation(eObject.getName() + "-diagram.html"); 099 addContent(diagramAction, diagram); 100 break; 101 } 102 default: 103 throw new IllegalArgumentException("Unsupported diagram annotation value '" + diagramMode +"' on EClass " + eObject); 104 } 105 } 106 107 // Generic supertypes 108 EList<EGenericType> eGenericSuperTypes = eObject.getEGenericSuperTypes(); 109 if (!eGenericSuperTypes.isEmpty()) { 110 HTMLFactory htmlFactory = context.get(HTMLFactory.class); 111 Fragment gstf = htmlFactory.fragment(TagName.a.create(TagName.h3.create("Supertypes")).attribute("name", "supertypes")); 112 113 Tag list = TagName.ul.create(); 114 gstf.content(list); 115 116 for (EGenericType superType: eGenericSuperTypes) { 117 Tag listItem = TagName.li.create(); 118 list.content(listItem); 119 genericType(superType, eObject, listItem.getContent(), progressMonitor); 120 } 121 addContent(action, gstf.toString()); 122 } 123 124 // Subtypes 125 Collection<EClass> eSubTypes = getSubTypes(eObject).stream().sorted((a,b) -> a.getName().compareTo(b.getName())).collect(Collectors.toList()); 126 if (!eSubTypes.isEmpty()) { 127 HTMLFactory htmlFactory = context.get(HTMLFactory.class); 128 Fragment gstf = htmlFactory.fragment(TagName.a.create(TagName.h3.create("Subtypes")).attribute("name", "subtypes")); 129 130 Tag list = TagName.ul.create(); 131 gstf.content(list); 132 133 for (EClass subType: eSubTypes) { 134 list.content(TagName.li.create(link(subType, eObject))); 135 } 136 addContent(action, gstf.toString()); 137 } 138 139 // Referrers 140 Collection<EClass> referrers = getReferrers().stream().sorted((a,b) -> a.getName().compareTo(b.getName())).collect(Collectors.toList()); 141 if (!referrers.isEmpty()) { 142 HTMLFactory htmlFactory = context.get(HTMLFactory.class); 143 Fragment gstf = htmlFactory.fragment(TagName.a.create(TagName.h3.create("Referrers")).attribute("name", "referrers")); 144 145 Tag list = TagName.ul.create(); 146 gstf.content(list); 147 148 for (EClass referrer: referrers) { 149 list.content(TagName.li.create(link(referrer, eObject))); 150 } 151 addContent(action, gstf.toString()); 152 } 153 154 // Uses 155 Collection<EClass> uses = getUses().stream().sorted((a,b) -> a.getName().compareTo(b.getName())).collect(Collectors.toList()); 156 if (!uses.isEmpty()) { 157 HTMLFactory htmlFactory = context.get(HTMLFactory.class); 158 Fragment gstf = htmlFactory.fragment(TagName.a.create(TagName.h3.create("Uses")).attribute("name", "uses")); 159 160 Tag list = TagName.ul.create(); 161 gstf.content(list); 162 163 for (EClass use: uses) { 164 list.content(TagName.li.create(link(use, eObject))); 165 } 166 addContent(action, gstf.toString()); 167 } 168 169 Comparator<ENamedElement> namedElementComparator = (a,b) -> a.getName().compareTo(b.getName()); 170 171 List<EAttribute> allAttributes = eObject.getEAllAttributes().stream().sorted((a,b) -> a.getName().compareTo(b.getName())).collect(Collectors.toList()); 172 List<EReference> allReferences = eObject.getEAllReferences().stream().sorted((a,b) -> a.getName().compareTo(b.getName())).collect(Collectors.toList()); 173 List<EOperation> allOperations = eObject.getEAllOperations().stream().sorted((a,b) -> a.getName().compareTo(b.getName())).collect(Collectors.toList()); 174 EList<EGenericType> allGenericSupertypes = eObject.getEAllGenericSuperTypes(); 175 176 if (allAttributes.size() + allReferences.size() + allOperations.size() + allGenericSupertypes.size() != 0) { 177 Action allGroup = AppFactory.eINSTANCE.createAction(); 178 allGroup.setText("All"); 179 allGroup.setUuid(action.getUuid() + "-all"); 180 action.getNavigation().add(allGroup); 181 182 generateAllAttributes(allAttributes, allGroup, progressMonitor); 183 generateAllReferences(allReferences, allGroup, progressMonitor); 184 generateAllOperations(allOperations, allGroup, progressMonitor); 185 generateAllGenericSupertypes(allGenericSupertypes, allGroup, progressMonitor); 186 } 187 188 // No load specification for EMap entries. 189 if (isGenerateLoadSpecification.getAsBoolean() && Map.Entry.class != instanceClass) { 190 generateLoadSpecification(action, namedElementComparator, progressMonitor); 191 } 192 193// TODO - Table (list) of contents 194// Map<String, Object> locConfig = new LinkedHashMap<>(); 195// locConfig.put("tooltip", true); 196// locConfig.put("header", "Members"); 197// locConfig.put("role", Action.Role.SECTION); 198// addContent(data, Collections.singletonMap("component-list-of-contents", locConfig)); 199 200 EList<Action> sections = action.getSections(); 201 if (!eObject.getEAttributes().isEmpty()) { 202 Action attributesCategory = AppFactory.eINSTANCE.createAction(); 203 attributesCategory.setText("Attributes"); 204 attributesCategory.setName("attributes"); 205 attributesCategory.setSectionStyle(SectionStyle.HEADER); 206 sections.add(attributesCategory); 207 EList<Action> attributes = attributesCategory.getSections(); 208 for (EStructuralFeature sf: eObject.getEAttributes().stream().sorted((a,b) -> a.getName().compareTo(b.getName())).collect(Collectors.toList())) { 209 attributes.add(adaptChild(sf).execute(null, progressMonitor)); 210 } 211 } 212 213 if (!eObject.getEReferences().isEmpty()) { 214 Action referencesCategory = AppFactory.eINSTANCE.createAction(); 215 referencesCategory.setText("References"); 216 referencesCategory.setName("references"); 217 referencesCategory.setSectionStyle(SectionStyle.HEADER); 218 sections.add(referencesCategory); 219 EList<Action> references = referencesCategory.getSections(); 220 for (EStructuralFeature sf: eObject.getEReferences().stream().sorted((a,b) -> a.getName().compareTo(b.getName())).collect(Collectors.toList())) { 221 references.add(adaptChild(sf).execute(null, progressMonitor)); 222 } 223 } 224 225 if (!eObject.getEOperations().isEmpty()) { 226 Action operationsCategory = AppFactory.eINSTANCE.createAction(); 227 operationsCategory.setText("Operations"); 228 operationsCategory.setName("operations"); 229 operationsCategory.setSectionStyle(SectionStyle.HEADER); 230 sections.add(operationsCategory); 231 EList<Action> operations = operationsCategory.getSections(); 232 for (EOperation eOp: eObject.getEOperations().stream().sorted((a,b) -> a.getName().compareTo(b.getName())).collect(Collectors.toList())) { 233 operations.add(adaptChild(eOp).execute(null, progressMonitor)); 234 } 235 } 236 237 if (eObject.isInterface()) { 238 action.setIcon(ICONS_BASE + "EInterface.gif"); 239 } 240 241 return action; 242 } 243 244 private void generateAllGenericSupertypes(List<EGenericType> allGenericSupertypes, Action allGroup, ProgressMonitor progressMonitor) throws Exception { 245 if (!allGenericSupertypes.isEmpty()) { 246 String inheritanceDiagram = generateInheritanceDiagram(0, RelationshipDirection.both, true, true, progressMonitor); 247 if (!Util.isBlank(inheritanceDiagram)) { 248 Action allSupertypesAction = AppFactory.eINSTANCE.createAction(); 249 allSupertypesAction.setText("Supertypes"); 250 allSupertypesAction.setLocation(eObject.getName() + "-all-supertypes.html"); 251 allSupertypesAction.setSectionStyle(SectionStyle.HEADER); 252 allGroup.getChildren().add(allSupertypesAction); 253 254 Tag list = TagName.ul.create(); 255 256 for (EGenericType superType: allGenericSupertypes) { 257 Tag listItem = TagName.li.create(); 258 list.content(listItem); 259 genericType(superType, eObject, listItem.getContent(), progressMonitor); 260 } 261 addContent(allSupertypesAction, list.toString()); 262 addContent(allSupertypesAction, inheritanceDiagram); 263 } 264 } 265 } 266 267 protected String generateInheritanceDiagram( 268 int depth, 269 RelationshipDirection relationshipDirection, 270 boolean appendAttributes, 271 boolean appendOperations, 272 ProgressMonitor monitor) throws Exception { 273 274 StringBuilder sb = new StringBuilder(); 275 DiagramTextGenerator gen = getDiagramTextGenerator(sb, appendAttributes, appendOperations); 276 if (gen == null) { 277 return null; 278 } 279 List<EClass> diagramElements = new ArrayList<>(); 280 diagramElements.add(eObject); 281 diagramElements.addAll(eObject.getEAllSuperTypes()); 282 gen.appendWithRelationships(diagramElements, relationshipDirection, depth); 283 284 return context.get(DiagramGenerator.class).generateUmlDiagram(sb.toString()); 285 } 286 287 288 private void generateAllOperations(List<EOperation> allOperations, Action allGroup, ProgressMonitor progressMonitor) throws Exception { 289 if (!allOperations.isEmpty()) { 290 Action allOperationsAction = AppFactory.eINSTANCE.createAction(); 291 allOperationsAction.setText("Operations"); 292 allOperationsAction.setLocation(eObject.getName() + "-all-operations.html"); 293 allOperationsAction.setSectionStyle(SectionStyle.HEADER); 294 allGroup.getChildren().add(allOperationsAction); 295 296 EList<Action> operations = allOperationsAction.getSections(); 297 for (EOperation eOp: allOperations) { 298 operations.add(adaptChild(eOp).execute(eObject, progressMonitor)); 299 } 300 } 301 } 302 303 private void generateAllReferences(List<EReference> allReferences, Action allGroup, ProgressMonitor progressMonitor) throws Exception { 304 if (!allReferences.isEmpty()) { 305 Action allReferencesAction = AppFactory.eINSTANCE.createAction(); 306 allReferencesAction.setText("References"); 307 allReferencesAction.setLocation(eObject.getName() + "-all-references.html"); 308 allReferencesAction.setSectionStyle(SectionStyle.HEADER); 309 allGroup.getChildren().add(allReferencesAction); 310 311 EList<Action> references = allReferencesAction.getSections(); 312 for (EStructuralFeature sf: allReferences) { 313 references.add(adaptChild(sf).execute(eObject, progressMonitor)); 314 } 315 } 316 } 317 318 private void generateAllAttributes(List<EAttribute> allAttributes, Action allGroup, ProgressMonitor progressMonitor) throws Exception { 319 if (!allAttributes.isEmpty()) { 320 Action allAttributesAction = AppFactory.eINSTANCE.createAction(); 321 allAttributesAction.setText("Attributes"); 322 allAttributesAction.setLocation(eObject.getName() + "-all-attributes.html"); 323 allAttributesAction.setSectionStyle(SectionStyle.HEADER); 324 allGroup.getChildren().add(allAttributesAction); 325 326 EList<Action> attributes = allAttributesAction.getSections(); 327 for (EStructuralFeature sf: allAttributes) { 328 attributes.add(adaptChild(sf).execute(eObject, progressMonitor)); 329 } 330 } 331 } 332 333 private void generateLoadSpecification( 334 Action action, 335 Comparator<ENamedElement> namedElementComparator, 336 ProgressMonitor progressMonitor) throws Exception { 337 338 // Load specification 339 if (!eObject.isAbstract() && "true".equals(NcoreUtil.getNasdanikaAnnotationDetail(eObject, EObjectLoader.IS_LOADABLE, "true"))) { 340 Action loadSpecificationAction = AppFactory.eINSTANCE.createAction(); 341 loadSpecificationAction.setText("Load specification"); 342 loadSpecificationAction.setLocation(eObject.getName() + "-load-specification.html"); 343 action.getNavigation().add(loadSpecificationAction); 344 345 EModelElementDocumentation loadDoc = EmfUtil.getLoadDocumentation(eObject); 346 if (loadDoc != null) { 347 loadSpecificationAction.getContent().add(interpolatedMarkdown(loadDoc.getDocumentation(), loadDoc.getLocation(), progressMonitor)); 348 } 349 350 Tag toc = TagName.ul.create(); 351 352 Predicate<EStructuralFeature> predicate = sf -> sf.isChangeable() && "true".equals(NcoreUtil.getNasdanikaAnnotationDetail(sf, EObjectLoader.IS_LOADABLE, "true")); 353 for (EStructuralFeature sf: eObject.getEAllStructuralFeatures().stream().filter(predicate).sorted(namedElementComparator).collect(Collectors.toList())) { 354 Action featureAction = AppFactory.eINSTANCE.createAction(); 355 String key = NcoreUtil.getNasdanikaAnnotationDetail(sf, EObjectLoader.LOAD_KEY, NcoreUtil.getFeatureKey(eObject, sf)); 356 featureAction.setText(key); 357 String sectionAnchor = "key-section-" + key; 358 359 featureAction.setName(sectionAnchor); 360 loadSpecificationAction.getSections().add(featureAction); 361 362 // Properties table 363 Table table = context.get(BootstrapFactory.class).table(); 364 table.toHTMLElement().style().width("auto"); 365 366 genericType(sf.getEGenericType(), eObject, ETypedElementActionSupplier.addRow(table, "Type"), progressMonitor); 367 368 boolean isDefaultFeature = EObjectLoader.isDefaultFeature(eObject, sf); 369 if (isDefaultFeature) { 370 ETypedElementActionSupplier.addRow(table, "Default").add("true"); 371 } 372 toc.accept(TagName.li.create(TagName.a.create(key).attribute("href", "#" + sectionAnchor).attribute("style", "font-weight:bold", isDefaultFeature))); 373 374 boolean isHomogenous = "true".equals(NcoreUtil.getNasdanikaAnnotationDetail(sf, EObjectLoader.IS_HOMOGENOUS)) || NcoreUtil.getNasdanikaAnnotationDetail(sf, EObjectLoader.REFERENCE_TYPE) != null; 375 if (isHomogenous) { 376 ETypedElementActionSupplier.addRow(table, "Homogenous").add("true"); 377 } 378 379 boolean isStrictContainment = isHomogenous && "true".equals(NcoreUtil.getNasdanikaAnnotationDetail(sf, EObjectLoader.IS_STRICT_CONTAINMENT)); 380 if (isStrictContainment) { 381 ETypedElementActionSupplier.addRow(table, "Strict containment").add("true"); 382 } 383 384 Object[] exclusiveWith = EObjectLoader.getExclusiveWith(eObject, sf, EObjectLoader.LOAD_KEY_PROVIDER); 385 if (exclusiveWith.length != 0) { 386 Tag ul = TagName.ul.create(); 387 for (Object exw: exclusiveWith) { 388 ul.content(TagName.li.create(exw)); 389 } 390 ETypedElementActionSupplier.addRow(table, "Exclusive with").add(ul); 391 } 392 393 addContent(featureAction, table.toString()); 394 395 EModelElementDocumentation featureLoadDoc = EmfUtil.getLoadDocumentation(sf); 396 if (featureLoadDoc == null) { 397// featureLoadDoc = EObjectAdaptable.getResourceContext(sf).getString("documentation", EcoreUtil.getDocumentation(sf)); 398// if (Util.isBlank(featureLoadDoc)) { 399 featureLoadDoc = EmfUtil.getDocumentation(sf); 400// } 401 } 402 if (featureLoadDoc != null) { 403 featureAction.getContent().add(interpolatedMarkdown(context.interpolateToString(featureLoadDoc.getDocumentation()), featureLoadDoc.getLocation(), progressMonitor)); 404 } 405 } 406 407 addContent(loadSpecificationAction, toc.toString()); 408 } 409 } 410 411 protected DiagramTextGenerator getDiagramTextGenerator(StringBuilder sb, boolean appendAttributes, boolean appendOperations) { 412 String dialect = diagramDialectSupplier.get(); 413 if (Util.isBlank(dialect)) { 414 return null; 415 } 416 switch (dialect) { 417 case DiagramGenerator.UML_DIALECT: 418 return new PlantUmlTextGenerator(sb, ec -> path(ec, eObject), this::getEModelElementFirstDocSentence) { 419 420 @Override 421 protected Collection<EClass> getSubTypes(EClass eClass) { 422 return EClassActionSupplier.this.getSubTypes(eClass); 423 } 424 425 @Override 426 protected Collection<EClass> getReferrers(EClass eClass) { 427 return EClassActionSupplier.this.getReferrers(eClass); 428 } 429 430 @Override 431 protected Collection<EClass> getUses(EClassifier eClassifier) { 432 return EClassActionSupplier.this.getUses(eClassifier); 433 } 434 435 @Override 436 protected boolean isAppendAttributes(EClass eClass) { 437 return appendAttributes; 438 } 439 440 @Override 441 protected boolean isAppendOperations(EClass eClass) { 442 return appendOperations; 443 } 444 445 }; 446 case DiagramGenerator.MERMAID_DIALECT: 447 return new MermaidTextGenerator(sb, ec -> path(ec, eObject), this::getEModelElementFirstDocSentence) { 448 449 @Override 450 protected Collection<EClass> getSubTypes(EClass eClass) { 451 return EClassActionSupplier.this.getSubTypes(eClass); 452 } 453 454 @Override 455 protected Collection<EClass> getReferrers(EClass eClass) { 456 return EClassActionSupplier.this.getReferrers(eClass); 457 } 458 459 @Override 460 protected Collection<EClass> getUses(EClassifier eClassifier) { 461 return EClassActionSupplier.this.getUses(eClassifier); 462 } 463 464 @Override 465 protected boolean isAppendAttributes(EClass eClass) { 466 return appendAttributes; 467 } 468 469 @Override 470 protected boolean isAppendOperations(EClass eClass) { 471 return appendOperations; 472 } 473 474 }; 475 default: 476 throw new UnsupportedOperationException("Unsupported dialect: " + dialect); 477 } 478 } 479 480 protected String generateDiagram( 481 int depth, 482 DiagramTextGenerator.RelationshipDirection relationshipDirection, 483 boolean appendAttributes, 484 boolean appendOperations, 485 ProgressMonitor monitor) throws Exception { 486 487 DiagramGenerator diagramGenerator = context.get(DiagramGenerator.class); 488 489 StringBuilder sb = new StringBuilder(); 490 491 DiagramTextGenerator gen = getDiagramTextGenerator(sb, appendAttributes, appendOperations); 492 if (gen == null) { 493 return null; 494 } 495 gen.appendWithRelationships(Collections.singleton(eObject), relationshipDirection, depth); 496 497 return diagramGenerator.generateUmlDiagram(sb.toString()); 498 } 499 500 /** 501 * Override to return a list of sub-types of given EClass. 502 * This implementation returns all sub-types found in the resource set. 503 * @param eClass 504 * @return 505 */ 506 protected Collection<EClass> getSubTypes(EClass eClass) { 507 TreeIterator<?> acit; 508 Resource eResource = eClass.eResource(); 509 if (eResource == null) { 510 EPackage ePackage = eClass.getEPackage(); 511 if (ePackage == null) { 512 return Collections.emptySet(); 513 } 514 acit = ePackage.eAllContents(); 515 } else { 516 ResourceSet resourceSet = eResource.getResourceSet(); 517 acit = resourceSet == null ? eResource.getAllContents() : resourceSet.getAllContents(); 518 } 519 Set<EClass> ret = new HashSet<>(); 520 acit.forEachRemaining(obj -> { 521 if (obj instanceof EClass && ((EClass) obj).getESuperTypes().contains(eClass)) { 522 ret.add((EClass) obj); 523 } 524 }); 525 return ret; 526 } 527 528 /** 529 * @return Referrers to this class 530 */ 531 protected Collection<EClass> getReferrers() { 532 return getReferrers(eObject); 533 } 534 535}