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}