package greenfoot.export;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.jar.Attributes;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipOutputStream;
import bluej.Boot;
import bluej.Config;
import bluej.extensions.SourceType;
import bluej.pkgmgr.Project;
import bluej.utility.BlueJFileReader;
import bluej.utility.Debug;
import bluej.utility.FileUtility;
| Utility class to create jar or zip files from a Greenfoot project.
|
| @author Poul Henriksen <polle@polle.org>
|
public class JarCreator
{
private static final String SOURCE_SUFFIX = "." + SourceType.Java.toString().toLowerCase();
| Should source files be included in the jar?
|
private boolean includeSource;
| The main class attribute for the JAR.
|
private String mainClass;
| Directory where the jar is exported to.
|
private File exportDir;
| Directory to be exported.
|
private File projectDir;
| Name of the jar file that will be created.
|
private String jarName;
| List of extra jars that should be put in the same dir as the created jar (the exportDir)
|
private List<File> extraJars = new LinkedList<>();
| List of extra jars whose contents should be put into the created jar
|
private List<File> extraJarsInJar = new LinkedList<>();
| List of paths to external jars that should be included in the manifest's classpath.
|
private List<String> extraExternalJars = new LinkedList<>();
private List<File> dirs = new LinkedList<>();
private List<PrefixedFile> prefixDirs = new LinkedList<>();
| array of directory names not to be included in jar file *
|
private List<String> skipDirs = new LinkedList<>();
| array of file names not to be included in jar file *
|
private List<String> skipFiles = new LinkedList<>();
| The maninfest
|
private Manifest manifest = new Manifest();
| Properties that contains information read by the GreenfootScnearioViewer
|
private Properties properties;
private boolean isZip = false;
| Prepares a new jar creator. Once everything is set up, call create()
|
| @param exportDir The directory in which to store the jar-file and any
| other resources required. Must exist and be writable
| @param jarName The name of the jar file.
|
private JarCreator(File exportDir, String jarName)
{
File jarFile = new File(exportDir, jarName);
if (!jarFile.canWrite() && jarFile.exists()) {
throw new IllegalArgumentException("Cannot write file: " + jarFile);
}
this.exportDir = exportDir;
this.jarName = jarName;
properties = new Properties();
}
| Export the class files for a project.
|
| Convenience constructor that includes settings that are common for all
| projects and export types. This will exclude BlueJ metafiles.
|
| @param project The project to be exported.
| @param exportDir The directory to export to.
| @param jarName Name of the jar file that should be created.
| @param worldClass Name of the main class.
| @param lockScenario Should the exported scenario include 'act'
| and speedslider.
| @param hideControls Should the exported scenario include the controls panel
| @param applet Whether the export is for an applet on a webpage (true) or for a stand-alone JAR (false)
|
public JarCreator(Project project, File exportDir, String jarName, String worldClass,
boolean lockScenario, boolean hideControls, boolean fullScreen, boolean applet)
{
this(project, exportDir, jarName, worldClass, lockScenario, applet);
properties.put("scenario.hideControls", "" + hideControls);
properties.put("scenario.fullScreen", "" + fullScreen);
}
| Export the class files for a project.
|
| Convenience constructor that includes settings that are common for all
| projects and export types. This will exclude BlueJ metafiles.
|
| @param project The project to be exported.
| @param exportDir The directory to export to.
| @param jarName Name of the jar file that should be created.
| @param worldClass Name of the main class.
| @param lockScenario Should the exported scenario include 'act'
| and speedslider.
| @param applet Whether the export is for an applet on a webpage (true) or for a stand-alone JAR (false)
|
public JarCreator(Project project, File exportDir, String jarName, String worldClass,
boolean lockScenario, boolean applet)
{
this(exportDir, jarName);
projectDir = project.getProjectDir();
String scenarioName = project.getProjectName();
addFile(projectDir);
addSkipDir("CVS");
addSkipFile(".cvsignore");
addSkipDir(".svn");
addSkipFile(".DS_Store");
addSkipDir(projectDir.getPath() + System.getProperty("file.separator") + "doc");
addSkipDir(exportDir.getAbsolutePath());
addSkipFile(".ctxt");
addSkipFile("bluej.pkg");
addSkipFile("bluej.pkh");
addSkipDir(Project.projectLibDirName);
String mainClass = (applet ? GreenfootScenarioViewer.class : GreenfootScenarioApplication.class).getCanonicalName();
setMainClass(mainClass);
properties.put("project.name", scenarioName);
properties.put("project.greenfootversion", Boot.GREENFOOT_VERSION);
properties.put("project.javaspecversion", System.getProperty("java.specification.version"));
properties.put("project.javaversion", System.getProperty("java.version"));
properties.put("project.javaclassversion", System.getProperty("java.class.version"));
properties.put("main.class", worldClass);
properties.put("scenario.lock", "" + lockScenario);
properties.put("scenario.viewer.appletInfo", Config.getString("scenario.viewer.appletInfo"));
properties.put("run.once", Config.getString("run.once"));
properties.put("run.simulation", Config.getString("run.simulation"));
properties.put("pause.simulation", Config.getString("pause.simulation"));
properties.put("reset.world", Config.getString("reset.world"));
properties.put("controls.speed.label", Config.getString("controls.speed.label"));
properties.put("controls.runonce.longDescription", Config.getString("controls.runonce.longDescription"));
properties.put("controls.runonce.shortDescription", Config.getString("controls.runonce.shortDescription"));
properties.put("controls.run.longDescription", Config.getString("controls.run.longDescription"));
properties.put("controls.run.shortDescription", Config.getString("controls.run.shortDescription"));
properties.put("controls.pause.longDescription", Config.getString("controls.pause.longDescription"));
properties.put("controls.pause.shortDescription", Config.getString("controls.pause.shortDescription"));
properties.put("controls.run.button", Config.getString("controls.run.button"));
properties.put("controls.pause.button", Config.getString("controls.pause.button"));
properties.put("controls.reset.longDescription", Config.getString("controls.reset.longDescription"));
properties.put("controls.reset.shortDescription", Config.getString("controls.reset.shortDescription"));
properties.put("controls.speedSlider.tooltip", Config.getString("controls.speedSlider.tooltip"));
properties.put("sound-line-unavailable", Config.getString("sound-line-unavailable"));
}
| Export source code. Includes all the project files. Creates a dir in the
| zip with the same name as the project dir.
|
| <p>Convenience constructor that includes settings that are common for all
| projects and export types.
|
| @param project The project to be exported.
| @param exportDir The directory to export to.
| @param zipName Name of the jar file that should be created.
|
public JarCreator(Project project, File exportDir, String zipName)
{
this(exportDir, zipName);
isZip = true;
projectDir = project.getProjectDir();
addFile(projectDir);
addSkipDir("CVS");
addSkipFile(".cvsignore");
addSkipDir(".svn");
addSkipFile(".DS_Store");
addSkipDir(projectDir.getPath() + System.getProperty("file.separator") + "doc");
addSkipDir(exportDir.getAbsolutePath());
addSkipDir(projectDir.getPath() + System.getProperty("file.separator") + "greenfoot");
addSkipFile("bluej.pkg");
addSkipFile("bluej.pkh");
includeSource(true);
}
| Creates the jar file with the current settings.
|
public void create()
{
File jarFile = new File(exportDir, jarName);
File propertiesFile = null;
File soundFile = null;
OutputStream oStream = null;
ZipOutputStream jStream = null;
try {
oStream = new BufferedOutputStream(new FileOutputStream(jarFile));
String pathPrefix = "";
if (! isZip) {
writeManifest();
propertiesFile = new File(projectDir, "standalone.properties");
writePropertiesFile(propertiesFile);
soundFile = new File(projectDir, "soundindex.list");
writeSoundFilesList(soundFile);
jStream = new JarOutputStream(oStream, manifest);
}
else {
pathPrefix = projectDir.getName() + "/";
jStream = new ZipOutputStream(oStream);
}
for (File dir : dirs) {
writeFileToJar(dir, pathPrefix, jStream, jarFile.getCanonicalFile(), true);
}
for (PrefixedFile dir : prefixDirs) {
writeFileToJar(dir.getFile(), pathPrefix + dir.getPrefix(), jStream, jarFile.getCanonicalFile(), true);
}
for (File jar : extraJarsInJar) {
writeJarToJar(jar, jStream);
}
copyLibsToDir(extraJars, exportDir);
}
catch (IOException exc) {
Debug.reportError("problem writing jar file: " + exc);
}
finally {
try {
if (jStream != null)
jStream.close();
}
catch (IOException e) {
}
if (propertiesFile != null) {
propertiesFile.delete();
}
}
}
private void writeSoundFilesList(File file)
{
BufferedWriter os;
try {
file.createNewFile();
os = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
for (String name : new File(projectDir, "sounds").list())
{
os.write(name + "\n");
}
os.close();
}
catch (IOException e)
{
Debug.reportError("Error writing list of sounds: ", e);
}
}
| Writes the properties to the given file.
|
private void writePropertiesFile(File file)
{
OutputStream os = null;
try {
file.createNewFile();
os = new BufferedOutputStream(new FileOutputStream(file));
properties.store(os, "Properties for running Greenfoot scenarios alone.");
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
finally {
try {
if (os != null) {
os.close();
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
| Puts an entry into to the manifest file.
|
| @param key The key
| @param value The value
|
public void putManifestEntry(String key, String value)
{
Attributes attr = manifest.getMainAttributes();
value = fixNewlines(value);
attr.put(new Attributes.Name(key), value);
}
private String fixNewlines(String value)
{
StringBuffer buffer = new StringBuffer(value.length());
String newLineRegExp = "(?m)(?s)$.^";
String trailingNewLineReqExp = "$.\\z";
String[] lines = value.split(newLineRegExp + "|" + trailingNewLineReqExp );
for (int i = 0; i < lines.length; i++) {
String string = lines[i];
if (i!=0) {
buffer.append("<br>");
}
buffer.append(string);
}
return buffer.toString();
}
| Writes entries to the manifest file.
|
private void writeManifest()
{
String classpath = "";
for (File extraJar : extraJars)
{
classpath += " " + extraJar.getName();
}
for (String extraExternalJar : extraExternalJars)
{
classpath += " " + extraExternalJar;
}
Attributes attr = manifest.getMainAttributes();
attr.put(Attributes.Name.MANIFEST_VERSION, "1.0");
attr.put(Attributes.Name.MAIN_CLASS, mainClass);
attr.put(Attributes.Name.CLASS_PATH, classpath);
}
| Whether to include sources or not.
|
public void includeSource(boolean b)
{
includeSource = b;
}
| Sets the main class for this JAR. The class that contains the main method
| or Applet class.
|
| @param mainClass
|
public void setMainClass(String mainClass)
{
this.mainClass = mainClass;
}
| Adds a jar file to be distributed together with this jar-file. It will be
| copied into the same location as the jar-file created (the exportDir).
|
| <p>This will usually be the jars +libs dir and userlib jars
|
| @param jar A jar file.
|
public void addJar(File jar)
{
extraJars.add(jar);
}
| Add a jar to the list of extra jars whose contents should be put into the created jar
|
| <p>This will usually be the jars +libs dir and userlib jars
|
| @param jar A jar file.
|
public void addJarToJar(File jar)
{
extraJarsInJar.add(jar);
}
| Adds a location of an external jar file. This will be added to the
| classpath of the manifest.
|
| @param path Usually a URL or a relative path.
|
public void addToClassPath(String path)
{
extraExternalJars.add(path);
}
| Directory or file to include in export.
|
public void addFile(File file)
{
dirs.add(file);
}
| Directory or file to include in export, with the given prefix added when putting
| it into the jar.
|
public void addFile(String prefix, File file)
{
prefixDirs.add(new PrefixedFile(prefix, file));
}
| All dirs that end with the specified string will be skipped.
| Be aware of platform dependent file separators.
|
public void addSkipDir(String dir)
{
skipDirs.add(dir);
}
| All files that end with the specified string will be skipped.
| Be aware of platform dependent file separators.
|
public void addSkipFile(String file)
{
skipFiles.add(file);
}
| Write the contents of a directory to a jar stream. Recursively called for
| subdirectories. outputFile should be the canonical file representation of
| the Jar file we are creating (to prevent including itself in the Jar
| file)
|
private void writeDirToJar(File sourceDir, String pathPrefix, ZipOutputStream stream, File outputFile)
throws IOException
{
if (!skipDir(sourceDir))
{
File[] dir = sourceDir.listFiles();
for (File sourceFile : dir)
{
writeFileToJar(sourceFile, pathPrefix, stream, outputFile, false);
}
}
}
| Writes a file or directory to a jar. Recursively called for
| subdirectories. outputFile should be the canonical file representation of
| the Jar file we are creating (to prevent including itself in the Jar
| file). If the source file does not exist, this method will just return without
| doing anything.
|
| @param onlyDirContents If sourceFile is a dir, this parameter indicates that
| the contents of the dir should be added, not the dir itself.
|
private void writeFileToJar(File sourceFile, String pathPrefix, ZipOutputStream stream, File outputFile, boolean onlyDirContents)
throws IOException
{
if (!sourceFile.exists()) {
return;
}
if (sourceFile.isDirectory()) {
if (!onlyDirContents) {
pathPrefix += sourceFile.getName() + "/";
}
writeDirToJar(sourceFile, pathPrefix, stream, outputFile);
}
else {
if (!skipFile(sourceFile.getName(), !includeSource)
&& !outputFile.equals(sourceFile.getCanonicalFile())) {
writeJarEntry(sourceFile, stream, pathPrefix + sourceFile.getName());
}
}
}
| Write the contents of a jar into another jar stream. If the source file does not exist,
| this method will just return without doing anything.
|
private void writeJarToJar(File inputJar, ZipOutputStream outputStream)
throws IOException
{
if (!inputJar.exists()) {
return;
}
JarInputStream inputStream = new JarInputStream(
new BufferedInputStream(new FileInputStream(inputJar)));
ZipEntry inputEntry = inputStream.getNextJarEntry();
while (inputEntry != null) {
outputStream.putNextEntry(inputEntry);
FileUtility.copyStream(inputStream, outputStream);
inputStream.closeEntry();
inputEntry = inputStream.getNextJarEntry();
}
inputStream.close();
}
| Copy all files specified in the given list to the new jar directory.
|
private void copyLibsToDir(List<File> userLibs, File destDir)
{
for (File lib : userLibs)
{
if (lib.exists())
{
File destFile = new File(destDir, lib.getName());
try
{
FileUtility.copyFile(lib, destFile);
}
catch (IOException e)
{
Debug.reportError("Error when copying file: " + lib + " to: " + destFile, e);
}
}
}
}
| Test whether a given directory should be skipped (not included) in
| export.
|
private boolean skipDir(File dir) throws IOException
{
for (String skipDir : skipDirs)
{
if (dir.getCanonicalFile().getPath().endsWith(skipDir))
{
return true;
}
}
return false;
}
| Checks whether a file should be skipped during a copy operation.
|
private boolean skipFile(String fileName, boolean skipSource)
{
for (String skipFile : skipFiles) {
if (fileName.endsWith(skipFile))
return true;
}
if (fileName.endsWith(SOURCE_SUFFIX))
return !includeSource;
return false;
}
| Write a jar file entry to the jar output stream. Note: entryName should
| always be a path with / seperators (NOT the platform dependant
| File.seperator)
|
private void writeJarEntry(File file, ZipOutputStream stream, String entryName)
throws IOException
{
InputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream(file));
stream.putNextEntry(new ZipEntry(entryName));
FileUtility.copyStream(in, stream);
}
catch (ZipException exc) {
Debug.message("warning: " + exc);
}
finally {
if (in != null)
in.close();
}
}
public void generateHTMLSkeleton(File outputFile, String title, int width, int height)
{
Hashtable<String,String> translations = new Hashtable<>();
translations.put("TITLE", title);
translations.put("CLASSFILE", mainClass + ".class");
translations.put("CODEBASE", "");
translations.put("APPLETWIDTH", "" + width);
translations.put("APPLETHEIGHT", "" + height);
|
|
|
public defVis This does not work on Safari (and maybe other browser as well)
|
|
String archives = jarName;
|
|
try {} for (int i = 0; i < libs.length; i++) {}if (archives.length() == 0)
|
|
archives = libs[i].toURI().toURL().toString();
|
|
else
|
|
archives += "," + libs[i].toURL();
}
}
catch (MalformedURLException e) {}*/
try {
translations.put("ARCHIVE", URLEncoder.encode(jarName, "UTF-8"));
}
catch (UnsupportedEncodingException uee) {
// This can't happen; Java always supports UTF-8.
}
String baseName = "greenfoot/templates/html.tmpl";
File template = Config.getLanguageFile(baseName);
try {
Charset utf8 = Charset.forName("UTF-8");
BlueJFileReader.translateFile(template, outputFile, translations, utf8, utf8);
}
catch (IOException e) {
Debug.reportError("Exception during file translation from " + template + " to " + outputFile);
e.printStackTrace();
}
}
static class PrefixedFile
{
private File file;
public File getFile()
{
return file;
|
|
}
|
|
public String getPrefix()
|
|
{
|
|
return prefix;
|
|
}
|
|
private String prefix;
|
|
public PrefixedFile(String prefix, File file)
|
|
{
|
|
this.prefix = prefix;
|
|
this.file = file;
|
|
}
|
|
}
|
|
}