--- /dev/null 2012-02-13 16:10:32.000000000 -0500 +++ new/src/macosx/bundle/appbundler/src/com/oracle/appbundler/AppBundlerTask.java 2012-02-13 16:10:32.000000000 -0500 @@ -0,0 +1,485 @@ +/* + * 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.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +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 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; + +/** + * 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 shortVersion = "1.0"; + private String signature = "????"; + private String copyright = ""; + + // JVM info properties + private File runtime = null; + private String mainClassName = null; + private ArrayList classPath = new ArrayList<>(); + private ArrayList nativeLibraries = new ArrayList<>(); + private ArrayList options = new ArrayList<>(); + private ArrayList arguments = new ArrayList<>(); + + public static final String EXECUTABLE_NAME = "JavaAppLauncher"; + public static final String DEFAULT_ICON_NAME = "GenericApp.icns"; + public static final String OS_TYPE_CODE = "APPL"; + public static final String CLASS_EXTENSION = ".class"; + + public static final String PLIST_DTD = ""; + public static final String PLIST_TAG = "plist"; + public static final String PLIST_VERSION_ATTRIBUTE = "version"; + public static final String DICT_TAG = "dict"; + public static final String KEY_TAG = "key"; + public static final String ARRAY_TAG = "array"; + public static final String STRING_TAG = "string"; + + public static final int BUFFER_SIZE = 1024; + + 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 setShortVersion(String shortVersion) { + this.shortVersion = shortVersion; + } + + public void setSignature(String signature) { + this.signature = signature; + } + + public void setCopyright(String copyright) { + this.copyright = copyright; + } + + public File getRuntime() { + return runtime; + } + + public void setRuntime(File runtime) { + this.runtime = runtime; + } + + public void setMainClassName(String mainClassName) { + this.mainClassName = mainClassName; + } + + public void addConfiguredClassPath(FileSet classPath) { + File parent = classPath.getDir(); + + DirectoryScanner directoryScanner = classPath.getDirectoryScanner(getProject()); + String[] includedFiles = directoryScanner.getIncludedFiles(); + + for (int i = 0; i < includedFiles.length; i++) { + this.classPath.add(new File(parent, includedFiles[i])); + } + } + + public void addNativeLibrary(File nativeLibrary) throws BuildException { + if (nativeLibrary.isDirectory()) { + throw new BuildException("Native library cannot be a directory."); + } + + nativeLibraries.add(nativeLibrary); + } + + 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); + } + + @Override + public void execute() throws BuildException { + // Validate required properties + if (outputDirectory == null) { + throw new IllegalStateException("Destination directory is required."); + } + + if (!outputDirectory.exists()) { + throw new IllegalStateException("Destination directory does not exist."); + } + + if (!outputDirectory.isDirectory()) { + throw new IllegalStateException("Invalid destination 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 (runtime != null) { + if (!runtime.exists()) { + throw new IllegalStateException("Runtime does not exist."); + } + + if (!runtime.isDirectory()) { + throw new IllegalStateException("Invalid runtime."); + } + } + + if (mainClassName == null) { + throw new IllegalStateException("Main class name is required."); + } + + if (classPath.isEmpty()) { + throw new IllegalStateException("Class path is required."); + } + + // Create directory structure + try { + System.out.println("Creating app bundle: " + name); + + 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, "JavaVM"); + javaDirectory.mkdir(); + + File classesDirectory = new File(javaDirectory, "Classes"); + classesDirectory.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, EXECUTABLE_NAME); + copy(getClass().getResource(EXECUTABLE_NAME), executableFile); + + executableFile.setExecutable(true); + + // Copy runtime to PlugIns folder (if specified) + if (runtime != null) { + copy(runtime, new File(plugInsDirectory, runtime.getName())); + } + + // Copy class path entries to Java folder + for (File entry : classPath) { + String name = entry.getName(); + + if (entry.isDirectory() || name.endsWith(CLASS_EXTENSION)) { + copy(entry, new File(classesDirectory, name)); + } else { + copy(entry, new File(javaDirectory, name)); + } + } + + // Copy native libraries to Java folder + for (File nativeLibrary : nativeLibraries) { + copy(nativeLibrary, new File(javaDirectory, nativeLibrary.getName())); + } + + // Copy icon to Resources folder + if (icon == null) { + copy(getClass().getResource(DEFAULT_ICON_NAME), new File(resourcesDirectory, + DEFAULT_ICON_NAME)); + } else { + copy(icon, new File(resourcesDirectory, icon.getName())); + } + } catch (IOException exception) { + throw new BuildException(exception); + } + } + + 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", EXECUTABLE_NAME); + 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, "CFBundleSignature", signature); + writeProperty(xout, "CFBundleVersion", "1"); + writeProperty(xout, "NSHumanReadableCopyright", copyright); + + // Start Java properties + writeKey(xout, "JavaVM"); + xout.writeStartElement(DICT_TAG); + + // Write runtime + writeProperty(xout, "Runtime", runtime.getName()); + + // Write main class name + writeProperty(xout, "MainClassName", mainClassName); + + // Write options + writeKey(xout, "Options"); + + xout.writeStartElement(ARRAY_TAG); + xout.writeCharacters("\n"); + + for (String option : options) { + writeString(xout, option); + } + + xout.writeEndElement(); + xout.writeCharacters("\n"); + + // Write arguments + writeKey(xout, "Arguments"); + + xout.writeStartElement(ARRAY_TAG); + xout.writeCharacters("\n"); + + for (String argument : arguments) { + writeString(xout, argument); + } + + xout.writeEndElement(); + xout.writeCharacters("\n"); + + // End Java properties + 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 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(); + + 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)); + } + } + } +}