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}