All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jsweet.transpiler.candy.CandyProcessor Maven / Gradle / Ivy

The newest version!
/* 
 * JSweet transpiler - http://www.jsweet.org
 * Copyright (C) 2015 CINCHEO SAS 
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *  
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package org.jsweet.transpiler.candy;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.jsweet.JSweetConfig;
import org.jsweet.transpiler.JSweetProblem;
import org.jsweet.transpiler.TranspilationHandler;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

/**
 * The candy processor extracts and processes what is required from the candy
 * jars. It can include:
 * 
 * 
    *
  • embedded TypeScript definition files (*.d.ts)
  • *
  • embedded JavaScript
  • *
* * @author Louis Grignon */ public class CandyProcessor { private static final Logger logger = Logger.getLogger(CandyProcessor.class); private final static Gson gson = new GsonBuilder().setPrettyPrinting().create(); private String classPath; /** * The name of the directory that will contain the candies. */ public static final String CANDIES_DIR_NAME = "candies"; /** * This directory will contain the sources. */ public static final String CANDIES_SOURCES_DIR_NAME = CANDIES_DIR_NAME + File.separator + "src"; /** * The name of the file that stores processed candies info. */ public static final String CANDIES_STORE_FILE_NAME = CANDIES_DIR_NAME + File.separator + CandyStore.class.getSimpleName() + ".json"; /** * The name of the directory that contains the TypeScript source files. */ public static final String CANDIES_TSDEFS_DIR_NAME = CANDIES_DIR_NAME + File.separator + JSweetConfig.TS_LIBS_DIR_NAME; /** * Default directory for extracted candies' javascript. */ private static final String CANDIES_DEFAULT_JS_DIR_NAME = CANDIES_DIR_NAME + File.separator + "js"; private File candiesSourceDir; private File candyStoreFile; private File candiesTsdefsDir; private File candiesJavascriptOutDir; private File workingDir; private Map> extractedJsFilesByCandy = new HashMap<>(); private File tsOutputDir; /** * Create a candies processor. * * @param workingDir the directory where the processor will * save all cache and temporary data for * processing * @param tsOutputDir directory where TS files will be * generated by transpiler. It is used for * ES6 modules support (a node_module dir * will be generated in this dir) * @param classPath the classpath where the processor will * seek for JSweet candies * @param extractedCandiesJavascriptDir see * JSweetTranspiler.extractedCandyJavascriptDir */ public CandyProcessor(File workingDir, File tsOutputDir, String classPath, File extractedCandiesJavascriptDir) { this.workingDir = workingDir; this.tsOutputDir = tsOutputDir; this.classPath = (classPath == null ? System.getProperty("java.class.path") : classPath); String[] cp = this.classPath.split(File.pathSeparator); int[] indices = new int[0]; for (int i = 0; i < cp.length; i++) { if (cp[i].replace('\\', '/').matches(".*org/jsweet/lib/.*-testbundle/.*/.*-testbundle-.*\\.jar")) { logger.warn("candies processor ignores classpath entry: " + cp[i]); indices = ArrayUtils.add(indices, i); } } cp = ArrayUtils.removeAll(cp, indices); this.classPath = StringUtils.join(cp, File.pathSeparator); logger.info("candies processor classpath: " + this.classPath); candiesSourceDir = new File(workingDir, CANDIES_SOURCES_DIR_NAME); candyStoreFile = new File(workingDir, CANDIES_STORE_FILE_NAME); candiesTsdefsDir = new File(workingDir, CANDIES_TSDEFS_DIR_NAME); setCandiesJavascriptOutDir(extractedCandiesJavascriptDir); } private List getCandies() { return getCandiesStore().getCandies(); } private void setCandiesJavascriptOutDir(File extractedCandiesJavascriptDir) { this.candiesJavascriptOutDir = extractedCandiesJavascriptDir; if (this.candiesJavascriptOutDir == null) { logger.info("extracted candies directory is set to default"); this.candiesJavascriptOutDir = new File(workingDir, CANDIES_DEFAULT_JS_DIR_NAME); } logger.info("extracted candies directory: " + extractedCandiesJavascriptDir); this.candiesJavascriptOutDir.mkdirs(); } /** * Returns the directory that contains the orginal TypeScript source code of the * processed (merged) candies. */ public File getCandiesTsdefsDir() { return candiesTsdefsDir; } /** * Does the processing for the candies jars found in the classpath. */ public void processCandies(TranspilationHandler transpilationHandler) throws IOException { CandyStore candiesStore = getCandiesStore(); LinkedHashMap newCandiesDescriptors = getCandiesDescriptorsFromClassPath( transpilationHandler); CandyStore newStore = new CandyStore(new ArrayList<>(newCandiesDescriptors.values())); if (newStore.equals(candiesStore)) { logger.info("candies are up to date"); return; } this.candyStore = newStore; logger.info("candies changed, processing candies: " + this.candyStore); try { extractCandies(newCandiesDescriptors); writeCandyStore(); } catch (Throwable t) { logger.error("cannot generate candies bundle", t); // exit with fatal if no jar ? } } /** * Returns true if the candy store contains the J4TS candy. */ public boolean isUsingJavaRuntime() { if (candyStore == null) { return false; } else { for (CandyDescriptor c : candyStore.getCandies()) { if (c.name != null && c.name.equals("j4ts")) { logger.info("found j4ts Java runtime in classpath"); return true; } } return false; } } private LinkedHashMap getCandiesDescriptorsFromClassPath( TranspilationHandler transpilationHandler) throws IOException { LinkedHashMap jarFilesCollector = new LinkedHashMap<>(); for (String classPathEntry : classPath.split("[" + System.getProperty("path.separator") + "]")) { if (classPathEntry.endsWith(".jar")) { File jarFile = new File(classPathEntry); if (!jarFile.exists()) { // critical warning, candy not found logger.warn("candy jar file not found: " + jarFile, new Exception()); continue; } try (JarFile jarFileHandle = new JarFile(jarFile)) { JarEntry candySpecificEntry = jarFileHandle .getJarEntry("META-INF/maven/" + JSweetConfig.MAVEN_CANDIES_GROUP); JarEntry candySpecificEntry2 = jarFileHandle.getJarEntry("META-INF/candy-metadata.json"); boolean isCandy = candySpecificEntry != null || candySpecificEntry2 != null; if (isCandy) { CandyDescriptor descriptor = CandyDescriptor.fromCandyJar(jarFileHandle, candiesJavascriptOutDir.getAbsolutePath()); checkCandyVersion(descriptor, transpilationHandler); jarFilesCollector.put(jarFile, descriptor); } } } } logger.info(jarFilesCollector.keySet().size() + " candies found in classpath"); return jarFilesCollector; } private String normalizeVersion(String version) { if (version == null) { return null; } String[] v = JSweetConfig.getVersionNumber().split("\\."); if (v.length == 2) { return version; } else if (v.length == 1) { return v[0] + ".0"; } else { return v[0] + "." + v[1]; } } private void checkCandyVersion(CandyDescriptor candy, TranspilationHandler transpilationHandler) { String actualTranspilerVersion = normalizeVersion(JSweetConfig.getVersionNumber().split("-")[0]); String candyTranspilerVersion = normalizeVersion( candy.transpilerVersion == null ? null : candy.transpilerVersion.split("-")[0]); if (candyTranspilerVersion == null || !candyTranspilerVersion.equals(actualTranspilerVersion)) { transpilationHandler.report(JSweetProblem.CANDY_VERSION_DISCREPANCY, null, JSweetProblem.CANDY_VERSION_DISCREPANCY.getMessage(candy.name, candy.version, actualTranspilerVersion, candyTranspilerVersion)); } } private void extractCandies(Map candies) throws IOException { File extractedSourcesDir = candiesSourceDir; File extractedTsDefsDir = candiesTsdefsDir; FileUtils.deleteQuietly(extractedSourcesDir); FileUtils.deleteQuietly(extractedTsDefsDir); extractedSourcesDir.mkdirs(); extractedTsDefsDir.mkdirs(); for (Map.Entry candy : candies.entrySet()) { CandyDescriptor candyDescriptor = candy.getValue(); File jarFile = candy.getKey(); String candyName = candyDescriptor.name; boolean isCore = "jsweet-core".equals(candyName); try (JarFile jarFileHandle = new JarFile(jarFile)) { String candyJarName = FilenameUtils.getBaseName(jarFile.getName()); File candyExtractedSourcesDir = new File(extractedSourcesDir, candyJarName); File candyExtractedJsDir = new File(candiesJavascriptOutDir, candyJarName); extractCandy( // candyDescriptor, // jarFileHandle, // candyExtractedSourcesDir, // extractedTsDefsDir, // candyExtractedJsDir, // isCore ? tsDefName -> false : null); } } } private void extractCandy( // CandyDescriptor descriptor, // JarFile jarFile, // File javaOutputDirectory, // File tsDefOutputDirectory, // File jsOutputDirectory, // Predicate isTsDefToBeExtracted) { logger.info("extract candy: " + jarFile.getName() + " javaOutputDirectory=" + javaOutputDirectory + " tsDefOutputDirectory=" + tsDefOutputDirectory + " jsOutputDir=" + jsOutputDirectory); jarFile.stream() .filter(entry -> entry.getName().endsWith(".d.ts") && (entry.getName().startsWith("src/") || entry.getName().startsWith("META-INF/resources/"))) // .forEach(entry -> { File out; if (entry.getName().endsWith(".java")) { // RP: this looks like dead code... out = new File(javaOutputDirectory + "/" + entry.getName().substring(4)); } else if (entry.getName().endsWith(".d.ts")) { if (isTsDefToBeExtracted != null && !isTsDefToBeExtracted.test(entry.getName())) { return; } out = new File(tsDefOutputDirectory + "/" + entry.getName()); } else { out = null; } extractEntry(jarFile, entry, out); }); List extractJsFileForCandy = new LinkedList<>(); extractedJsFilesByCandy.put(descriptor.name, extractJsFileForCandy); for (String jsFilePath : descriptor.jsFilesPaths) { JarEntry entry = jarFile.getJarEntry(jsFilePath); String relativeJsPath = jsFilePath.substring(descriptor.jsDirPath.length()); File out = new File(jsOutputDirectory, relativeJsPath); extractEntry(jarFile, entry, out); extractJsFileForCandy.add(out); } } private void extractEntry(JarFile jarFile, JarEntry entry, File out) { if (out == null) { return; } out.getParentFile().mkdirs(); try { FileUtils.copyInputStreamToFile(jarFile.getInputStream(entry), out); } catch (Exception e) { throw new RuntimeException(e); } } private CandyStore candyStore; /** * Cleans the candies store so that it will be read from file next time. */ public void touch() { candyStore = null; } private CandyStore getCandiesStore() { if (candyStore == null) { if (candyStoreFile.exists()) { try { candyStore = gson.fromJson(FileUtils.readFileToString(candyStoreFile), CandyStore.class); } catch (Exception e) { logger.error("cannot read candies index", e); } } if (candyStore == null) { candyStore = new CandyStore(); } } return candyStore; } private void writeCandyStore() { if (candyStore != null) { try { FileUtils.write(candyStoreFile, gson.toJson(candyStore)); } catch (Exception e) { logger.error("cannot read candies index", e); } } } /** * Checks if the candy store contains a deprecated candy. */ public boolean hasDeprecatedCandy() { for (CandyDescriptor candy : getCandies()) { if (candy.transpilerVersion.startsWith("1")) { return true; } } return false; } public List getExtractedJsFiles() { List extractedJsFiles = new LinkedList<>(); for (List extractedFilesForCandy : getExtractedJsFilesByCandy().values()) { extractedJsFiles.addAll(extractedFilesForCandy); } return extractedJsFiles; } public Map> getExtractedJsFilesByCandy() { return extractedJsFilesByCandy; } public void setTsOutputDir(File tsOutputDir) { this.tsOutputDir = tsOutputDir; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy