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