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