<commit_before>/*
 * Copyright 2011 Red Hat inc. and third party contributors as noted
 * by the author tags.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.redhat.ceylon.cmr.impl;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

import net.minidev.json.JSONValue;

import com.redhat.ceylon.cmr.api.AbstractDependencyResolver;
import com.redhat.ceylon.cmr.api.ArtifactContext;
import com.redhat.ceylon.cmr.api.DependencyContext;
import com.redhat.ceylon.cmr.api.ModuleDependencyInfo;
import com.redhat.ceylon.cmr.api.ModuleInfo;
import com.redhat.ceylon.cmr.api.ModuleVersionArtifact;
import com.redhat.ceylon.cmr.api.ModuleVersionDetails;
import com.redhat.ceylon.cmr.api.Overrides;
import com.redhat.ceylon.cmr.spi.Node;
import com.redhat.ceylon.common.ModuleUtil;
import com.redhat.ceylon.model.cmr.ArtifactResult;

/**
 * Utility functions to retrieve module meta information from compiled JS modules
 *
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 */
public final class JSUtils extends AbstractDependencyResolver implements ModuleInfoReader {
    public static JSUtils INSTANCE = new JSUtils();

    private JSUtils() {
    }

    @Override
    public ModuleInfo resolve(DependencyContext context, Overrides overrides) {
        if (context.ignoreInner()) {
            return null;
        }

        ArtifactResult result = context.result();
        File mod = result.artifact();
        if (mod != null && (mod.getName().toLowerCase().endsWith(ArtifactContext.JS_MODEL)
                || mod.getName().toLowerCase().endsWith(ArtifactContext.JS))) {
            return readModuleInformation(result.name(), mod, overrides);
        } else {
            return null;
        }
    }
    
    @Override
    public ModuleInfo resolveFromFile(File file, String name, String version, Overrides overrides) {
        throw new UnsupportedOperationException("Operation not supported for .js files");
    }

    @Override
    public ModuleInfo resolveFromInputStream(InputStream stream, String name, String version, Overrides overrides) {
        throw new UnsupportedOperationException("Operation not supported for .js files");
    }

    public Node descriptor(Node artifact) {
        return null; // artifact is a descriptor
    }

    /**
     * Read module info from JS file
     *
     * @param moduleName the module name
     * @param jarFile    the module JS file
     * @return module info list
     */
    public static ModuleInfo readModuleInformation(final String moduleName, final File jarFile, Overrides overrides) {
        Map<String, Object> model = loadJsonModel(jarFile);
        String version = asString(metaModelProperty(model, "$mod-version"));
        return getModuleInfo(model, moduleName, version, overrides);
    }
    
    @Override
    public int[] getBinaryVersions(String moduleName, String version, File moduleArchive) {
        int major = 0;
        int minor = 0;
        ModuleVersionDetails mvd = readModuleInfo(moduleName, version, moduleArchive, false, null);
        ModuleVersionArtifact mva = mvd.getArtifactTypes().first();
        if (mva.getMajorBinaryVersion() != null) {
            major = mva.getMajorBinaryVersion();
        }
        if (mva.getMinorBinaryVersion() != null) {
            minor = mva.getMinorBinaryVersion();
        }
        
        return new int[]{major, minor};
    }

    private static ScriptEngine getEngine(File moduleArchive) {
        ScriptEngine engine;
        try {
            engine = new ScriptEngineManager().getEngineByName("JavaScript");
            engine.eval("var exports={}");
            engine.eval("var module={}");
            engine.eval("function require() { return { '$addmod$' : function() {} } }");
            engine.eval(new FileReader(moduleArchive));
            return engine;
        } catch (Exception ex) {
            throw new RuntimeException("Failed to parse module JS file", ex);
        }
    }

    private static ModuleInfo getModuleInfo(Map<String,Object> model, String module, String version, Overrides overrides) {
        try {
            return getModuleInfo(metaModelProperty(model, "$mod-deps"), module, version, overrides);
        } catch (Exception ex) {
            throw new RuntimeException("Failed to parse module JS file", ex);
        }
    }
    
