diff --git a/build/macosx/appbundler/.gitignore b/build/macosx/appbundler/.gitignore
new file mode 100644
index 000000000..fe99505dc
--- /dev/null
+++ b/build/macosx/appbundler/.gitignore
@@ -0,0 +1,2 @@
+bin
+
diff --git a/build/macosx/appbundler/README.md b/build/macosx/appbundler/README.md
new file mode 100644
index 000000000..cd3eabc84
--- /dev/null
+++ b/build/macosx/appbundler/README.md
@@ -0,0 +1,85 @@
+appbundler
+=============
+
+A fork of the [Java Application Bundler](https://svn.java.net/svn/appbundler~svn)
+with the following changes:
+
+- The native binary is created as universal (32/64)
+- Fixes [icon not showing bug](http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7159381) in `JavaAppLauncher`
+- Adds `LC_CTYPE` environment variable to the `Info.plist` file in order to fix an [issue with `File.exists()` in OpenJDK 7](http://java.net/jira/browse/MACOSX_PORT-165) **(Contributed by Steve Hannah)**
+- Allows to specify the name of the executable instead of using the default `"JavaAppLauncher"` **(contributed by Karl von Randow)**
+- Adds `classpathref` support to the `bundleapp` task
+- Adds support for `JVMArchs` and `LSArchitecturePriority` keys
+- Allows to specify a custom value for `CFBundleVersion`
+- Allows specifying registered file extensions using `CFBundleDocumentTypes`
+- Passes to the Java application a set of environment variables with the paths of
+ the OSX special folders and whether the application is running in the
+ sandbox (see below).
+
+These are the environment variables passed to the JVM:
+
+- `LibraryDirectory`
+- `DocumentsDirectory`
+- `CachesDirectory`
+- `ApplicationSupportDirectory`
+- `SandboxEnabled` (the String `true` or `false`)
+
+
+Example:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/macosx/appbundler/TODO b/build/macosx/appbundler/TODO
new file mode 100644
index 000000000..270d3e950
--- /dev/null
+++ b/build/macosx/appbundler/TODO
@@ -0,0 +1,3 @@
+- Architectures should be automatically inferred from the embedded JVM
+- It makes more sense to take as input a JRE instead of taking a JDK and strip
+ it until it becomes a JRE...
diff --git a/build/macosx/appbundler/build.xml b/build/macosx/appbundler/build.xml
new file mode 100644
index 000000000..e0a5e5b3b
--- /dev/null
+++ b/build/macosx/appbundler/build.xml
@@ -0,0 +1,176 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/macosx/appbundler/native/main.m b/build/macosx/appbundler/native/main.m
new file mode 100644
index 000000000..55bcfa917
--- /dev/null
+++ b/build/macosx/appbundler/native/main.m
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2012, Oracle and/or its affiliates. All rights reserved.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#import
+#include
+#include
+
+#define JAVA_LAUNCH_ERROR "JavaLaunchError"
+
+#define JVM_RUNTIME_KEY "JVMRuntime"
+#define WORKING_DIR "WorkingDirectory"
+#define JVM_MAIN_CLASS_NAME_KEY "JVMMainClassName"
+#define JVM_OPTIONS_KEY "JVMOptions"
+#define JVM_ARGUMENTS_KEY "JVMArguments"
+
+#define JVM_RUN_PRIVILEGED "JVMRunPrivileged"
+
+#define UNSPECIFIED_ERROR "An unknown error occurred."
+
+#define APP_ROOT_PREFIX "$APP_ROOT"
+
+#define LIBJLI_DYLIB "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/lib/jli/libjli.dylib"
+
+typedef int (JNICALL *JLI_Launch_t)(int argc, char ** argv,
+ int jargc, const char** jargv,
+ int appclassc, const char** appclassv,
+ const char* fullversion,
+ const char* dotversion,
+ const char* pname,
+ const char* lname,
+ jboolean javaargs,
+ jboolean cpwildcard,
+ jboolean javaw,
+ jint ergo);
+
+int launch(char *);
+
+int main(int argc, char *argv[]) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ int result;
+ @try {
+ launch(argv[0]);
+ result = 0;
+ } @catch (NSException *exception) {
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setAlertStyle:NSCriticalAlertStyle];
+ [alert setMessageText:[exception reason]];
+ [alert runModal];
+
+ result = 1;
+ }
+
+ [pool drain];
+
+ return result;
+}
+
+int launch(char *commandName) {
+ // Get the main bundle
+ NSBundle *mainBundle = [NSBundle mainBundle];
+
+ // Get the main bundle's info dictionary
+ NSDictionary *infoDictionary = [mainBundle infoDictionary];
+
+ // Set the working directory based on config, defaulting to the user's home directory
+ NSString *workingDir = [infoDictionary objectForKey:@WORKING_DIR];
+ if (workingDir != nil) {
+ workingDir = [workingDir stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]];
+ } else {
+ workingDir = NSHomeDirectory();
+ }
+
+ chdir([workingDir UTF8String]);
+
+ // execute privileged
+ NSString *privileged = [infoDictionary objectForKey:@JVM_RUN_PRIVILEGED];
+ if ( privileged != nil && getuid() != 0 ) {
+ NSDictionary *error = [NSDictionary new];
+ NSString *script = [NSString stringWithFormat:@"do shell script \"\\\"%@\\\" > /dev/null 2>&1 &\" with administrator privileges", [NSString stringWithCString:commandName encoding:NSASCIIStringEncoding]];
+ NSAppleScript *appleScript = [[NSAppleScript new] initWithSource:script];
+ if ([appleScript executeAndReturnError:&error]) {
+ // This means we successfully elevated the application and can stop in here.
+ return;
+ }
+ }
+
+ // Locate the JLI_Launch() function
+ NSString *runtime = [infoDictionary objectForKey:@JVM_RUNTIME_KEY];
+
+ const char *libjliPath = NULL;
+ if (runtime != nil) {
+ NSString *runtimePath = [[[NSBundle mainBundle] builtInPlugInsPath] stringByAppendingPathComponent:runtime];
+ libjliPath = [[runtimePath stringByAppendingPathComponent:@"Contents/Home/jre/lib/jli/libjli.dylib"] fileSystemRepresentation];
+ } else {
+ libjliPath = LIBJLI_DYLIB;
+ }
+
+ void *libJLI = dlopen(libjliPath, RTLD_LAZY);
+
+ JLI_Launch_t jli_LaunchFxnPtr = NULL;
+ if (libJLI != NULL) {
+ jli_LaunchFxnPtr = dlsym(libJLI, "JLI_Launch");
+ }
+
+ if (jli_LaunchFxnPtr == NULL) {
+ [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR
+ reason:NSLocalizedString(@"JRELoadError", @UNSPECIFIED_ERROR)
+ userInfo:nil] raise];
+ }
+
+ // Get the main class name
+ NSString *mainClassName = [infoDictionary objectForKey:@JVM_MAIN_CLASS_NAME_KEY];
+ if (mainClassName == nil) {
+ [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR
+ reason:NSLocalizedString(@"MainClassNameRequired", @UNSPECIFIED_ERROR)
+ userInfo:nil] raise];
+ }
+
+ // Set the class path
+ NSString *mainBundlePath = [mainBundle bundlePath];
+ NSString *javaPath = [mainBundlePath stringByAppendingString:@"/Contents/Java"];
+ NSMutableString *classPath = [NSMutableString stringWithFormat:@"-Djava.class.path=%@/Classes", javaPath];
+
+ NSFileManager *defaultFileManager = [NSFileManager defaultManager];
+ NSArray *javaDirectoryContents = [defaultFileManager contentsOfDirectoryAtPath:javaPath error:nil];
+ if (javaDirectoryContents == nil) {
+ [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR
+ reason:NSLocalizedString(@"JavaDirectoryNotFound", @UNSPECIFIED_ERROR)
+ userInfo:nil] raise];
+ }
+
+ for (NSString *file in javaDirectoryContents) {
+ if ([file hasSuffix:@".jar"]) {
+ [classPath appendFormat:@":%@/%@", javaPath, file];
+ }
+ }
+
+ // Set the library path
+ NSString *libraryPath = [NSString stringWithFormat:@"-Djava.library.path=%@/Contents/MacOS", mainBundlePath];
+
+ // Get the VM options
+ NSArray *options = [infoDictionary objectForKey:@JVM_OPTIONS_KEY];
+ if (options == nil) {
+ options = [NSArray array];
+ }
+
+ // Get the application arguments
+ NSArray *arguments = [infoDictionary objectForKey:@JVM_ARGUMENTS_KEY];
+ if (arguments == nil) {
+ arguments = [NSArray array];
+ }
+
+ // Set OSX special folders
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
+ NSUserDomainMask, YES);
+ NSString *basePath = [paths objectAtIndex:0];
+ NSString *libraryDirectory = [NSString stringWithFormat:@"-DLibraryDirectory=%@", basePath];
+
+ paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
+ NSUserDomainMask, YES);
+ basePath = [paths objectAtIndex:0];
+ NSString *documentsDirectory = [NSString stringWithFormat:@"-DDocumentsDirectory=%@", basePath];
+
+ paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory,
+ NSUserDomainMask, YES);
+ basePath = [paths objectAtIndex:0];
+ NSString *applicationSupportDirectory = [NSString stringWithFormat:@"-DApplicationSupportDirectory=%@", basePath];
+
+ paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
+ NSUserDomainMask, YES);
+ basePath = [paths objectAtIndex:0];
+ NSString *cachesDirectory = [NSString stringWithFormat:@"-DCachesDirectory=%@", basePath];
+
+ NSString *sandboxEnabled = @"true";
+ if ([basePath rangeOfString:@"Containers"].location == NSNotFound) {
+ sandboxEnabled = @"false";
+ }
+ NSString *sandboxEnabledVar = [NSString stringWithFormat:@"-DSandboxEnabled=%@", sandboxEnabled];
+
+ // Initialize the arguments to JLI_Launch()
+ // +5 due to the special directories and the sandbox enabled property
+ int argc = 1 + [options count] + 2 + [arguments count] + 1 + 5;
+ char *argv[argc];
+
+ int i = 0;
+ argv[i++] = commandName;
+ argv[i++] = strdup([classPath UTF8String]);
+ argv[i++] = strdup([libraryPath UTF8String]);
+ argv[i++] = strdup([libraryDirectory UTF8String]);
+ argv[i++] = strdup([documentsDirectory UTF8String]);
+ argv[i++] = strdup([applicationSupportDirectory UTF8String]);
+ argv[i++] = strdup([cachesDirectory UTF8String]);
+ argv[i++] = strdup([sandboxEnabledVar UTF8String]);
+
+ for (NSString *option in options) {
+ option = [option stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]];
+ argv[i++] = strdup([option UTF8String]);
+ }
+
+ argv[i++] = strdup([mainClassName UTF8String]);
+
+ for (NSString *argument in arguments) {
+ argument = [argument stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]];
+ argv[i++] = strdup([argument UTF8String]);
+ }
+
+ // Invoke JLI_Launch()
+ return jli_LaunchFxnPtr(argc, argv,
+ 0, NULL,
+ 0, NULL,
+ "",
+ "",
+ "java",
+ "java",
+ FALSE,
+ FALSE,
+ FALSE,
+ 0);
+}
diff --git a/build/macosx/appbundler/res/en.lproj/Localizable.strings b/build/macosx/appbundler/res/en.lproj/Localizable.strings
new file mode 100644
index 000000000..0d306aaaf
--- /dev/null
+++ b/build/macosx/appbundler/res/en.lproj/Localizable.strings
@@ -0,0 +1,3 @@
+"JRELoadError" = "Unable to load Java Runtime Environment.";
+"MainClassNameRequired" = "Main class name is required.";
+"JavaDirectoryNotFound" = "Unable to enumerate Java directory contents.";
diff --git a/build/macosx/appbundler/src/com/oracle/appbundler/AppBundlerTask.java b/build/macosx/appbundler/src/com/oracle/appbundler/AppBundlerTask.java
new file mode 100644
index 000000000..ef9121223
--- /dev/null
+++ b/build/macosx/appbundler/src/com/oracle/appbundler/AppBundlerTask.java
@@ -0,0 +1,731 @@
+/*
+ * Copyright 2012, Oracle and/or its affiliates. All rights reserved.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.appbundler;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.Reference;
+import org.apache.tools.ant.types.resources.FileResource;
+
+/**
+ * App bundler Ant task.
+ */
+public class AppBundlerTask extends Task {
+ // Output folder for generated bundle
+ private File outputDirectory = null;
+
+ // General bundle properties
+ private String name = null;
+ private String displayName = null;
+ private String identifier = null;
+ private File icon = null;
+ private String executableName = EXECUTABLE_NAME;
+
+ private String shortVersion = "1.0";
+ private String version = "1.0";
+ private String signature = "????";
+ private String copyright = "";
+ private String privileged = null;
+ private String workingDirectory = null;
+
+ private String applicationCategory = null;
+
+ private boolean highResolutionCapable = true;
+
+ // JVM info properties
+ private String mainClassName = null;
+ private FileSet runtime = null;
+ private ArrayList classPath = new ArrayList<>();
+ private ArrayList libraryPath = new ArrayList<>();
+ private ArrayList options = new ArrayList<>();
+ private ArrayList arguments = new ArrayList<>();
+ private ArrayList architectures = new ArrayList<>();
+ private ArrayList bundleDocuments = new ArrayList<>();
+
+ private Reference classPathRef;
+
+ private static final String EXECUTABLE_NAME = "JavaAppLauncher";
+ private static final String DEFAULT_ICON_NAME = "GenericApp.icns";
+ private static final String OS_TYPE_CODE = "APPL";
+
+ private static final String PLIST_DTD = "";
+ private static final String PLIST_TAG = "plist";
+ private static final String PLIST_VERSION_ATTRIBUTE = "version";
+ private static final String DICT_TAG = "dict";
+ private static final String KEY_TAG = "key";
+ private static final String ARRAY_TAG = "array";
+ private static final String STRING_TAG = "string";
+
+ private static final int BUFFER_SIZE = 2048;
+
+ public void setOutputDirectory(File outputDirectory) {
+ this.outputDirectory = outputDirectory;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.displayName = displayName;
+ }
+
+ public void setIdentifier(String identifier) {
+ this.identifier = identifier;
+ }
+
+ public void setIcon(File icon) {
+ this.icon = icon;
+ }
+
+ public void setExecutableName(String executable) {
+ this.executableName = executable;
+ }
+
+ public void setShortVersion(String shortVersion) {
+ this.shortVersion = shortVersion;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public void setSignature(String signature) {
+ this.signature = signature;
+ }
+
+ public void setCopyright(String copyright) {
+ this.copyright = copyright;
+ }
+
+ public void setPrivileged(String privileged) {
+ this.privileged = privileged;
+ }
+
+ public void setWorkingDirectory(String workingDirectory) {
+ this.workingDirectory = workingDirectory;
+ }
+
+ public void setApplicationCategory(String applicationCategory) {
+ this.applicationCategory = applicationCategory;
+ }
+
+ public void setHighResolutionCapable(boolean highResolutionCapable) {
+ this.highResolutionCapable = highResolutionCapable;
+ }
+
+ public void setMainClassName(String mainClassName) {
+ this.mainClassName = mainClassName;
+ }
+
+ public void addConfiguredRuntime(FileSet runtime) throws BuildException {
+ if (this.runtime != null) {
+ throw new BuildException("Runtime already specified.");
+ }
+
+ this.runtime = runtime;
+
+ runtime.appendIncludes(new String[] {
+ "jre/",
+ });
+
+ runtime.appendExcludes(new String[] {
+ "bin/",
+ "jre/bin/",
+ "jre/lib/deploy/",
+ "jre/lib/deploy.jar",
+ "jre/lib/javaws.jar",
+ "jre/lib/libdeploy.dylib",
+ "jre/lib/libnpjp2.dylib",
+ "jre/lib/plugin.jar",
+ "jre/lib/security/javaws.policy"
+ });
+ }
+
+ public void setClasspathRef(Reference ref) {
+ this.classPathRef = ref;
+ }
+
+ public void addConfiguredClassPath(FileSet classPath) {
+ this.classPath.add(classPath);
+ }
+
+ public void addConfiguredLibraryPath(FileSet libraryPath) {
+ this.libraryPath.add(libraryPath);
+ }
+
+ public void addConfiguredBundleDocument(BundleDocument document) {
+ this.bundleDocuments.add(document);
+ }
+
+ public void addConfiguredOption(Option option) throws BuildException {
+ String value = option.getValue();
+
+ if (value == null) {
+ throw new BuildException("Value is required.");
+ }
+
+ options.add(value);
+ }
+
+ public void addConfiguredArgument(Argument argument) throws BuildException {
+ String value = argument.getValue();
+
+ if (value == null) {
+ throw new BuildException("Value is required.");
+ }
+
+ arguments.add(value);
+ }
+
+ public void addConfiguredArch(Architecture architecture) throws BuildException {
+ String name = architecture.getName();
+
+ if (name == null) {
+ throw new BuildException("Name is required.");
+ }
+
+ architectures.add(name);
+ }
+
+ @Override
+ public void execute() throws BuildException {
+ // Validate required properties
+ if (outputDirectory == null) {
+ throw new IllegalStateException("Output directory is required.");
+ }
+
+ if (!outputDirectory.exists()) {
+ throw new IllegalStateException("Output directory does not exist.");
+ }
+
+ if (!outputDirectory.isDirectory()) {
+ throw new IllegalStateException("Invalid output directory.");
+ }
+
+ if (name == null) {
+ throw new IllegalStateException("Name is required.");
+ }
+
+ if (displayName == null) {
+ throw new IllegalStateException("Display name is required.");
+ }
+
+ if (identifier == null) {
+ throw new IllegalStateException("Identifier is required.");
+ }
+
+ if (icon != null) {
+ if (!icon.exists()) {
+ throw new IllegalStateException("Icon does not exist.");
+ }
+
+ if (icon.isDirectory()) {
+ throw new IllegalStateException("Invalid icon.");
+ }
+ }
+
+ if (shortVersion == null) {
+ throw new IllegalStateException("Short version is required.");
+ }
+
+ if (signature == null) {
+ throw new IllegalStateException("Signature is required.");
+ }
+
+ if (signature.length() != 4) {
+ throw new IllegalStateException("Invalid signature.");
+ }
+
+ if (copyright == null) {
+ throw new IllegalStateException("Copyright is required.");
+ }
+
+ if (mainClassName == null) {
+ throw new IllegalStateException("Main class name is required.");
+ }
+
+ // Create the app bundle
+ try {
+ System.out.println("Creating app bundle: " + name);
+
+ // Create directory structure
+ File rootDirectory = new File(outputDirectory, name + ".app");
+ delete(rootDirectory);
+ rootDirectory.mkdir();
+
+ File contentsDirectory = new File(rootDirectory, "Contents");
+ contentsDirectory.mkdir();
+
+ File macOSDirectory = new File(contentsDirectory, "MacOS");
+ macOSDirectory.mkdir();
+
+ File javaDirectory = new File(contentsDirectory, "Java");
+ javaDirectory.mkdir();
+
+ File plugInsDirectory = new File(contentsDirectory, "PlugIns");
+ plugInsDirectory.mkdir();
+
+ File resourcesDirectory = new File(contentsDirectory, "Resources");
+ resourcesDirectory.mkdir();
+
+ // Generate Info.plist
+ File infoPlistFile = new File(contentsDirectory, "Info.plist");
+ infoPlistFile.createNewFile();
+ writeInfoPlist(infoPlistFile);
+
+ // Generate PkgInfo
+ File pkgInfoFile = new File(contentsDirectory, "PkgInfo");
+ pkgInfoFile.createNewFile();
+ writePkgInfo(pkgInfoFile);
+
+ // Copy executable to MacOS folder
+ File executableFile = new File(macOSDirectory, executableName);
+ copy(getClass().getResource(EXECUTABLE_NAME), executableFile);
+
+ executableFile.setExecutable(true, false);
+
+ // Copy localized resources to Resources folder
+ copyResources(resourcesDirectory);
+
+ // Copy runtime to PlugIns folder
+ copyRuntime(plugInsDirectory);
+
+ // Copy class path entries to Java folder
+ copyClassPathEntries(javaDirectory);
+
+ // Copy class path ref entries to Java folder
+ copyClassPathRefEntries(javaDirectory);
+
+ // Copy library path entries to MacOS folder
+ copyLibraryPathEntries(macOSDirectory);
+
+ // Copy icon to Resources folder
+ copyIcon(resourcesDirectory);
+ } catch (IOException exception) {
+ throw new BuildException(exception);
+ }
+ }
+
+ private void copyResources(File resourcesDirectory) throws IOException {
+ // Unzip res.zip into resources directory
+ InputStream inputStream = getClass().getResourceAsStream("res.zip");
+ ZipInputStream zipInputStream = new ZipInputStream(inputStream);
+
+ try {
+ ZipEntry zipEntry = zipInputStream.getNextEntry();
+ while (zipEntry != null) {
+ File file = new File(resourcesDirectory, zipEntry.getName());
+
+ if (zipEntry.isDirectory()) {
+ file.mkdir();
+ } else {
+ OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file), BUFFER_SIZE);
+
+ try {
+ int b = zipInputStream.read();
+ while (b != -1) {
+ outputStream.write(b);
+ b = zipInputStream.read();
+ }
+
+ outputStream.flush();
+ } finally {
+ outputStream.close();
+ }
+
+ }
+
+ zipEntry = zipInputStream.getNextEntry();
+ }
+ } finally {
+ zipInputStream.close();
+ }
+ }
+
+ private void copyRuntime(File plugInsDirectory) throws IOException {
+ if (runtime != null) {
+ File runtimeHomeDirectory = runtime.getDir();
+ File runtimeContentsDirectory = runtimeHomeDirectory.getParentFile();
+ File runtimeDirectory = runtimeContentsDirectory.getParentFile();
+
+ // Create root plug-in directory
+ File pluginDirectory = new File(plugInsDirectory, runtimeDirectory.getName());
+ pluginDirectory.mkdir();
+
+ // Create Contents directory
+ File pluginContentsDirectory = new File(pluginDirectory, runtimeContentsDirectory.getName());
+ pluginContentsDirectory.mkdir();
+
+ // Copy MacOS directory
+ File runtimeMacOSDirectory = new File(runtimeContentsDirectory, "MacOS");
+ copy(runtimeMacOSDirectory, new File(pluginContentsDirectory, runtimeMacOSDirectory.getName()));
+
+ // Copy Info.plist file
+ File runtimeInfoPlistFile = new File(runtimeContentsDirectory, "Info.plist");
+ copy(runtimeInfoPlistFile, new File(pluginContentsDirectory, runtimeInfoPlistFile.getName()));
+
+ // Copy included contents of Home directory
+ File pluginHomeDirectory = new File(pluginContentsDirectory, runtimeHomeDirectory.getName());
+
+ DirectoryScanner directoryScanner = runtime.getDirectoryScanner(getProject());
+ String[] includedFiles = directoryScanner.getIncludedFiles();
+
+ for (int i = 0; i < includedFiles.length; i++) {
+ String includedFile = includedFiles[i];
+ File source = new File(runtimeHomeDirectory, includedFile);
+ File destination = new File(pluginHomeDirectory, includedFile);
+ copy(source, destination);
+ }
+ }
+ }
+
+ private void copyClassPathRefEntries(File javaDirectory) throws IOException {
+ if(classPathRef != null) {
+ org.apache.tools.ant.types.Path classpath =
+ (org.apache.tools.ant.types.Path) classPathRef.getReferencedObject(getProject());
+
+ Iterator iter = (Iterator)(Object)classpath.iterator();
+ while(iter.hasNext()) {
+ FileResource resource = iter.next();
+ File source = resource.getFile();
+ File destination = new File(javaDirectory, source.getName());
+ copy(source, destination);
+ }
+ }
+ }
+
+ private void copyClassPathEntries(File javaDirectory) throws IOException {
+ for (FileSet fileSet : classPath) {
+ File classPathDirectory = fileSet.getDir();
+ DirectoryScanner directoryScanner = fileSet.getDirectoryScanner(getProject());
+ String[] includedFiles = directoryScanner.getIncludedFiles();
+
+ for (int i = 0; i < includedFiles.length; i++) {
+ String includedFile = includedFiles[i];
+ File source = new File(classPathDirectory, includedFile);
+ File destination = new File(javaDirectory, new File(includedFile).getName());
+ copy(source, destination);
+ }
+ }
+ }
+
+ private void copyLibraryPathEntries(File macOSDirectory) throws IOException {
+ for (FileSet fileSet : libraryPath) {
+ File libraryPathDirectory = fileSet.getDir();
+ DirectoryScanner directoryScanner = fileSet.getDirectoryScanner(getProject());
+ String[] includedFiles = directoryScanner.getIncludedFiles();
+
+ for (int i = 0; i < includedFiles.length; i++) {
+ String includedFile = includedFiles[i];
+ File source = new File(libraryPathDirectory, includedFile);
+ File destination = new File(macOSDirectory, new File(includedFile).getName());
+ copy(source, destination);
+ }
+ }
+ }
+
+ private void copyIcon(File resourcesDirectory) throws IOException {
+ if (icon == null) {
+ copy(getClass().getResource(DEFAULT_ICON_NAME), new File(resourcesDirectory, DEFAULT_ICON_NAME));
+ } else {
+ copy(icon, new File(resourcesDirectory, icon.getName()));
+ }
+ }
+
+ private void writeInfoPlist(File file) throws IOException {
+ Writer out = new BufferedWriter(new FileWriter(file));
+ XMLOutputFactory output = XMLOutputFactory.newInstance();
+
+ try {
+ XMLStreamWriter xout = output.createXMLStreamWriter(out);
+
+ // Write XML declaration
+ xout.writeStartDocument();
+ xout.writeCharacters("\n");
+
+ // Write plist DTD declaration
+ xout.writeDTD(PLIST_DTD);
+ xout.writeCharacters("\n");
+
+ // Begin root element
+ xout.writeStartElement(PLIST_TAG);
+ xout.writeAttribute(PLIST_VERSION_ATTRIBUTE, "1.0");
+ xout.writeCharacters("\n");
+
+ // Begin root dictionary
+ xout.writeStartElement(DICT_TAG);
+ xout.writeCharacters("\n");
+
+ // Write bundle properties
+ writeProperty(xout, "CFBundleDevelopmentRegion", "English");
+ writeProperty(xout, "CFBundleExecutable", executableName);
+ writeProperty(xout, "CFBundleIconFile", (icon == null) ? DEFAULT_ICON_NAME : icon.getName());
+ writeProperty(xout, "CFBundleIdentifier", identifier);
+ writeProperty(xout, "CFBundleDisplayName", displayName);
+ writeProperty(xout, "CFBundleInfoDictionaryVersion", "6.0");
+ writeProperty(xout, "CFBundleName", name);
+ writeProperty(xout, "CFBundlePackageType", OS_TYPE_CODE);
+ writeProperty(xout, "CFBundleShortVersionString", shortVersion);
+ writeProperty(xout, "CFBundleVersion", version);
+ writeProperty(xout, "CFBundleSignature", signature);
+ writeProperty(xout, "NSHumanReadableCopyright", copyright);
+
+ if (applicationCategory != null) {
+ writeProperty(xout, "LSApplicationCategoryType", applicationCategory);
+ }
+
+ if (highResolutionCapable) {
+ writeKey(xout, "NSHighResolutionCapable");
+ writeBoolean(xout, true);
+ xout.writeCharacters("\n");
+ }
+
+ // Write runtime
+ if (runtime != null) {
+ writeProperty(xout, "JVMRuntime", runtime.getDir().getParentFile().getParentFile().getName());
+ }
+
+ if ( privileged != null ) {
+ writeProperty(xout, "JVMRunPrivileged", privileged);
+ }
+
+ if ( workingDirectory != null ) {
+ writeProperty(xout, "WorkingDirectory", workingDirectory);
+ }
+
+ // Write main class name
+ writeProperty(xout, "JVMMainClassName", mainClassName);
+
+
+ // Write CFBundleDocument entries
+ writeKey(xout, "CFBundleDocumentTypes");
+
+ xout.writeStartElement(ARRAY_TAG);
+ xout.writeCharacters("\n");
+
+ for(BundleDocument bundleDocument: bundleDocuments) {
+ xout.writeStartElement(DICT_TAG);
+ xout.writeCharacters("\n");
+
+ writeKey(xout, "CFBundleTypeExtensions");
+ xout.writeStartElement(ARRAY_TAG);
+ xout.writeCharacters("\n");
+ for(String extension : bundleDocument.getExtensions()) {
+ writeString(xout, extension);
+ }
+ xout.writeEndElement();
+ xout.writeCharacters("\n");
+
+ if(bundleDocument.hasIcon()) {
+ writeKey(xout, "CFBundleTypeIconFile");
+ writeString(xout, bundleDocument.getIcon());
+ }
+
+ writeKey(xout, "CFBundleTypeName");
+ writeString(xout, bundleDocument.getName());
+
+ writeKey(xout, "CFBundleTypeRole");
+ writeString(xout, bundleDocument.getRole());
+
+ writeKey(xout, "LSTypeIsPackage");
+ writeBoolean(xout, bundleDocument.isPackage());
+
+ xout.writeEndElement();
+ xout.writeCharacters("\n");
+ }
+
+ xout.writeEndElement();
+ xout.writeCharacters("\n");
+
+ // Write architectures
+ writeKey(xout, "LSArchitecturePriority");
+
+ xout.writeStartElement(ARRAY_TAG);
+ xout.writeCharacters("\n");
+
+ for (String architecture : architectures) {
+ writeString(xout, architecture);
+ }
+
+ xout.writeEndElement();
+ xout.writeCharacters("\n");
+
+ // Write Environment
+ writeKey(xout, "LSEnvironment");
+ xout.writeStartElement(DICT_TAG);
+ xout.writeCharacters("\n");
+ writeKey(xout, "LC_CTYPE");
+ writeString(xout, "UTF-8");
+ xout.writeEndElement();
+ xout.writeCharacters("\n");
+
+ // Write options
+ writeKey(xout, "JVMOptions");
+
+ xout.writeStartElement(ARRAY_TAG);
+ xout.writeCharacters("\n");
+
+ for (String option : options) {
+ writeString(xout, option);
+ }
+
+ xout.writeEndElement();
+ xout.writeCharacters("\n");
+
+ // Write arguments
+ writeKey(xout, "JVMArguments");
+
+ xout.writeStartElement(ARRAY_TAG);
+ xout.writeCharacters("\n");
+
+ for (String argument : arguments) {
+ writeString(xout, argument);
+ }
+
+ xout.writeEndElement();
+ xout.writeCharacters("\n");
+
+ // End root dictionary
+ xout.writeEndElement();
+ xout.writeCharacters("\n");
+
+ // End root element
+ xout.writeEndElement();
+ xout.writeCharacters("\n");
+
+ // Close document
+ xout.writeEndDocument();
+ xout.writeCharacters("\n");
+
+ out.flush();
+ } catch (XMLStreamException exception) {
+ throw new IOException(exception);
+ } finally {
+ out.close();
+ }
+ }
+
+ private void writeKey(XMLStreamWriter xout, String key) throws XMLStreamException {
+ xout.writeStartElement(KEY_TAG);
+ xout.writeCharacters(key);
+ xout.writeEndElement();
+ xout.writeCharacters("\n");
+ }
+
+ private void writeString(XMLStreamWriter xout, String value) throws XMLStreamException {
+ xout.writeStartElement(STRING_TAG);
+ xout.writeCharacters(value);
+ xout.writeEndElement();
+ xout.writeCharacters("\n");
+ }
+
+ private void writeBoolean(XMLStreamWriter xout, boolean value) throws XMLStreamException {
+ xout.writeEmptyElement(value ? "true" : "false");
+ }
+
+ private void writeProperty(XMLStreamWriter xout, String key, String value) throws XMLStreamException {
+ writeKey(xout, key);
+ writeString(xout, value);
+ }
+
+ private void writePkgInfo(File file) throws IOException {
+ Writer out = new BufferedWriter(new FileWriter(file));
+
+ try {
+ out.write(OS_TYPE_CODE + signature);
+ out.flush();
+ } finally {
+ out.close();
+ }
+ }
+
+ private static void delete(File file) throws IOException {
+ Path filePath = file.toPath();
+
+ if (Files.exists(filePath, LinkOption.NOFOLLOW_LINKS)) {
+ if (Files.isDirectory(filePath, LinkOption.NOFOLLOW_LINKS)) {
+ File[] files = file.listFiles();
+
+ for (int i = 0; i < files.length; i++) {
+ delete(files[i]);
+ }
+ }
+
+ Files.delete(filePath);
+ }
+ }
+
+ private static void copy(URL location, File file) throws IOException {
+ try (InputStream in = location.openStream()) {
+ Files.copy(in, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+
+ private static void copy(File source, File destination) throws IOException {
+ Path sourcePath = source.toPath();
+ Path destinationPath = destination.toPath();
+
+ destination.getParentFile().mkdirs();
+
+ Files.copy(sourcePath, destinationPath, StandardCopyOption.REPLACE_EXISTING, LinkOption.NOFOLLOW_LINKS);
+
+ if (Files.isDirectory(sourcePath, LinkOption.NOFOLLOW_LINKS)) {
+ String[] files = source.list();
+
+ for (int i = 0; i < files.length; i++) {
+ String file = files[i];
+ copy(new File(source, file), new File(destination, file));
+ }
+ }
+ }
+}
diff --git a/build/macosx/appbundler/src/com/oracle/appbundler/Architecture.java b/build/macosx/appbundler/src/com/oracle/appbundler/Architecture.java
new file mode 100644
index 000000000..8c1d3d2b4
--- /dev/null
+++ b/build/macosx/appbundler/src/com/oracle/appbundler/Architecture.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012, The Infinite Kind and/or its affiliates. All rights reserved.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. The Infinite Kind designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by The Infinite Kind in the LICENSE file that accompanied this code.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+package com.oracle.appbundler;
+
+/**
+ * Class representing an architecture that will be written in the Info.plist file
+ * to indicate which architectures the binary support.
+ */
+public class Architecture {
+ private String name = null;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/build/macosx/appbundler/src/com/oracle/appbundler/Argument.java b/build/macosx/appbundler/src/com/oracle/appbundler/Argument.java
new file mode 100644
index 000000000..352c19c23
--- /dev/null
+++ b/build/macosx/appbundler/src/com/oracle/appbundler/Argument.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012, Oracle and/or its affiliates. All rights reserved.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.appbundler;
+
+/**
+ * Class representing an argument that will be passed to the Java application
+ * at startup.
+ */
+public class Argument {
+ private String value = null;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+}
diff --git a/build/macosx/appbundler/src/com/oracle/appbundler/BundleDocument.java b/build/macosx/appbundler/src/com/oracle/appbundler/BundleDocument.java
new file mode 100644
index 000000000..1687ec397
--- /dev/null
+++ b/build/macosx/appbundler/src/com/oracle/appbundler/BundleDocument.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2012, The Infinite Kind and/or its affiliates. All rights reserved.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. The Infinite Kind designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by The Infinite Kind in the LICENSE file that accompanied this code.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+package com.oracle.appbundler;
+
+import java.io.File;
+import org.apache.tools.ant.BuildException;
+
+
+/**
+ * Represent a CFBundleDocument.
+ */
+public class BundleDocument {
+ private String name = "editor";
+ private String role = "";
+ private String icon = null;
+ private String[] extensions;
+ private boolean isPackage = false;
+
+ private String capitalizeFirst(String string) {
+ char[] stringArray = string.toCharArray();
+ stringArray[0] = Character.toUpperCase(stringArray[0]);
+ return new String(stringArray);
+ }
+
+ public void setExtensions(String extensionsList) {
+ if(extensionsList == null) {
+ throw new BuildException("Extensions can't be null");
+ }
+
+ extensions = extensionsList.split(",");
+ for (String extension : extensions) {
+ extension.trim().toLowerCase();
+ }
+ }
+
+ public void setIcon(String icon) {
+ this.icon = icon;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setRole(String role) {
+ this.role = capitalizeFirst(role);
+ }
+
+ public void setIsPackage(String isPackageString) {
+ if(isPackageString.trim().equalsIgnoreCase("true")) {
+ this.isPackage = true;
+ } else {
+ this.isPackage = false;
+ }
+ }
+
+ public String getIcon() {
+ return icon;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getRole() {
+ return role;
+ }
+
+ public String[] getExtensions() {
+ return extensions;
+ }
+
+ public boolean hasIcon() {
+ return icon != null;
+ }
+
+ public boolean isPackage() {
+ return isPackage;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder(getName());
+ s.append(" ").append(getRole()).append(" ").append(getIcon()). append(" ");
+ for(String extension : extensions) {
+ s.append(extension).append(" ");
+ }
+
+ return s.toString();
+ }
+}
diff --git a/build/macosx/appbundler/src/com/oracle/appbundler/GenericApp.icns b/build/macosx/appbundler/src/com/oracle/appbundler/GenericApp.icns
new file mode 100644
index 000000000..20d16f39a
Binary files /dev/null and b/build/macosx/appbundler/src/com/oracle/appbundler/GenericApp.icns differ
diff --git a/build/macosx/appbundler/src/com/oracle/appbundler/Option.java b/build/macosx/appbundler/src/com/oracle/appbundler/Option.java
new file mode 100644
index 000000000..d9eaf3f5e
--- /dev/null
+++ b/build/macosx/appbundler/src/com/oracle/appbundler/Option.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012, Oracle and/or its affiliates. All rights reserved.
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.appbundler;
+
+/**
+ * Class representing an option that will be passed to the JVM at startup.
+ */
+public class Option {
+ private String value = null;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+}
diff --git a/core/todo.txt b/core/todo.txt
index 43a27d0e7..2adde90e2 100644
--- a/core/todo.txt
+++ b/core/todo.txt
@@ -3,6 +3,8 @@ X background color for present mode has no effect
X https://github.com/processing/processing/issues/2071
X https://github.com/processing/processing/pull/2072
+_ Sort out blending differences with P2D/P3D
+_ https://github.com/processing/processing/issues/1844
high
_ loadPixels doesn't set alpha value for pixels on Java2D