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                
070                String diagramMode = NcoreUtil.getNasdanikaAnnotationDetail(eObject, "diagram", "navigation");
071                String diagram = generateDiagram(0, RelationshipDirection.both, true, true);
072                if (!Util.isBlank(diagram)) {
073                        switch (diagramMode) {
074                        case "content":
075                                addContent(action, diagram);
076                                break;
077                        case "none":
078                                break;
079                        case "navigation": {
080                                Action diagramAction = AppFactory.eINSTANCE.createAction();
081                                action.getNavigation().add(diagramAction);
082                                diagramAction.setText("Diagram");
083                                diagramAction.setIcon("fas fa-project-diagram");
084                                diagramAction.setLocation("package-summary-diagram.html");
085                                addContent(diagramAction, diagram);
086                                break;
087                        }
088                        case "anonymous": {
089                                Action diagramAction = AppFactory.eINSTANCE.createAction();
090                                action.getAnonymous().add(diagramAction);
091                                diagramAction.setText("Diagram");
092                                diagramAction.setIcon("fas fa-project-diagram");
093                                diagramAction.setLocation(ePackageFolder + "/package-summary-diagram.html");
094                                addContent(diagramAction, diagram);
095                                break;                  
096                        }
097                        default:
098                                throw new IllegalArgumentException("Unsupported diagram annotation value '" + diagramMode +"' on EPackage " + eObject);                         
099                        }
100                }
101                
102                // TODO - Table (list) of contents
103//              addContent(data, Collections.singletonMap("component-list-of-contents", Collections.singletonMap("tooltip", true))); 
104                
105                EList<EObject> children = action.getChildren();
106                for (EPackage subPackage: eObject.getESubpackages().stream().filter(elementPredicate).sorted((a,b) ->  a.getName().compareTo(b.getName())).collect(Collectors.toList())) {
107                        children.add(adaptChild(subPackage).execute(contextEClass, progressMonitor));
108                }
109        
110                for (EClassifier eClassifier: eObject.getEClassifiers().stream().filter(elementPredicate).sorted((a,b) ->  a.getName().compareTo(b.getName())).collect(Collectors.toList())) {
111                        Action eClassifierAction = adaptChild(eClassifier).execute(contextEClass, progressMonitor);
112                        EReference eClassifierRole = eClassifierRoleProvider.apply(eClassifier);
113                        ((Collection<Object>) action.eGet(eClassifierRole)).add(eClassifierAction);
114                }
115                
116                return action;
117        }
118        
119        protected DiagramTextGenerator getDiagramTextGenerator(StringBuilder sb, boolean appendAttributes, boolean appendOperations) {
120                String dialect = diagramDialectSupplier.get();
121                if (Util.isBlank(dialect)) {
122                        return null;
123                }
124                switch (dialect) {
125                case DiagramGenerator.UML_DIALECT:
126                        return new PlantUmlTextGenerator(sb, elementPredicate, eClassifierLinkResolver, this::getEModelElementFirstDocSentence, labelProvider) {
127                                
128                                @Override
129                                protected Collection<EClass> getSubTypes(EClass eClass) {
130                                        return EPackageActionSupplier.this.getSubTypes(eClass);
131                                }
132                                
133                                @Override
134                                protected Collection<EClass> getReferrers(EClass eClass) {
135                                        return EPackageActionSupplier.this.getReferrers(eClass);
136                                }
137                                
138                                @Override
139                                protected boolean isAppendAttributes(EClass eClass) {
140                                        return appendAttributes;
141                                }
142                                
143                                @Override
144                                protected boolean isAppendOperations(EClass eClass) {
145                                        return appendOperations;                                
146                                }
147                                
148                                @Override
149                                protected Collection<EClass> getUses(EClassifier eClassifier) {
150                                        return Collections.emptySet(); // No usage information on package diagrams - too much.
151                                }
152                                                                        
153                        };
154                case DiagramGenerator.MERMAID_DIALECT:
155                        return new MermaidTextGenerator(sb, elementPredicate, eClassifierLinkResolver, this::getEModelElementFirstDocSentence) {
156                                
157                                @Override
158                                protected Collection<EClass> getSubTypes(EClass eClass) {
159                                        return EPackageActionSupplier.this.getSubTypes(eClass);
160                                }
161                                
162                                @Override
163                                protected Collection<EClass> getReferrers(EClass eClass) {
164                                        return EPackageActionSupplier.this.getReferrers(eClass);
165                                }
166                                
167                                @Override
168                                protected boolean isAppendAttributes(EClass eClass) {
169                                        return appendAttributes;
170                                }
171                                
172                                @Override
173                                protected boolean isAppendOperations(EClass eClass) {
174                                        return appendOperations;                                
175                                }
176                                
177                                @Override
178                                protected Collection<EClass> getUses(EClassifier eClassifier) {
179                                        return Collections.emptySet(); // No usage information on package diagrams - too much.
180                                }
181                                                                        
182                        };
183                default:
184                        throw new UnsupportedOperationException("Unsupported dialect: " + dialect);
185                }                                       
186        }
187        
188        /**
189         * Generates PNG diagram.
190         * @return Inline PNG and the image map.
191         * @throws IOException 
192         */
193        protected String generateDiagram(
194                        int depth, 
195                        PlantUmlTextGenerator.RelationshipDirection relationshipDirection,
196                        boolean appendAttributes,
197                        boolean appendOperations) {
198                
199                StringBuilder sb = new StringBuilder();
200                
201                DiagramTextGenerator gen = getDiagramTextGenerator(sb, appendAttributes, appendOperations); 
202                if (gen == null) {
203                        return null;
204                }
205                gen.appendWithRelationships(eObject.getEClassifiers(), relationshipDirection, depth);           
206                return context.get(DiagramGenerator.class).generateUmlDiagram(sb.toString());
207        }
208
209        /**
210         * Override to return a list of sub-types of given EClass. 
211         * This implementation returns all sub-types found in the current package. 
212         * @param eClass
213         * @return
214         */
215        protected Collection<EClass> getSubTypes(EClass eClass) {
216                Collection<EClass> ret = new ArrayList<>();
217                for (EClassifier ec: eObject.getEClassifiers()) {
218                        if (eClass != ec && ec instanceof EClass && eClass.isSuperTypeOf((EClass) ec)) {
219                                ret.add((EClass) ec);
220                        }
221                }
222                return ret;             
223        }       
224                
225        protected Function<EClassifier, String> eClassifierLinkResolver = target -> {
226                String localName = target.getName() + ".html";
227                if (target.getEPackage().getNsURI().equals(eObject.getNsURI())) {
228                        return localName;
229                }
230                
231                StringBuilder pathUp = new StringBuilder();
232                for (EPackage p = eObject; p != null; p = p.getESuperPackage()) {
233                        pathUp.append("../");
234                }
235                
236                return pathUp + encodeEPackage(target.getEPackage()) + "/" + localName;
237        };
238        
239}