001package org.nasdanika.html.ecore; 002 003import java.io.File; 004import java.nio.charset.StandardCharsets; 005import java.util.ArrayList; 006import java.util.HashSet; 007import java.util.List; 008import java.util.Map; 009import java.util.Set; 010import java.util.concurrent.ConcurrentHashMap; 011 012import org.apache.commons.codec.binary.Hex; 013import org.eclipse.emf.codegen.ecore.genmodel.GenModel; 014import org.eclipse.emf.codegen.ecore.genmodel.GenPackage; 015import org.eclipse.emf.common.util.URI; 016import org.eclipse.emf.ecore.EObject; 017import org.eclipse.emf.ecore.EPackage; 018import org.eclipse.emf.ecore.resource.Resource; 019import org.nasdanika.cli.CommandBase; 020import org.nasdanika.cli.ProgressMonitorMixin; 021import org.nasdanika.common.Context; 022import org.nasdanika.common.MutableContext; 023import org.nasdanika.common.ProgressMonitor; 024import org.nasdanika.common.Util; 025import org.nasdanika.emf.EObjectAdaptable; 026import org.nasdanika.html.model.app.util.ActionSupplier; 027 028import picocli.CommandLine.Command; 029import picocli.CommandLine.Mixin; 030import picocli.CommandLine.Option; 031import picocli.CommandLine.Parameters; 032 033@Command( 034 description = "Generates Ecore model documentation as an action model", 035 name = "ecore") 036public class EcoreDocumentationGeneratorCommand extends CommandBase { 037 038 static final String JAVADOC_CONTEXT_BUILDER_MOUNT = "javadoc-context-builder-mount"; 039 040 @Parameters( 041 paramLabel = "URI", 042 arity = "1..*", 043 description = "A list of gen-model URI's to generate documentation for. " 044 + "For each URI a YAML actions file is generated named after the root package in the model. " 045 + "If there are duplicate package names then argument index is added to de-dup the names. ") 046 protected List<String> URIs = new ArrayList<>(); 047 048 @Mixin 049 private ProgressMonitorMixin progressMonitorMixin; 050 051 @Option(names = {"-o", "--output"}, description = "Output directory, defaults to the current directory.") 052 private File outputDir; 053 054 @Option(names = {"-b", "--base-uri"}, description = "Base URI for resolving eclasifier references in diagram image maps. Resolved against the output directory URI. Defaults to the output directory URI.") 055 private String baseUri; 056 057 @Option( 058 names = {"-J", "--javadoc-context-builder-mount"}, 059 arity = "0..1", 060 fallbackValue = "javadoc/", 061 description = "If specified model instance class names are output as tokens for expansion to links to JavaDoc. If specified without parameter option value is ${FALLBACK-VALUE}") 062 private String javaDocContextBuilderMount; 063 064 @Option( 065 names = {"-n", "--name"}, 066 description = "Use EPackage name in paths and URL's instead of encoded NS URI. Duplicate names are de-duplicated by adding -<number> suffix") 067 private boolean useEPackageNameInPath; 068 069 // TODO - localizations - enum as they become available. 070 071 protected GenModelResourceSet resourceSet; 072 // protected ResourceLocator resourceLocator; 073 074 /** 075 * Map of {@link EPackage} path to NS URI. 076 */ 077 private Map<String,String> pathMap = new ConcurrentHashMap<>(); 078 079 private String getEPackagePath(EPackage ePackage) { 080 for (int i = 0; i < Integer.MAX_VALUE; ++i) { 081 String path = i == 0 ? ePackage.getName() : ePackage.getName() + "_" + i; 082 if (pathMap.containsKey(path)) { 083 if (ePackage.getNsURI().equals(pathMap.get(path))) { 084 return path; 085 } 086 } else { 087 pathMap.put(path, ePackage.getNsURI()); 088 return path; 089 } 090 } 091 092 // Encoding NS URI as HEX. Shall never reach this point. 093 return Hex.encodeHexString(ePackage.getNsURI().getBytes(StandardCharsets.UTF_8)); 094 } 095 096 /** 097 * Override to customize the adapter factory. 098 * @return 099 */ 100 protected EcoreActionSupplierAdapterFactory createAdapterFactory() { 101 if (outputDir == null) { 102 outputDir = new File("."); 103 } 104 URI outputURI = URI.createFileURI(outputDir.getAbsolutePath() + File.separator); 105 URI baseURI = Util.isBlank(baseUri) ? outputURI : URI.createURI(baseUri).resolve(outputURI); 106 MutableContext context = Context.EMPTY_CONTEXT.fork(); 107 context.register(URI.class, baseURI); 108 context.put("base-uri", baseURI); 109 if (!Util.isBlank(javaDocContextBuilderMount)) { 110 context.put(JAVADOC_CONTEXT_BUILDER_MOUNT, javaDocContextBuilderMount); 111 } 112 113 return new EcoreActionSupplierAdapterFactory(context, useEPackageNameInPath ? this::getEPackagePath : null, null); // TODO JavadocResolver - perhaps javadoc URL's options 114 } 115 116 public List<ActionSupplier> loadGenModel() { 117 resourceSet = new GenModelResourceSet(); 118 resourceSet.getAdapterFactories().add(createAdapterFactory()); 119 120 List<Resource> resources = new ArrayList<>(); 121 for (String uri: URIs) { 122 Resource genModel = resourceSet.getResource(URI.createURI(uri), true); 123 if (genModel == null) { 124 throw new IllegalArgumentException("Gen model not found: " + uri); 125 } 126 resources.add(genModel); 127 } 128 List<ActionSupplier> ret = new ArrayList<>(); 129 for (Resource resource: resources) { 130 for (EObject contents: resource.getContents()) { 131 if (contents instanceof GenModel) { 132 for (GenPackage genPackage: ((GenModel) contents).getGenPackages()) { 133 EPackage ecorePackage = genPackage.getEcorePackage(); 134 ret.add(EObjectAdaptable.adaptTo(ecorePackage, ActionSupplier.class)); 135 } 136 } 137 } 138 } 139 return ret; 140 } 141 142 @Override 143 public Integer call() throws Exception { 144 List<ActionSupplier> suppliers = loadGenModel(); 145 146 Set<String> names = new HashSet<>(); 147 148 try (ProgressMonitor pm = progressMonitorMixin.createProgressMonitor(suppliers.size())) { 149// int pos = 0; 150// List<Object> data = new ArrayList<>(); 151// for (ViewActionStorable storable: storables) { 152// data.add(storable.store(new URL(getURI().toString()), pm)); 153// ++pos; 154// } 155// 156// DumperOptions dumperOptions = new DumperOptions(); 157// dumperOptions.setDefaultFlowStyle(FlowStyle.BLOCK); 158// dumperOptions.setIndent(4); 159// Yaml yaml = new Yaml(dumperOptions); 160// yaml.dump(data.size() == 1 ? data.get(0) : data, new OutputStreamWriter(outputStream)); 161 } 162 return 0; 163 } 164 165} 166