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}