    @Override
    public ModuleVersionDetails readModuleInfo(String moduleName, String moduleVersion, File moduleArchive, boolean includeMembers, Overrides overrides) {
        Map<String, Object> model = loadJsonModel(moduleArchive);

        String name = asString(metaModelProperty(model, "$mod-name"));
        if (!moduleName.equals(name)) {
            throw new RuntimeException("Incorrect module");
        }
        String version = asString(metaModelProperty(model, "$mod-version"));
        Set<ModuleDependencyInfo> dependencies = getModuleInfo(model, moduleName, version, overrides).getDependencies();
        
        String type = ArtifactContext.getSuffixFromFilename(moduleArchive.getName());

        Integer major = null, minor = null;
        String bin = asString(metaModelProperty(model, "$mod-bin"));
        if (bin != null) {
            int p = bin.indexOf('.');
            if (p >= 0) {
                major = Integer.parseInt(bin.substring(0, p));
                minor = Integer.parseInt(bin.substring(p + 1));
            } else {
                major = Integer.parseInt(bin);
            }
        }
        ModuleVersionDetails mvd = new ModuleVersionDetails(moduleName, version);
        mvd.getArtifactTypes().add(new ModuleVersionArtifact(type, major, minor));
        mvd.getDependencies().addAll(dependencies);
        
        if (includeMembers) {
            mvd.setMembers(getMembers(moduleName, moduleArchive));
        }
        
        return mvd;
    }

    private Set<String> getMembers(String moduleName, File moduleArchive) {
        // TODO Implement this!
        throw new RuntimeException("Not implemented yet");
    }
    
    private static Object metaModelProperty(Map<String,Object> model, String propName) {
        return model.get(propName);
    }
    
    private static String asString(Object obj) {
        if (obj == null) {
            return null;
        } else {
            return obj.toString();
        }
    }

