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