001package org.nasdanika.html.ecore;
002
003import java.io.IOException;
004import java.nio.charset.StandardCharsets;
005import java.util.ArrayList;
006import java.util.Collection;
007import java.util.Collections;
008import java.util.function.BiFunction;
009import java.util.function.Function;
010import java.util.function.Predicate;
011import java.util.function.Supplier;
012import java.util.stream.Collectors;
013
014import org.apache.commons.codec.binary.Hex;
015import org.eclipse.emf.common.util.EList;
016import org.eclipse.emf.ecore.EClass;
017import org.eclipse.emf.ecore.EClassifier;
018import org.eclipse.emf.ecore.EModelElement;
019import org.eclipse.emf.ecore.ENamedElement;
020import org.eclipse.emf.ecore.EObject;
021import org.eclipse.emf.ecore.EPackage;
022import org.eclipse.emf.ecore.EReference;
023import org.nasdanika.common.Context;
024import org.nasdanika.common.DiagramGenerator;
025import org.nasdanika.common.ProgressMonitor;
026import org.nasdanika.common.Util;
027import org.nasdanika.emf.DiagramTextGenerator;
028import org.nasdanika.emf.DiagramTextGenerator.RelationshipDirection;
029import org.nasdanika.emf.MermaidTextGenerator;
030import org.nasdanika.emf.PlantUmlTextGenerator;
031import org.nasdanika.exec.content.ContentFactory;
032import org.nasdanika.exec.content.Text;
033import org.nasdanika.html.model.app.Action;
034import org.nasdanika.html.model.app.AppFactory;
035import org.nasdanika.ncore.util.NcoreUtil;
036
037public class EPackageActionSupplier extends ENamedElementActionSupplier<EPackage> {
038
039        private Supplier<String> diagramDialectSupplier;
040        private Function<EClassifier, EReference> eClassifierRoleProvider;
041
042        public EPackageActionSupplier(
043                        EPackage value, 
044                        Context context, 
045                        java.util.function.Function<EPackage,String> ePackagePathComputer, 
046                        Predicate<EModelElement> elementPredicate,
047                        BiFunction<ENamedElement, String, String> labelProvider,
048                        Supplier<String> diagramDialectSupplier,
049                        Function<EClassifier, EReference> eClassifierRoleProvider) {            
050                super(value, context, ePackagePathComputer, elementPredicate, labelProvider);
051                this.diagramDialectSupplier = diagramDialectSupplier;
052                this.eClassifierRoleProvider = eClassifierRoleProvider;
053        }
054        
055        @Override
056        protected void header(Action action, ProgressMonitor progressMonitor) {
057                Text text = ContentFactory.eINSTANCE.createText();
058                text.setContent("<div class='text-monospace'>" + eObject.getNsURI() + "</div>");
059                action.getContent().add(text);
060        }
061                
062        @SuppressWarnings("unchecked")
063        @Override
064        public Action execute(EClass contextEClass, ProgressMonitor progressMonitor) {
065                Action action = super.execute(contextEClass, progressMonitor);
066                String ePackageFolder = ePackagePathComputer == null ? Hex.encodeHexString(eObject.getNsURI().getBytes(StandardCharsets.UTF_8)) : ePackagePathComputer.apply(eObject);
067                action.setLocation(ePackageFolder + "/package-summary.html");
068                action.setId(eObject.eClass().getName() + "-" + encodeEPackage(eObject));
069                action.getUris().add(eObject.getNsURI());
070                
071                String diagramMode = NcoreUtil.getNasdanikaAnnotationDetail(eObject, "diagram", "navigation");
072                String diagram = generateDiagram(0, RelationshipDirection.both, true, true);
073                if (!Util.isBlank(diagram)) {
074                        switch (diagramMode) {
075                        case "content":
076                                addContent(action, diagram);
077                                break;
078                        case "none":
079                                break;
080                        case "navigation": {
081                                Action diagramAction = AppFactory.eINSTANCE.createAction();
082                                action.getNavigation().add(diagramAction);
083                                diagramAction.setText("Diagram");
084                                diagramAction.setIcon("fas fa-project-diagram");
085                                diagramAction.setLocation("package-summary-diagram.html");
086                                addContent(diagramAction, diagram);
087                                break;
088                        }
089                        case "anonymous": {
090                                Action diagramAction = AppFactory.eINSTANCE.createAction();
091                                action.getAnonymous().add(diagramAction);
092                                diagramAction.setText("Diagram");
093                                diagramAction.setIcon("fas fa-project-diagram");
094                                diagramAction.setLocation(ePackageFolder + "/package-summary-diagram.html");
095                                addContent(diagramAction, diagram);
096                                break;                  
097                        }
098                        default:
099                                throw new IllegalArgumentException("Unsupported diagram annotation value '" + diagramMode +"' on EPackage " + eObject);                         
100                        }
101                }
102                
103                // TODO - Table (list) of contents
104//              addContent(data, Collections.singletonMap("component-list-of-contents", Collections.singletonMap("tooltip", true))); 
105                
106                EList<EObject> children = action.getChildren();
107                for (EPackage subPackage: eObject.getESubpackages().stream().filter(elementPredicate).sorted((a,b) ->  a.getName().compareTo(b.getName())).collect(Collectors.toList())) {
108                        children.add(adaptChild(subPackage).execute(contextEClass, progressMonitor));
109                }
110        
111                for (EClassifier eClassifier: eObject.getEClassifiers().stream().filter(elementPredicate).sorted((a,b) ->  a.getName().compareTo(b.getName())).collect(Collectors.toList())) {
112                        Action eClassifierAction = adaptChild(eClassifier).execute(contextEClass, progressMonitor);
113                        EReference eClassifierRole = eClassifierRoleProvider.apply(eClassifier);
114                        ((Collection<Object>) action.eGet(eClassifierRole)).add(eClassifierAction);
115                }
116                
117                return action;
118        }
119        
120        protected DiagramTextGenerator getDiagramTextGenerator(StringBuilder sb, boolean appendAttributes, boolean appendOperations) {
121                String dialect = diagramDialectSupplier.get();
122                if (Util.isBlank(dialect)) {
123                        return null;
124                }
125                switch (dialect) {
126                case DiagramGenerator.UML_DIALECT:
127                        return new PlantUmlTextGenerator(sb, elementPredicate, eClassifierLinkResolver, this::getEModelElementFirstDocSentence, labelProvider) {
128                                
129                                @Override
130                                protected Collection<EClass> getSubTypes(EClass eClass) {
131                                        return EPackageActionSupplier.this.getSubTypes(eClass);
132                                }
133                                
134                                @Override
135                                protected Collection<EClass> getReferrers(EClass eClass) {
136                                        return EPackageActionSupplier.this.getReferrers(eClass);
137                                }
138                                
139                                @Override
140                                protected boolean isAppendAttributes(EClass eClass) {
141                                        return appendAttributes;
142                                }
143                                
144                                @Override
145                                protected boolean isAppendOperations(EClass eClass) {
146                                        return appendOperations;                                
147                                }
148                                
149                                @Override
150                                protected Collection<EClass> getUses(EClassifier eClassifier) {
151                                        return Collections.emptySet(); // No usage information on package diagrams - too much.
152                                }
153                                                                        
154                        };
155                case DiagramGenerator.MERMAID_DIALECT:
156                        return new MermaidTextGenerator(sb, elementPredicate, eClassifierLinkResolver, this::getEModelElementFirstDocSentence) {
157                                
158                                @Override
159                                protected Collection<EClass> getSubTypes(EClass eClass) {
160                                        return EPackageActionSupplier.this.getSubTypes(eClass);
161                                }
162                                
163                                @Override
164                                protected Collection<EClass> getReferrers(EClass eClass) {
165                                        return EPackageActionSupplier.this.getReferrers(eClass);
166                                }
167                                
168                                @Override
169                                protected boolean isAppendAttributes(EClass eClass) {
170                                        return appendAttributes;
171                                }
172                                
173                                @Override
174                                protected boolean isAppendOperations(EClass eClass) {
175                                        return appendOperations;                                
176                                }
177                                
178                                @Override
179                                protected Collection<EClass> getUses(EClassifier eClassifier) {
180                                        return Collections.emptySet(); // No usage information on package diagrams - too much.
181                                }
182                                                                        
183                        };
184                default:
185                        throw new UnsupportedOperationException("Unsupported dialect: " + dialect);
186                }                                       
187        }
188        
189        /**
190         * Generates PNG diagram.
191         * @return Inline PNG and the image map.
192         * @throws IOException 
193         */
194        protected String generateDiagram(
195                        int depth, 
196                        PlantUmlTextGenerator.RelationshipDirection relationshipDirection,
197                        boolean appendAttributes,
198                        boolean appendOperations) {
199                
200                StringBuilder sb = new StringBuilder();
201                
202                DiagramTextGenerator gen = getDiagramTextGenerator(sb, appendAttributes, appendOperations); 
203                if (gen == null) {
204                        return null;
205                }
206                gen.appendWithRelationships(eObject.getEClassifiers(), relationshipDirection, depth);           
207                return context.get(DiagramGenerator.class).generateUmlDiagram(sb.toString());
208        }
209
210        /**
211         * Override to return a list of sub-types of given EClass. 
212         * This implementation returns all sub-types found in the current package. 
213         * @param eClass
214         * @return
215         */
216        protected Collection<EClass> getSubTypes(EClass eClass) {
217                Collection<EClass> ret = new ArrayList<>();
218                for (EClassifier ec: eObject.getEClassifiers()) {
219                        if (eClass != ec && ec instanceof EClass && eClass.isSuperTypeOf((EClass) ec)) {
220                                ret.add((EClass) ec);
221                        }
222                }
223                return ret;             
224        }       
225                
226        protected Function<EClassifier, String> eClassifierLinkResolver = target -> {
227                String localName = target.getName() + ".html";
228                if (target.getEPackage().getNsURI().equals(eObject.getNsURI())) {
229                        return localName;
230                }
231                
232                StringBuilder pathUp = new StringBuilder();
233                for (EPackage p = eObject; p != null; p = p.getESuperPackage()) {
234                        pathUp.append("../");
235                }
236                
237                return pathUp + encodeEPackage(target.getEPackage()) + "/" + localName;
238        };
239        
240}