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}