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}