/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library 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 Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.build.packager; import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; import java.io.FileReader; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.jnode.build.BuildException; /** * Build a jnode scripts from both a linux/unix scripts, from msdos scripts or, if none is found, * from scratch (with the help of {@link MainFinder}). * * @author fabien * */ public class ScriptBuilder extends PackagerTask { private static final String SET_COMMAND = "propset "; private static final String JAVA = "java "; private static final String DEFINE_SYS_PROPERTY = "-D"; /** * jnode script extension. */ private static final String JNODE_SCRIPT = ".jns"; /** * Main method : search for existing (unix/linux, msdos) scripts and build jnode scripts * for launching the application given by its root directory. * * @param applicationDir * @param properties */ public static void build(File applicationDir, Properties properties) { List<Command> commands = new ArrayList<Command>(); try { // build from unix scripts if (!buildFromScripts(applicationDir, ".sh", "#", ":", commands)) { // build from msdos batches if (!buildFromScripts(applicationDir, ".bat", "REM", ";", commands)) { // build from scratch by searching for main classes in jars buildFromScratch(applicationDir, commands); } } boolean overwriteScripts = "true".equals(properties.getProperty(FORCE_OVERWRITE_SCRIPTS)); for (Command cmd : commands) { final File file = cmd.getScriptFile(); if (!file.exists() || overwriteScripts) { build(applicationDir, cmd, file); } } } catch (Throwable t) { throw new BuildException("failed to build scripts", t); } } /** * Build a jnode script for the given {@link Command} and application directory. * The generated script will be written in the given file. * * @param applicationDir * @param cmd * @param file * @throws IOException */ private static void build(File applicationDir, Command cmd, File file) throws IOException { //FIXME shouldn't be hard coded but use java.home system property instead // also see class AutoUnzipPlugin, which has the same problem. // In our case, we might use something like ${java.home} // but that feature is not yet support by the shell final File jnodeHome = new File("/jnode/"); FileWriter fw = null; boolean success = false; try { fw = new FileWriter(file); fw.write("# File automatically generated by JNode Packager\n"); fw.write("\n"); fw.write("# enable exception tracing\n"); fw.write(SET_COMMAND + "jnode.debug true\n"); fw.write("\n"); fw.write("# set system properties\n"); final Map<String, String> properties = cmd.getSystemProperties(); for (String name : properties.keySet()) { fw.write(SET_COMMAND + name + " " + properties.get(name) + "\n"); } fw.write("\n"); fw.write("# set classpath\n"); final String prefix = "classpath --add file://" + jnodeHome + "/" + cmd.getApplicationName() + "/"; for (String jarPath : cmd.getClasspath()) { fw.write(prefix + jarPath + "\n"); } fw.write("\n"); fw.write("# TODO : add permissions\n"); fw.write("\n"); fw.write("# launch the application\n"); fw.write("java " + cmd.getMainClass()); for (String arg : cmd.getArguments()) { fw.write(" " + arg); } fw.write("\n\n"); success = true; } finally { if (fw != null) { fw.close(); } if (!success) { // in case of failure, delete the incomplete script file.delete(); } } } /** * * @param applicationDir * @param commands * @throws IOException */ private static void buildFromScratch(File applicationDir, List<Command> commands) throws IOException { final List<String> jars = new ArrayList<String>(); final List<String> mains = new ArrayList<String>(); final int rootLength = (applicationDir.getAbsolutePath() + File.separator).length(); searchJars(rootLength, applicationDir, jars, mains); for (String main : mains) { int idx = main.lastIndexOf('.') + 1; File file = new File(applicationDir, main.substring(idx) + JNODE_SCRIPT); Command cmd = new Command(main, file); for (String jar : jars) { cmd.addToClasspath(jar); } addCommand(commands, cmd, applicationDir); } } private static void searchJars(int rootLength, File currentDir, List<String> jars, List<String> mains) throws IOException { File[] files = currentDir.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.getName().endsWith(".jar") || pathname.isDirectory(); } }); for (File file : files) { if (file.isDirectory()) { // recursively search in sub directory searchJars(rootLength, file, jars, mains); } else { // add the jar and its main classes String relativePath = file.getAbsolutePath().substring(rootLength); jars.add(relativePath); mains.addAll(MainFinder.searchMain(file)); } } } /** * Search for scripts with given extension, which uses the given comment, pathSeparator conventions * (depending on provided parameters, it can be msdos, linux/unix conventions or anything else). * Each found script, will be scanned for a "java" command line and will be used to build a new * {@link Command} that will be added to the provided list. * * @param applicationDir directory of the application * @param extension script's extension * @param comment {@link String} used for comments in the script (example for unix/linux : "#") * @param pathSeparator path separator for the classpath (example for unix/linux : ":") * @param commands list in which new commands will be added * @return * @throws IOException */ private static boolean buildFromScripts(File applicationDir, final String extension, String comment, String pathSeparator, List<Command> commands) throws IOException { File[] scripts = applicationDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(extension); } }); for (File script : scripts) { buildFromScript(applicationDir, script, extension, comment, pathSeparator, commands); } return (scripts.length > 0); } /** * Build a command from the given script file and add it to the provided list. * * @param applicationDir * @param script * @param extension * @param comment * @param pathSeparator * @param commands * @throws IOException */ private static void buildFromScript(File applicationDir, File script, String extension, String comment, String pathSeparator, List<Command> commands) throws IOException { String line; FileReader fr = null; BufferedReader br = null; try { fr = new FileReader(script); br = new BufferedReader(fr); while ((line = br.readLine()) != null) { line = line.trim(); if (!line.isEmpty() && !line.startsWith(comment)) { int idx = line.indexOf(JAVA); if (idx >= 0) { line = line.substring(idx + JAVA.length()); String[] tokens = line.split(" "); // build the Command from the line Command cmd = buildCommand(script, extension, pathSeparator, tokens); addCommand(commands, cmd, applicationDir); break; } } } } finally { if (br != null) { br.close(); } if (fr != null) { fr.close(); } } } /** * Complete the command fill in and do additional checks regarding existing commands * in the list. * * @param commands * @param cmd * @param applicationDir */ private static void addCommand(List<Command> commands, Command cmd, File applicationDir) { cmd.setApplicationName(applicationDir.getName()); commands.add(cmd); } /** * Build a new command from the given parameters. * @param script * @param extension * @param pathSeparator * @param tokens * @return */ private static Command buildCommand(File script, String extension, String pathSeparator, String[] tokens) { Command cmd = new Command(); for (int i = 0; i < tokens.length; i++) { String token = tokens[i]; if (token.startsWith("-")) { if ("-cp".equals(token) || "-classpath".equals(token)) { token = tokens[++i]; // split the classpath for (String path : token.split(pathSeparator)) { cmd.addToClasspath(path); } } else if (token.startsWith(DEFINE_SYS_PROPERTY)) { int beginName = DEFINE_SYS_PROPERTY.length(); int equalPosition = token.indexOf("=", beginName + 1); cmd.addSystemProperty(token.substring(beginName, equalPosition), token.substring(equalPosition + 1)); } } else { cmd.setMainClass(token); // add command arguments for (int j = (i + 1); j < tokens.length; j++) { cmd.addArgument(tokens[j]); } String path = script.getAbsolutePath(); path = path.substring(0, path.length() - extension.length()); path += JNODE_SCRIPT; cmd.setScriptFile(new File(path)); break; } } return cmd; } /** * That class is used to represent a "java" command in a script. * It contains what's needed to build a classpath, define system properties * and launch the main class. * * @author fabien * */ private static class Command { private final List<String> classpath = new ArrayList<String>(); private final Map<String, String> systemProperties = new HashMap<String, String>(); private final List<String> arguments = new ArrayList<String>(); private String applicationName; private String mainClass; private File scriptFile; public Command() { } public Command(String mainClass, File scriptFile) { this.mainClass = mainClass; this.scriptFile = scriptFile; } public void setScriptFile(File scriptFile) { this.scriptFile = scriptFile; } public File getScriptFile() { return scriptFile; } public void addArgument(String arg) { arguments.add(arg); } public List<String> getArguments() { return arguments; } public void addSystemProperty(String name, String value) { systemProperties.put(name, value); } public Map<String, String> getSystemProperties() { return systemProperties; } public void setMainClass(String mainClass) { this.mainClass = mainClass; } public void addToClasspath(String path) { classpath.add(path); } public String getMainClass() { return mainClass; } public List<String> getClasspath() { return classpath; } public String getApplicationName() { return applicationName; } public void setApplicationName(String applicationName) { this.applicationName = applicationName; } } }