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