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                        };
581                case DiagramGenerator.MERMAID_DIALECT:
582                        return new MermaidTextGenerator(sb, elementPredicate, ec -> path(ec, eObject), this::getEModelElementFirstDocSentence) {
583                                
584                                @Override
585                                protected Collection<EClass> getSubTypes(EClass eClass) {
586                                        return EClassActionSupplier.this.getSubTypes(eClass);
587                                }
588                                
589                                @Override
590                                protected Collection<EClass> getReferrers(EClass eClass) {
591                                        return EClassActionSupplier.this.getReferrers(eClass);
592                                }
593                                
594                                @Override
595                                protected Collection<EClass> getUses(EClassifier eClassifier) {
596                                        return EClassActionSupplier.this.getUses(eClassifier);
597                                }
598                                
599                                @Override
600                                protected boolean isAppendAttributes(EClass eClass) {
601                                        return appendAttributes;
602                                }
603                                
604                                @Override
605                                protected boolean isAppendOperations(EClass eClass) {
606                                        return appendOperations;
607                                }
608                                
609                        };
610                default:
611                        throw new UnsupportedOperationException("Unsupported dialect: " + dialect);
612                }                                       
613        }
614        
615        protected String generateDiagram(
616                        int depth, 
617                        DiagramTextGenerator.RelationshipDirection relationshipDirection,
618                        boolean appendAttributes,
619                        boolean appendOperations,
620                        ProgressMonitor monitor) {
621
622                DiagramGenerator diagramGenerator = context.get(DiagramGenerator.class);                
623                
624                StringBuilder sb = new StringBuilder();
625
626                DiagramTextGenerator gen = getDiagramTextGenerator(sb, appendAttributes, appendOperations);
627                if (gen == null) {
628                        return null;
629                }
630                gen.appendWithRelationships(Collections.singleton(eObject), relationshipDirection, depth);
631                
632                return diagramGenerator.generateUmlDiagram(sb.toString());
633        }
634                
635        /**
636         * Override to return a list of sub-types of given EClass. 
637         * This implementation returns all sub-types found in the resource set. 
638         * @param eClass
639         * @return
640         */
641        protected Collection<EClass> getSubTypes(EClass eClass) {
642                TreeIterator<?> acit;
643                Resource eResource = eClass.eResource();
644                if (eResource == null) {
645                        EPackage ePackage = eClass.getEPackage();
646                        if (ePackage == null) {
647                                return Collections.emptySet();
648                        }
649                        acit = ePackage.eAllContents();
650                } else {
651                        ResourceSet resourceSet = eResource.getResourceSet();
652                        acit = resourceSet == null ? eResource.getAllContents() : resourceSet.getAllContents();
653                }
654                Set<EClass> ret = new HashSet<>();
655                acit.forEachRemaining(obj -> {
656                        if (obj instanceof EClass && ((EClass) obj).getESuperTypes().contains(eClass)) {
657                                ret.add((EClass) obj);
658                        }
659                });
660                return ret;
661        }
662                        
663        /**
664         * @return Referrers to this class
665         */     
666        protected Collection<EClass> getReferrers() {
667                return getReferrers(eObject);
668        }
669        
670}