    private static ModuleInfo getModuleInfo(Object obj, String moduleName, String version, Overrides overrides) {
        if (obj == null) {<commit_msg>JSUtils #128: read doc/by/license info from JS metamodel<commit_after>/*
 * Copyright 2011 Red Hat inc. and third party contributors as noted
 * by the author tags.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.redhat.ceylon.cmr.impl;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

import net.minidev.json.JSONValue;

import com.redhat.ceylon.cmr.api.AbstractDependencyResolver;
import com.redhat.ceylon.cmr.api.ArtifactContext;
import com.redhat.ceylon.cmr.api.DependencyContext;
import com.redhat.ceylon.cmr.api.ModuleDependencyInfo;
import com.redhat.ceylon.cmr.api.ModuleInfo;
import com.redhat.ceylon.cmr.api.ModuleVersionArtifact;
import com.redhat.ceylon.cmr.api.ModuleVersionDetails;
import com.redhat.ceylon.cmr.api.Overrides;
import com.redhat.ceylon.cmr.spi.Node;
import com.redhat.ceylon.common.ModuleUtil;
import com.redhat.ceylon.model.cmr.ArtifactResult;

/**
 * Utility functions to retrieve module meta information from compiled JS modules
 *
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 */
public final class JSUtils extends AbstractDependencyResolver implements ModuleInfoReader {
    public static JSUtils INSTANCE = new JSUtils();

    private JSUtils() {
    }

    @Override
    public ModuleInfo resolve(DependencyContext context, Overrides overrides) {
        if (context.ignoreInner()) {
            return null;
        }

        ArtifactResult result = context.result();
        File mod = result.artifact();
        if (mod != null && (mod.getName().toLowerCase().endsWith(ArtifactContext.JS_MODEL)
                || mod.getName().toLowerCase().endsWith(ArtifactContext.JS))) {
            return readModuleInformation(result.name(), mod, overrides);
        } else {
            return null;
        }
    }
    
    @Override
    public ModuleInfo resolveFromFile(File file, String name, String version, Overrides overrides) {
        throw new UnsupportedOperationException("Operation not supported for .js files");
    }

    @Override
    public ModuleInfo resolveFromInputStream(InputStream stream, String name, String version, Overrides overrides) {
        throw new UnsupportedOperationException("Operation not supported for .js files");
    }

    public Node descriptor(Node artifact) {
        return null; // artifact is a descriptor
    }

    /**
     * Read module info from JS file
     *
     * @param moduleName the module name
     * @param jarFile    the module JS file
     * @return module info list
     */
    public static ModuleInfo readModuleInformation(final String moduleName, final File jarFile, Overrides overrides) {
        Map<String, Object> model = loadJsonModel(jarFile);
        String version = asString(metaModelProperty(model, "$mod-version"));
        return getModuleInfo(model, moduleName, version, overrides);
    }
    
    @Override
    public int[] getBinaryVersions(String moduleName, String version, File moduleArchive) {
        int major = 0;
        int minor = 0;
        ModuleVersionDetails mvd = readModuleInfo(moduleName, version, moduleArchive, false, null);
        ModuleVersionArtifact mva = mvd.getArtifactTypes().first();
        if (mva.getMajorBinaryVersion() != null) {
            major = mva.getMajorBinaryVersion();
        }
        if (mva.getMinorBinaryVersion() != null) {
            minor = mva.getMinorBinaryVersion();
        }
        
        return new int[]{major, minor};
    }

    private static ScriptEngine getEngine(File moduleArchive) {
        ScriptEngine engine;
        try {
            engine = new ScriptEngineManager().getEngineByName("JavaScript");
            engine.eval("var exports={}");
            engine.eval("var module={}");
            engine.eval("function require() { return { '$addmod$' : function() {} } }");
            engine.eval(new FileReader(moduleArchive));
            return engine;
        } catch (Exception ex) {
            throw new RuntimeException("Failed to parse module JS file", ex);
        }
    }

    private static ModuleInfo getModuleInfo(Map<String,Object> model, String module, String version, Overrides overrides) {
        try {
            return getModuleInfo(metaModelProperty(model, "$mod-deps"), module, version, overrides);
        } catch (Exception ex) {
            throw new RuntimeException("Failed to parse module JS file", ex);
        }
    }
    
    @Override
    public ModuleVersionDetails readModuleInfo(String moduleName, String moduleVersion, File moduleArchive, boolean includeMembers, Overrides overrides) {
        Map<String, Object> model = loadJsonModel(moduleArchive);

        String name = asString(metaModelProperty(model, "$mod-name"));
        if (!moduleName.equals(name)) {
            throw new RuntimeException("Incorrect module");
        }
        String version = asString(metaModelProperty(model, "$mod-version"));
        Set<ModuleDependencyInfo> dependencies = getModuleInfo(model, moduleName, version, overrides).getDependencies();
        
        String type = ArtifactContext.getSuffixFromFilename(moduleArchive.getName());

        Integer major = null, minor = null;
        String bin = asString(metaModelProperty(model, "$mod-bin"));
        if (bin != null) {
            int p = bin.indexOf('.');
            if (p >= 0) {
                major = Integer.parseInt(bin.substring(0, p));
                minor = Integer.parseInt(bin.substring(p + 1));
            } else {
                major = Integer.parseInt(bin);
            }
        }
        ModuleVersionDetails mvd = new ModuleVersionDetails(moduleName, version);
        mvd.getArtifactTypes().add(new ModuleVersionArtifact(type, major, minor));
        mvd.getDependencies().addAll(dependencies);

        Map<String,Object> annotations = (Map<String, Object>) metaModelProperty(model, "$mod-anns");
        if(annotations != null){
            
            mvd.setDoc(asString(annotations.get("doc")));
            mvd.setLicense(asString(annotations.get("license")));
            Iterable<String> by = (Iterable<String>) annotations.get("by");
            if(by != null){
                for(String author : by){
                    mvd.getAuthors().add(author);
                }
            }
        }

        if (includeMembers) {
            mvd.setMembers(getMembers(moduleName, moduleArchive));
        }
        
        return mvd;
    }

    private Set<String> getMembers(String moduleName, File moduleArchive) {
        // TODO Implement this!
        throw new RuntimeException("Not implemented yet");
    }
    
    private static Object metaModelProperty(Map<String,Object> model, String propName) {
        return model.get(propName);
    }
    
    private static String asString(Object obj) {
        if (obj == null) {
            return null;
        } else if(obj instanceof Iterable){
            Iterator<String> iter = ((Iterable<String>) obj).iterator();
            return iter.hasNext() ? iter.next() : null;
        } else {
            return obj.toString();
        }
    }

    private static ModuleInfo getModuleInfo(Object obj, String moduleName, String version, Overrides overrides) {
        if (obj == null) {
{'fraction_non_alphanumeric': 0.10207578253706755, 'fraction_numerical': 0.0019110378912685338, 'mean_word_length': 2.8704412139760267, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6644697', 'n_tokens_mistral': 4058, 'n_tokens_neox': 4012, 'n_words': 1105}