/*
* Copyright (c) 2004-2008 XMLVM --- An XML-based Programming Language
*
* 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 2 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., 675 Mass
* Ave, Cambridge, MA 02139, USA.
*
* For more information, visit the XMLVM Home Page at http://www.xmlvm.org
*/
package org.xmlvm;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jdom.Document;
import org.jdom.input.SAXBuilder;
import org.mozilla.javascript.ErrorReporter;
import org.mozilla.javascript.EvaluatorException;
import com.crazilec.util.UtilCopy;
import com.yahoo.platform.yui.compressor.JavaScriptCompressor;
public class XmlvmBuilder {
/**
* Class file pattern.
*/
public static final String CLASS_FILE_PATTERN = "*.class";
/**
* Exe file pattern.
*/
public static final String EXE_FILE_PATTERN = "*.exe";
/**
* Temporary subdirectory in output directory which is used to assemble the
* final output using qooxdoo.
*/
public static final String TEMP_CACHE_SUBDIR = "temp_cache";
/**
* The path to the qooxdoo distribution.
*/
public static final String QX_PATH = System.getenv("XMLVM_QOOXDOO_PATH");
/**
* The name of the script that is used to build the application.
*/
public static final String QX_GENERATOR_SCRIPT_NAME = "generate.py";
/**
* The absolute path to the script that is used to create the qooxdoo
* application skeleton.
*/
public static final String QX_CREATOR_SCRIPT = QX_PATH
+ "/tool/bin/create-application.py";
/**
* The name of the temporary qooxdoo app that is used during the process.
*/
public static final String QX_TEMP_APP_NAME = "temp_qx_app";
/**
* The path to the XMLVM emulation library.
*/
public static final String JS_EMULATION_LIB_PATH = "./src/xmlvm2js";
public static final String APPLICATION_JS_PATH = JS_EMULATION_LIB_PATH
+ "/Application.js.template";
protected String applicationName;
// protected String indexFileName;
protected String mainClass;
/**
* This is where the final compilation will be put in.
*/
protected String destination;
/**
* This is where the temporary computation output is put. The directory will
* be deleted after the process is done.
*/
protected String tempDestination;
protected List<LocationEntry> javaClasspaths = new ArrayList<LocationEntry>();
protected List<LocationEntry> exePaths = new ArrayList<LocationEntry>();
protected List<LocationEntry> javaScriptResources = new ArrayList<LocationEntry>();
protected List<LocationEntry> resources = new ArrayList<LocationEntry>();
protected String mainMethod;
// TODO(haeberling): Will go away.
protected boolean createAssembly = false;
protected boolean qxSourceBuild = false;
public static void main(String[] args) {
XmlvmBuilder builder = new XmlvmBuilder(new XmlvmBuilderArguments(args));
try {
builder.newBuild();
System.out.println("Build successful!");
} catch (XmlvmBuilderException e) {
e.printStackTrace();
}
}
public XmlvmBuilder(XmlvmBuilderArguments args) {
destination = args.option_destination();
tempDestination = destination + File.separator + TEMP_CACHE_SUBDIR;
// TODO(shaeberling): Support multiple entries & check for correctness
String classpath = args.option_classpath();
// TODO(shaeberling): Support multiple entries & check for correctness
String exepath = args.option_exepath();
// TODO(shaeberling): Support multiple entries & check for correctness
String resources = args.option_includeresource();
mainMethod = args.option_main();
qxSourceBuild = args.option_qxsourcebuild();
System.out.println(" * Destination : " + destination);
System.out.println(" * Classpath : " + classpath);
System.out.println(" * Exepath : " + exepath);
System.out.println(" * Resources : " + resources);
System.out.println(" * Main Method : " + mainMethod);
// System.out.println(" * Compress Assembly : " + compress);
System.out.println("-------------------------");
System.out.println(" * XMLVM_QOOXDOO_PATH : " + QX_PATH);
if (!classpath.equals("")) {
addJavaClasspath(new LocationEntry(classpath));
}
if (!exepath.equals("")) {
addExePath(new LocationEntry(exepath));
}
if (!resources.equals("")) {
addResource(new LocationEntry(resources));
}
System.out.print("\n");
}
/**
* A new build that will eventually replace the old one.
*
* @return Whether the building process was successful.
*/
private void newBuild() throws XmlvmBuilderException {
// This is the path, where the source for the temporary qooxdoo project will
// be allocated.
String tempQxSourcePath = tempDestination + "/" + QX_TEMP_APP_NAME
+ "/source/class";
// STEP 0: Sanity checks the environment.
System.out.println("Sanity checks ... ");
peformSanityChecks();
System.out.println("Sanity checks PASSED");
boolean destinationExists = isDestinationNotEmpty();
if (!destinationExists) {
// STEP 1: Preparation of destination directory.
// TODO(haeberling): If the directory exists, don't remove it completely.
// This will speed up the process. Instead just clear the classes.
System.out.println("> STEP 1/7: Preparation of destination directory: "
+ destination + " ...");
prepareDestination();
// STEP 2: Executing qooxdoo application creator.
System.out
.println("> STEP 2/7: Executing qooxdoo application creator ...");
initQxSkeleton();
} else {
System.out
.println("Skipping Step 1 & 2, as it seems that a valid QX project already exists.");
}
// STEP 3: Compile class and exe files to JavaScript and copy them to
// destination.
System.out.println("> STEP 3/7: Translating class/exe files to JS ...");
compileToJS(javaClasspaths, CLASS_FILE_PATTERN, tempQxSourcePath);
compileToJS(exePaths, EXE_FILE_PATTERN, tempQxSourcePath);
// STEP 4: Copy XMLVM JS compatibility library into temporary directory so
// it can be picked up by qooxdoo.
System.out.println("> STEP 4/7: Copying compatibility library ...");
// The path where qooxdoo expects all source to be in the temporary project.
prepareJsEmulationLibrary(new File(JS_EMULATION_LIB_PATH), new File(
tempQxSourcePath));
// STEP 5: Replace generated Application.js with our own, which will execute
// the main method to start the application.
System.out.println("> STEP 5/7: Inject custom Application.js ...");
injectCustomApplicationJs(tempQxSourcePath);
// STEP 6: Execute Qooxdoo's generator script in order to build the
// application.
System.out.println("> STEP 6/7: Building application ...");
executeGenerator();
// STEP 7: Copy build directory to final output directory.
// TODO(haeberling)
}
private boolean isDestinationNotEmpty() {
File outputDir = new File(destination);
return outputDir.isDirectory() && (outputDir.list().length != 0);
}
private void injectCustomApplicationJs(String jsClassPath)
throws XmlvmBuilderException {
String applicationJs = readFileAsString(new File(APPLICATION_JS_PATH));
// We replace the variables in the template with the requires values.
applicationJs = applicationJs.replace("{{XMLVM_TEMP_PROJECT_NAME}}",
QX_TEMP_APP_NAME);
applicationJs = applicationJs.replace("{{XMLVM_MAIN_METHOD_CALL}}",
generateMainCall());
String filename = jsClassPath + "/" + QX_TEMP_APP_NAME + "/Application.js";
try {
FileWriter writer = new FileWriter(filename);
writer.write(applicationJs);
writer.close();
} catch (IOException e) {
throw new XmlvmBuilderException("Couldn't not write: " + filename, e);
}
}
/**
* Based on the --main argument, the actual main call is generated.
*/
private String generateMainCall() {
String mainClass = mainMethod.substring(0, mainMethod.lastIndexOf('.'));
mainClass = mainClass.replace('.', '_');
if (mainMethod.endsWith("Main")) {
// If the format is <ClassName>.Main we expect this to be a C# main method
// call.
// TODO(haeberling): Is this how it will be called now in CS?
return mainClass + ".$Main();";
} else {
return mainClass + ".$main___java_lang_String_ARRAYTYPE(undefined);";
}
}
/**
* Executed Qooxdoo's generator to build the application.
*
* @throws XmlvmBuilderException
*/
private void executeGenerator() throws XmlvmBuilderException {
String buildType = qxSourceBuild ? " source" : " build";
System.out.println("QX '" + buildType + " '");
try {
Process process = createPythonProcess(tempDestination + "/"
+ QX_TEMP_APP_NAME + "/" + QX_GENERATOR_SCRIPT_NAME + buildType);
printOutputOfProcess(process, "GENERATOR");
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new XmlvmBuilderException(
"Error while executing python. Exit Code: " + exitCode);
}
} catch (IOException e) {
throw new XmlvmBuilderException("Error while executing python.", e);
} catch (InterruptedException e) {
throw new XmlvmBuilderException("Error while executing python.", e);
}
}
/**
* Takes the emulation library and puts it into the destination so it is ready
* to be used by qooxdoo's build system.
*/
private void prepareJsEmulationLibrary(File basePath, File destination)
throws XmlvmBuilderException {
// Check, whether the destination directory actually exists.
if (!destination.isDirectory()) {
throw new XmlvmBuilderException("Destination directory does not exist: "
+ destination.getAbsolutePath());
}
// Recursively rename and copy all JS files.
renameAndCopyJsFiles(basePath, basePath, destination);
}
/**
* Recursively go through all sub-directories and look for JS files. When
* found, rename file to match to internal class-name (required by qooxdoo),
* and copy files into destination directory.
*
* @param absoluteBasePath
* The root of the emulation library.
* @param basePath
* The path where to search for JS files.
* @param destination
* The path where the renamed JS files should be copied to.
*/
private void renameAndCopyJsFiles(File absoluteBasePath, File basePath,
File destination) throws XmlvmBuilderException {
// Accepts files
FileFilter jsFileFilter = new FileFilter() {
public boolean accept(File pathname) {
return !pathname.isDirectory()
&& pathname.getName().toLowerCase().endsWith(".js");
}
};
// Accepts directories
FileFilter directoryFilter = new FileFilter() {
public boolean accept(File pathname) {
return pathname.isDirectory();
}
};
// Go through all files in this directory ...
for (File entry : basePath.listFiles(jsFileFilter)) {
renameAndCopyJsFile(absoluteBasePath, entry, destination);
}
// ... then recursively go through subdirectories.
for (File entry : basePath.listFiles(directoryFilter)) {
renameAndCopyJsFiles(absoluteBasePath, entry, destination);
}
}
/**
* Renames and copies a single JS file. The renaming will integrate the
* pathname from a given root path, so the file name matches the class name
* within the file.
*
* @param absoluteBasePath
* @param jsFile
* @param destination
* @throws XmlvmBuilderException
*/
private void renameAndCopyJsFile(File absoluteBasePath, File jsFile,
File destination) throws XmlvmBuilderException {
// +1 to remove trailing slash.
String cutPath = jsFile.getAbsolutePath().substring(
(int) absoluteBasePath.getAbsolutePath().length() + 1);
String outputFileName = cutPath.replace(File.separatorChar, '_');
try {
UtilCopy uc = new UtilCopy();
uc.binCopy(destination.getAbsolutePath() + File.separator
+ outputFileName, jsFile.getAbsolutePath());
} catch (FileNotFoundException e) {
throw new XmlvmBuilderException("Error while copying file.", e);
} catch (Exception e) {
throw new XmlvmBuilderException("Error while copying file.", e);
}
}
/**
* Performs sanity checks that have to be passed in order for the builder to
* run successfully. This way the application is able to fail early.
*
* @throws XmlvmBuilderException
*/
private void peformSanityChecks() throws XmlvmBuilderException {
// Check whether qooxdoo-path exists.
if (!(new File(QX_PATH)).isDirectory()) {
throw new XmlvmBuilderException("QX directory cannot be found: "
+ QX_PATH);
}
// Check whether creator script is present.
if (!(new File(QX_CREATOR_SCRIPT)).isFile()) {
throw new XmlvmBuilderException("QX creator cannot be found: "
+ QX_CREATOR_SCRIPT);
}
// Check whether Python is present
if (!isPythonPresent()) {
throw new XmlvmBuilderException(
"Python executable cannot be found. Make sure python.exe is on your PATH ");
}
// Check whether JS emulation library is present.
if (!(new File(JS_EMULATION_LIB_PATH)).isDirectory()) {
throw new XmlvmBuilderException("Emulation library cannot be found: "
+ JS_EMULATION_LIB_PATH);
}
// Check whether the custom Application.js template is present
if (!(new File(APPLICATION_JS_PATH)).isFile()) {
throw new XmlvmBuilderException("Custom Application.js file not found: "
+ APPLICATION_JS_PATH);
}
// Check whether mainMethod is defined and has the required format
if (mainMethod.indexOf('.') == -1) {
throw new XmlvmBuilderException(
"--main must be of format: <ClassName>.(main|Main");
}
// If the destination directory exists and has content, we check whether
// there is already a valid QX project. If not, something is wrong.
if (isDestinationNotEmpty()) {
String generateScript = tempDestination + "/" + QX_TEMP_APP_NAME + "/"
+ QX_GENERATOR_SCRIPT_NAME;
File generateScriptFile = new File(generateScript);
if (!generateScriptFile.isFile()) {
throw new XmlvmBuilderException(
"Output directory exists, but doesn't seem to be a valid QX project as the following file could not be found"
+ generateScriptFile.getAbsolutePath());
}
}
}
/**
* Returns whether python can be executed.
*/
private static boolean isPythonPresent() {
String line = "";
try {
Process process;
process = Runtime.getRuntime().exec("python --version");
BufferedReader input = new BufferedReader(new InputStreamReader(process
.getErrorStream()));
line = input.readLine();
process.destroy();
} catch (IOException e) {
return false;
}
if (line != null && line.startsWith("Python")) {
return true;
} else {
return false;
}
}
/**
* Uses the qooxdoo application creator to create a temporary project that is
* used during the building process.
*
* @throws XmlvmBuilderException
*/
private void initQxSkeleton() throws XmlvmBuilderException {
try {
Process process = createPythonProcess(QX_CREATOR_SCRIPT + " --name "
+ QX_TEMP_APP_NAME + " --out " + tempDestination);
printOutputOfProcess(process, "CREATOR");
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new XmlvmBuilderException(
"Error while executing python. Exit Code: " + exitCode);
}
} catch (IOException e) {
throw new XmlvmBuilderException("Error while executing python.", e);
} catch (InterruptedException e) {
throw new XmlvmBuilderException("Error while executing python.", e);
}
}
/**
* Creates a Python process.
*
* @param Arguments
* arguments to the python process.
* @return A process object to monitor.
* @throws IOException
*/
private static Process createPythonProcess(String arguments)
throws IOException {
return Runtime.getRuntime().exec("python " + arguments);
}
/**
* Takes a process and reads it output till the end. The output is prefixed
* with the given line prefix.
*
* @param process
* The process to take the output from.
* @param linePrefix
* The line prefix to mark the output.
* @throws IOException
*/
private static void printOutputOfProcess(final Process process,
final String linePrefix) throws IOException {
InpuReaderThread inputThread = new InpuReaderThread(process
.getInputStream(), System.out, linePrefix);
InpuReaderThread errorThread = new InpuReaderThread(process
.getErrorStream(), System.err, "(ERROR) " + linePrefix);
inputThread.start();
errorThread.start();
}
/**
* Goes through all the given locations, searched for files with the given
* pattern and compiled matching files to JavaScript.
*
* This uses Main to filter files and translate them.
*
* @param locations
* Directories in which the source files are found.
* @param filePattern
* The pattern of the files to be translated.
* @throws XmlvmBuilderException
*/
private void compileToJS(List<LocationEntry> locations, String filePattern,
String compileDestination) throws XmlvmBuilderException {
// For every location...
for (LocationEntry loc : locations) {
// Check if location is actually a directory
if (loc.getType().equals("dir")) {
// Add file separator (directory sign) if it is not trailing the
// location name.
if (!loc.getLocation().endsWith(File.separator)) {
loc.setLocation(loc.getLocation() + File.separator);
}
// Build main arguments for compiling all files with given pattern in
// the given locations to JavaScript.
String mainArgs[] = { "--js", "--out=" + compileDestination,
"--file=" + loc.getLocation() + filePattern };
System.out.print("Exec: ");
for (String i : mainArgs) {
System.out.print(i + " ");
}
System.out.println("\n");
try {
// Actually invoke main.
// TODO(shaeberling): Oh boy, we should make this a proper API!
Main.main(mainArgs);
} catch (Exception ex) {
throw new XmlvmBuilderException("Error while invoking Main.main.", ex);
}
} else {
System.err
.println("ERROR: Only directories are supported as classpaths right now");
continue;
}
}
}
/**
* If destination directory exists, it will be deleted. If not, it will be
* created.
*/
private void prepareDestination() throws XmlvmBuilderException {
// Create destination and temporary destination directories, if they do not
// already exist. If they do exist, remove their contents.
for (String directory : new String[] { destination, tempDestination }) {
File dir = new File(directory);
if (dir.exists()) {
if (!deleteDirectory(dir)) {
throw new XmlvmBuilderException(
"Couldn't clear destination directory");
}
}
dir.mkdirs();
}
}
public boolean build(boolean includeXmlvmBootstrap) {
// Add all files needed by XMLVM to the lists, so that they are
// added to the final build
if (includeXmlvmBootstrap) {
includeXmlvmBootstrap();
}
System.out.println("Creating or clearing " + this.destination);
// Create destination directory, if it does not exist already
// If it does exist, remove its contents
File destinationDir = new File(this.destination);
if (destinationDir.exists()) {
if (!deleteDirectory(new File(this.destination))) {
System.out.println("Couldn't clear destination directory");
}
}
destinationDir.mkdirs();
// STEP 1: - Compile class files to JavaScript and copy them to
// destination
// Java
System.out.println("Translating Java classes...");
for (LocationEntry loc : javaClasspaths) {
if (loc.getType().equals("dir")) {
if (!loc.getLocation().endsWith(File.separator))
loc.setLocation(loc.getLocation() + File.separator);
String mainArgs[] = { "--js", "--out=" + destinationDir,
loc.getLocation() + "*.class" };
System.out.print("Exec: ");
for (String i : mainArgs) {
System.out.print(i + " ");
}
System.out.println("\n");
try {
Main.main(mainArgs);
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
} else {
System.err
.println("ERROR: Only directories are supported as classpaths right now");
continue;
}
}
// .NET
System.out.println("Translating .NET executables...");
for (LocationEntry loc : exePaths) {
if (loc.getType().equals("dir")) {
if (!loc.getLocation().endsWith(File.separator))
loc.setLocation(loc.getLocation() + File.separator);
String mainArgs[] = { "--js", "--out=" + destinationDir,
loc.getLocation() + "*.exe" };
System.out.print("Exec: ");
for (String i : mainArgs) {
System.out.print(i + " ");
}
System.out.println("\n");
try {
Main.main(mainArgs);
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
} else {
System.err
.println("ERROR: Only directories are supported as exepaths right now");
continue;
}
}
// Find library classes written by us.
List<JsFile> libFiles = new ArrayList<JsFile>();
List<File> subDirsToProcess = new ArrayList<File>();
subDirsToProcess.add(new File("./src/xmlvm2js"));
while (subDirsToProcess.size() != 0) {
File cur = subDirsToProcess.get(0);
subDirsToProcess.remove(0);
for (String subFileOrDir : cur.list()) {
File sub = new File(cur.getPath() + "\\" + subFileOrDir);
if (sub.isDirectory()) {
subDirsToProcess.add(sub);
} else {
if (sub.getName().endsWith(".js")) {
// System.out.println("Library: " + sub.getPath());
libFiles.add(new JsFile(sub.getAbsoluteFile()));
}
}
}
}
List<JsFile> userFiles = new ArrayList<JsFile>(); // The files the user
// converted to
// javascript.
subDirsToProcess.add(new File(this.destination));
while (subDirsToProcess.size() != 0) {
File cur = subDirsToProcess.get(0);
subDirsToProcess.remove(0);
for (String subFileOrDir : cur.list()) {
File sub = new File(cur.getPath() + "\\" + subFileOrDir);
if (sub.isDirectory()) {
subDirsToProcess.add(sub);
} else {
if (sub.getName().endsWith(".js")) {
// System.out.println("UserFile: " + sub.getPath());
userFiles.add(new JsFile(sub.getAbsoluteFile()));
}
}
}
}
/*
* for(JsFile userFile:userFiles) { System.out.println(
* userFile.getClassName() + " Depends on load ");
*
* for(String s : userFile.getLoadDependencies()) { System.out.println(s); }
*
* System.out.println( userFile.getClassName() + " Depends on library");
*
* for(String s : userFile.getAllDependencies()) { System.out.println(s); }
* }
*/
// Find what set of the library we need to add
List<JsFile> requiredLib = new ArrayList<JsFile>();
String mainClassName = "";
for (JsFile userFile : userFiles) {
if (userFile.fileContent.indexOf("_Main : function()") != -1) {
System.out.println("Using " + userFile.className + " as main class");
mainClassName = userFile.className;
requiredLib.add(userFile);
}
}
for (int x = 0; x < requiredLib.size(); x++) {
JsFile curClass = requiredLib.get(x);
List<String> allFiles = new ArrayList<String>();
for (String s : curClass.getAllDependencies()) {
allFiles.add(s);
}
for (String s : curClass.getLoadDependencies()) {
allFiles.add(s);
}
// Now for all the classes in our depend list, make sure they are in the
// required lib
for (String needClassName : allFiles) {
// System.out.println("For class " + curClass.className + " require " +
// needClassName );
Boolean isHere = false;
for (JsFile curList : requiredLib) {
if (curList.className != null
&& curList.className.equals(needClassName)) {
// System.out.println("Already have it!");
isHere = true;
break;
}
}
if (!isHere) {
Boolean found = false;
// Go looking in the library + user files and add it.
for (JsFile j : userFiles) {
if (j.className != null && j.className.equals(needClassName)) {
requiredLib.add(j);
found = true;
// System.out.println("Found " + j.className + " adding..");
break;
}
}
if (!found) {
for (JsFile j : libFiles) {
if (j.className == null) {
continue;
}
if (j.className.equals(needClassName)) {
requiredLib.add(j);
found = true;
// System.out.println("Found " + j.className + " adding...");
break;
}
}
}
if (!found) {
System.out.println("Error did not find " + needClassName);
}
}
}
}
/*
* System.out.println("All required..."); for(JsFile all: requiredLib) {
* System.out.println(all.className); }
*/
// Now need to reorder
Boolean hasChanged = true;
while (hasChanged) {
hasChanged = false;
for (int x = 0; x < requiredLib.size(); x++) {
// Verify all load dependencies come before us. If not then we stick
// ourselves after the
// last load depend that we are about.
JsFile cur = requiredLib.get(x);
// Find highest index of load depend
int highIdx = 0;
for (String s : cur.getLoadDependencies()) {
for (int k = 0; k < requiredLib.size(); k++) {
if (requiredLib.get(k).className.equals(s)) {
highIdx = highIdx < k ? k : highIdx;
break;
}
}
}
if (x < highIdx) {
// need to move ourself to after the last one we need.
JsFile tmp = requiredLib.get(x);
// System.out.println(tmp.className + " from " + x + " to " +
// highIdx);
requiredLib.remove(x);
requiredLib.add(highIdx, tmp);
hasChanged = true;
}
}
}
/*
* System.out.println("All required in load order ..."); for(JsFile all:
* requiredLib) { System.out.println(all.className); }
*/
// OK, go through the output directory seeing what classes there are.
// Include any class with a static void main that we just cross compiled as
// we are doing apps here not
// STEP 3: - Copy resources to destination
System.out.println("Copying resources...");
for (LocationEntry res : resources) {
if (res.getType().equals("dir")) {
System.out
.println("copy " + res.getLocation() + " " + this.destination);
copyDirectory(res.getLocation(), this.destination);
} else {
System.out
.println("copy " + res.getLocation() + " " + this.destination);
copyFile(res.getLocation(), this.destination);
}
}
// Spit out a reasonable index file...
// STEP 7: - Create one single JS file that works
try {
FileReader fReader = new FileReader("./src/xmlvm2js/indexTemp.html");
BufferedReader reader = new BufferedReader(fReader);
StringBuilder buf = new StringBuilder();
String s;
while ((s = reader.readLine()) != null) {
buf.append(s + System.getProperty("line.separator"));
}
reader.close();
String result = new String(buf.toString());
FileWriter writer = new FileWriter(this.destination + "\\index.html");
writer.write(result.replaceAll("\\{MainClassName\\}", mainClassName));
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
if (createAssembly) {
StringBuilder sb = new StringBuilder();
for (JsFile curF : requiredLib) {
sb.append("\n//" + curF.className + "\n"
+ curF.fileContent.replaceAll("checkClass\\(\".*\"\\);", ""));
}
// As we don't have a real classloader, we need to initialize the
// classes after loading...
sb.append("\n//CLASS INITIALIZATION:\n");
for (JsFile file : requiredLib) {
if (file.getClassName() != null) {
String c = file.getClassName();
sb.append("if (" + c + ".$$clinit_ != undefined) " + c
+ ".__clinit_();\n");
sb.append("if (" + c + ".$$cctor != undefined) " + c
+ ".__cctor();\n");
}
}
String serializedContent = sb.toString();
String assemblyFileName = this.destination + "\\assembly.js";
// STEP 6: Compress output if set
// if (compress) {
// System.out.print("Compressing... ");
// int sizeBefore = serializedContent.length();
// serializedContent = compress(serializedContent);
// int sizeAfter = serializedContent.length();
// System.out.println("Done. (" + sizeBefore + " - " + sizeAfter + " = "
// + (sizeBefore - sizeAfter) + ")");
// }
// STEP 7: - Create one single JS file that works
try {
FileWriter writer = new FileWriter(assemblyFileName);
writer.write(serializedContent.toCharArray());
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Assembly written to: " + assemblyFileName);
}
System.out.println("DONE - You can now deploy your project.");
return true;
}
public boolean build() {
// By default, include xmlvm bootstrap
return build(true);
}
/**
* Compresses the given JavaScript code and return the deflated result or
* null, if an error occurred
*
* @param code
* @return
*/
public String compress(String code) {
Reader in = new StringReader(code);
try {
ErrorReporter reporter = new ErrorReporter() {
public void warning(String message, String sourceName, int line,
String lineSource, int lineOffset) {
if (line < 0) {
System.out.println("\n" + message);
} else {
System.out.println("\n" + line + ':' + lineOffset + ':' + message);
}
}
public void error(String message, String sourceName, int line,
String lineSource, int lineOffset) {
if (line < 0) {
System.err.println("\n" + message);
} else {
System.err.println("\n" + line + ':' + lineOffset + ':' + message);
}
}
public EvaluatorException runtimeError(String message,
String sourceName, int line, String lineSource, int lineOffset) {
error(message, sourceName, line, lineSource, lineOffset);
return new EvaluatorException(message);
}
};
JavaScriptCompressor compressor = new JavaScriptCompressor(in, reporter);
// Close the input stream in case the output file should overwrite
// it...
in.close();
in = null;
// Open the output stream now in case it should overwrite the
// input...
Writer out = new StringWriter();
compressor.compress(out, -1, false, false, false);
return out.toString();
} catch (EvaluatorException e) {
e.printStackTrace();
// Return a special error code used specifically by the web
// front-end.
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public void includeXmlvmBootstrap() {
System.out.println("Including bootstrap...");
this.resources.add(new LocationEntry("./lib/jsolait_xhr.js", false));
this.resources.add(new LocationEntry("./lib/qooxdoo/qx.js", false));
this.resources.add(new LocationEntry("./lib/qooxdoo/resource"));
}
static private boolean copyDirectory(String from, String to) {
UtilCopy uc = new UtilCopy();
try {
if (!to.endsWith(File.separator))
to += File.separator;
uc.xCopy(to + (new File(from)).getName(), from);
return true;
} catch (FileNotFoundException e) {
System.err.println("Couldn't find: " + from);
} catch (Exception e) {
System.err.println("Couldn't copy: " + from);
System.err.println("Reason: " + e.getMessage());
}
return false;
}
static private boolean copyFile(String from, String to) {
UtilCopy uc = new UtilCopy();
File f = new File(from);
if (!f.isFile())
return false;
String fileName = f.getName();
if (!to.endsWith(File.separator))
to += File.separator;
try {
uc.binCopy(to + fileName, from);
} catch (FileNotFoundException e) {
System.err.println("Couldn't find: " + from);
} catch (IOException e) {
System.err.println("Couldn't copy: " + from);
} catch (Exception e) {
System.err.println("Couldn't copy: " + from);
System.err.println("Reason: " + e.getMessage());
}
return true;
}
static private boolean deleteDirectory(File path) {
if (path.exists()) {
File[] files = path.listFiles();
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
deleteDirectory(files[i]);
} else {
files[i].delete();
}
}
}
return (path.delete());
}
public void addJavaClasspath(LocationEntry location) {
this.javaClasspaths.add(location);
}
public void addExePath(LocationEntry location) {
this.exePaths.add(location);
}
public LocationEntry[] getJavaClasspaths() {
LocationEntry le[] = new LocationEntry[0];
return this.javaClasspaths.toArray(le);
}
public boolean removeJavaClasspath(LocationEntry locRemove) {
for (LocationEntry loc : this.javaClasspaths) {
if (loc.getLocation().equals(locRemove.getLocation())) {
this.javaClasspaths.remove(loc);
return true;
}
}
return false;
}
public void addJavaScriptResource(LocationEntry location) {
this.javaScriptResources.add(location);
}
public LocationEntry[] getJavaScriptResources() {
LocationEntry le[] = new LocationEntry[0];
return this.javaScriptResources.toArray(le);
}
public boolean removeJavaScriptResource(LocationEntry locRemove) {
for (LocationEntry loc : this.javaScriptResources) {
if (loc.getLocation().equals(locRemove.getLocation())) {
this.javaScriptResources.remove(loc);
return true;
}
}
return false;
}
public void addResource(LocationEntry location) {
this.resources.add(location);
}
public LocationEntry[] getResources() {
LocationEntry le[] = new LocationEntry[0];
return this.resources.toArray(le);
}
public boolean removeResource(LocationEntry locRemove) {
for (LocationEntry loc : this.resources) {
if (loc.getLocation().equals(locRemove.getLocation())) {
this.resources.remove(loc);
return true;
}
}
return false;
}
public String getApplicationName() {
return applicationName;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public String getMainClass() {
return mainClass;
}
public void setMainClass(String mainClass) {
this.mainClass = mainClass;
}
public String getDestination() {
return destination;
}
/**
* Recursively get a list of all JS-Files in the given base path
*
* @param base
* @return
*/
private static List<JsFile> getJsFilesRecursive(File base) {
List<JsFile> result = new ArrayList<JsFile>();
File children[] = base.listFiles();
for (File child : children) {
if (child.isFile() && child.getName().toLowerCase().endsWith(".js")) {
result.add(new JsFile(child));
} else if (child.isDirectory()) {
result.addAll(getJsFilesRecursive(child));
}
}
return result;
}
/**
* Get the content of a file.
*
* @param file
* @return
*/
public static String readFileAsString(File file) {
final int READ_BUFFER = 4096;
FileInputStream is;
try {
is = new FileInputStream(file);
StringBuffer buffer = new StringBuffer();
byte b[] = new byte[READ_BUFFER];
int l = 0;
if (is == null) {
return "";
} else {
while ((l = is.read(b)) > 0) {
buffer.append(new String(b, 0, l));
}
}
return buffer.toString();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
/**
* Defines a JavaScript file that contains one class. It holds the file's
* contents as well as all the dependencies of the class within.
*/
static class JsFile {
private static final String QX_CLASS_DEFINE = "qx.Class.define(\"";
private static final String CHECKCLASS = "checkClass(\"";
private String className = null;
private String fileContent;
private List<String> dependsOnAll = new ArrayList<String>();
private List<String> dependsOnLoad = new ArrayList<String>();
public JsFile(File file) {
// Read contents of the file
fileContent = readFileAsString(file);
int classDefinePosition = 0;
// Check if we find a class definition within the file
if ((classDefinePosition = fileContent.indexOf(QX_CLASS_DEFINE)) != -1) {
int defineEnd = fileContent.indexOf("\"", classDefinePosition
+ QX_CLASS_DEFINE.length() + 1);
this.className = fileContent.substring(classDefinePosition
+ QX_CLASS_DEFINE.length(), defineEnd);
} else {
// If there is no class definition, we don't have a qooxdoo
// class in this file
this.className = null;
return;
}
HashSet<String> depends = new HashSet<String>();
Matcher m = Pattern.compile(".*checkClass\\(\\\"(.*)\\\"\\)").matcher(
fileContent);
while (m.find()) {
if (m.start(0) > classDefinePosition) {
if (!depends.contains(m.group(1))) {
depends.add(m.group(1));
}
} else {
dependsOnLoad.add(m.group(1).replace(".", "_"));
}
}
for (String dep : depends) {
Boolean add = true;
if (this.className != null) {
add = !this.className.equals(dep.replace(".", "_"));
}
if (add) {
this.dependsOnAll.add(dep.replace(".", "_"));
}
}
}
public String[] getLoadDependencies() {
String[] s = new String[0];
return this.dependsOnLoad.toArray(s);
}
public String[] getAllDependencies() {
String[] s = new String[0];
return this.dependsOnAll.toArray(s);
}
public void setClassName(String name) {
this.className = name;
}
public String getClassName() {
return this.className;
}
public String getFileContent() {
return fileContent;
}
public void setFileContent(String fileContent) {
this.fileContent = fileContent;
}
}
public boolean isCreateAssembly() {
return createAssembly;
}
// public boolean isCompress() {
// return compress;
// }
}
class LocationEntry {
private String type = "dir";
private String kind;
private boolean justContents = false;
private String fileSelect = "*";
private String location;
private boolean includeInIndex = false;
/**
* Create a new Location Entry
*
* @param location
* @param dir
* true: location points to directory. false to a file
*/
public LocationEntry(String location, boolean dir) {
this.type = (dir ? "dir" : "file");
this.justContents = false;
this.fileSelect = "*";
this.location = location;
}
public LocationEntry(String location) {
this(location, true);
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getKind() {
return kind;
}
public void setKind(String kind) {
this.kind = kind;
}
public boolean isJustContents() {
return justContents;
}
public void setJustContents(boolean justContents) {
this.justContents = justContents;
}
public String getFileSelect() {
return fileSelect;
}
public void setFileSelect(String fileSelect) {
this.fileSelect = fileSelect;
}
public boolean isIncludeInIndex() {
return includeInIndex;
}
public void setIncludeInIndex(boolean includeInIndex) {
this.includeInIndex = includeInIndex;
}
}
/**
* Used to indicates error during the building process.
*
* @author Sascha Haeberling
*
*/
class XmlvmBuilderException extends Exception {
public XmlvmBuilderException(String message) {
super(message);
}
public XmlvmBuilderException(String message, Throwable throwable) {
super(message, throwable);
}
private static final long serialVersionUID = 1L;
}
/**
* Takes the input of an InputStream and writes it to the given output stream.
* Useful if e.g. the stream comes from a process.
*
* @author Sascha Haeberling
*
*/
class InpuReaderThread extends Thread {
private BufferedReader in;
private PrintStream out;
private String prefix;
public InpuReaderThread(InputStream inputStream, PrintStream outStream,
String linePrefix) {
in = new BufferedReader(new InputStreamReader(inputStream));
out = outStream;
prefix = linePrefix;
}
public void run() {
String line;
try {
while ((line = in.readLine()) != null) {
out.println(prefix + " > " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
};