/*
* Copyright (c) 2015, UltraMixer Digital Audio Solutions <info@ultramixer.com>, Seth J. Morabito <sethm@loomcom.com>
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ultramixer.jarbundler;
// This package's imports
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
// Java I/O
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
// Java Utility
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
// Java language imports
import java.lang.Boolean;
import java.lang.Double;
import java.lang.String;
import java.lang.System;
// Apache Ant
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.util.FileUtils;
// Java XML DOM creation
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
// W3C DOM
import org.w3c.dom.Document;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
/**
* Write out a Java application bundle property list file. For descriptions of
* the property list keys, see <a
* href="http://developer.apple.com/documentation/MacOSX/Conceptual/BPRuntimeConfig/Articles/PListKeys.html"
* >Apple docs</a>.
*/
public class PropertyListWriter
{
// Our application bundle properties
private AppBundleProperties bundleProperties;
// DOM version of Info.plist file
private Document document = null;
private FileUtils fileUtils = FileUtils.getFileUtils();
/**
* Create a new Property List writer.
*/
public PropertyListWriter(AppBundleProperties bundleProperties)
{
this.bundleProperties = bundleProperties;
}
public void writeFile(File fileName) throws BuildException
{
Writer writer = null;
try
{
this.document = createDOM();
buildDOM();
TransformerFactory transFactory = TransformerFactory.newInstance();
Transformer trans = transFactory.newTransformer();
trans.setOutputProperty(OutputKeys.INDENT, "yes");
trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
trans.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "-//Apple Computer//DTD PLIST 1.0//EN");
trans.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "http://www.apple.com/DTDs/PropertyList-1.0.dtd");
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName), "UTF-8"));
trans.transform(new DOMSource(document), new StreamResult(writer));
}
catch (TransformerConfigurationException tce)
{
throw new BuildException(tce);
}
catch (TransformerException te)
{
throw new BuildException(te);
}
catch (ParserConfigurationException pce)
{
throw new BuildException(pce);
}
catch (IOException ex)
{
throw new BuildException("Unable to write \"" + fileName + "\"");
}
finally
{
fileUtils.close(writer);
}
}
private Document createDOM() throws ParserConfigurationException
{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = factory.newDocumentBuilder();
DOMImplementation domImpl = documentBuilder.getDOMImplementation();
// We needed to reference using the full class name here because we already have
// a class named "DocumentType"
org.w3c.dom.DocumentType doctype = domImpl.createDocumentType(
"plist",
"-//Apple Computer//DTD PLIST 1.0//EN",
"http://www.apple.com/DTDs/PropertyList-1.0.dtd");
return domImpl.createDocument(null, "plist", doctype);
}
private void buildDOM()
{
Element plist = this.document.getDocumentElement();
plist.setAttribute("version", "1.0");
// Open the top level dictionary, <dict>
Node dict = createNode("dict", plist);
// Application short name i.e. About menu name
writeKeyStringPair("CFBundleName", bundleProperties.getCFBundleName(), dict);
// Finder 'Version' label, defaults to "1.0"
writeKeyStringPair("CFBundleShortVersionString", bundleProperties.getCFBundleShortVersionString(), dict);
// Mac OS X required key, defaults to "false"
writeKeyStringPair("CFBundleAllowMixedLocalizations",
(bundleProperties.getCFBundleAllowMixedLocalizations() ? "true" : "false"), dict);
// Mac OS X required, defaults to "6.0"
writeKeyStringPair("CFBundleInfoDictionaryVersion",
bundleProperties.getCFBundleInfoDictionaryVersion(), dict);
// Bundle Executable name, required, defaults to "JavaApplicationStub"
writeKeyStringPair("CFBundleExecutable", bundleProperties.getCFBundleExecutable(), dict);
// Bundle Development Region, required, defaults to "English"
writeKeyStringPair("CFBundleDevelopmentRegion", bundleProperties.getCFBundleDevelopmentRegion(), dict);
// Bundle Package Type, required, defaults tp "APPL"
writeKeyStringPair("CFBundlePackageType", bundleProperties.getCFBundlePackageType(), dict);
// Bundle Signature, required, defaults tp "????"
writeKeyStringPair("CFBundleSignature", bundleProperties.getCFBundleSignature(), dict);
// Application build number, optional
if (bundleProperties.getCFBundleVersion() != null)
{
writeKeyStringPair("CFBundleVersion", bundleProperties.getCFBundleVersion(), dict);
}
// Application Icon file, optional
if (bundleProperties.getCFBundleIconFile() != null)
{
writeKeyStringPair("CFBundleIconFile", bundleProperties.getCFBundleIconFile(), dict);
}
// Bundle Identifier, optional
if (bundleProperties.getCFBundleIdentifier() != null)
{
writeKeyStringPair("CFBundleIdentifier", bundleProperties.getCFBundleIdentifier(), dict);
}
// Help Book Folder, optional
if (bundleProperties.getCFBundleHelpBookFolder() != null)
{
writeKeyStringPair("CFBundleHelpBookFolder", bundleProperties.getCFBundleHelpBookFolder(), dict);
}
// Help Book Name, optional
if (bundleProperties.getCFBundleHelpBookName() != null)
{
writeKeyStringPair("CFBundleHelpBookName", bundleProperties.getCFBundleHelpBookName(), dict);
}
// Copyright, optional
if (bundleProperties.getNSHumanReadableCopyright() != null)
{
writeKeyStringPair("NSHumanReadableCopyright", bundleProperties.getNSHumanReadableCopyright(), dict);
}
// HiRes capability, optional
if ( bundleProperties.getNSHighResolutionCapable() != false )
writeKeyBooleanPair( "NSHighResolutionCapable", bundleProperties.getNSHighResolutionCapable(), dict );
// Content size, optional
if ( bundleProperties.getNSPreferencesContentSize() != null )
writeKeyStringPair( "NSPreferencesContentSize", "{" + bundleProperties.getNSPreferencesContentSize() + "}", dict );
// IsAgent, optional
if (bundleProperties.getLSUIElement() != null)
{
writeKeyBooleanPair("LSUIElement", bundleProperties.getLSUIElement(), dict);
}
// LSApplicationCategoryType, optional
//new since 08/05/2015 by Tobias Bley / UltraMixer
if (bundleProperties.getLSApplicationCategoryType() != null)
{
writeKeyStringPair("LSApplicationCategoryType", bundleProperties.getLSApplicationCategoryType(), dict);
}
//New since 08/05/2015 Tobias Bley / UltraMixer
//LSEnvironemnt dict node
if (bundleProperties.getLSEnvironment() != null && bundleProperties.getLSEnvironment().keySet().size() > 0)
{
writeKey("LSEnvironment", dict);
Node lsEnvironmentDict = createNode("dict", dict);
// Main class, required
Enumeration keys = bundleProperties.getLSEnvironment().keys();
while(keys.hasMoreElements())
{
String key = (String) keys.nextElement();
writeKeyStringPair(key, (String) bundleProperties.getLSEnvironment().get(key), lsEnvironmentDict);
}
}
// Document Types, optional
List documentTypes = bundleProperties.getDocumentTypes();
if (documentTypes.size() > 0)
{
writeDocumentTypes(documentTypes, dict);
}
// Java / JavaX entries in the plist dictionary
if (bundleProperties.getJavaVersion() < 1.7)
{
// Apple Java Version
writeKey(bundleProperties.getJavaXKey() ? "JavaX" : "Java", dict);
Node javaDict = createNode("dict", dict);
// Main class, required
writeKeyStringPair("MainClass", bundleProperties.getMainClass(), javaDict);
// Target JVM version, optional but recommended
if (bundleProperties.getJVMVersion() != null)
{
writeKeyStringPair("JVMVersion", bundleProperties.getJVMVersion(), javaDict);
}
// New in JarBundler 2.2.0; Tobias Bley ---------------------------------
// JVMArchs, optional
List jvmArchs = bundleProperties.getJVMArchs();
if (jvmArchs != null && !jvmArchs.isEmpty())
{
writeJVMArchs(jvmArchs, javaDict);
}
// lsArchitecturePriority, optional
List lsArchitecturePriority = bundleProperties.getLSArchitecturePriority();
if (lsArchitecturePriority != null && !lsArchitecturePriority.isEmpty())
{
writeLSArchitecturePriority(lsArchitecturePriority, javaDict);
}
//-----------------------------------------------------------------------
// Classpath is composed of two types, required
// 1: Jars bundled into the JAVA_ROOT of the application
// 2: External directories or files with an absolute path
List classPath = bundleProperties.getClassPath();
List extraClassPath = bundleProperties.getExtraClassPath();
if ((classPath.size() > 0) || (extraClassPath.size() > 0))
{
writeClasspath(classPath, extraClassPath, javaDict);
}
// JVM options, optional
if (bundleProperties.getVMOptions() != null)
{
writeKeyStringPair("VMOptions", bundleProperties.getVMOptions(), javaDict);
}
// Working directory, optional
if (bundleProperties.getWorkingDirectory() != null)
{
writeKeyStringPair("WorkingDirectory", bundleProperties.getWorkingDirectory(), javaDict);
}
// StartOnMainThread, optional
if (bundleProperties.getStartOnMainThread() != null)
{
writeKey("StartOnMainThread", javaDict);
createNode(bundleProperties.getStartOnMainThread().toString(), javaDict);
}
// SplashFile, optional
if (bundleProperties.getSplashFile() != null)
{
writeKeyStringPair("SplashFile", bundleProperties.getSplashFile(), javaDict);
}
// Main class arguments, optional
if (bundleProperties.getArguments() != null)
{
writeKeyStringPair("Arguments", bundleProperties.getArguments(), javaDict);
}
// Java properties, optional
Hashtable javaProperties = bundleProperties.getJavaProperties();
if (javaProperties.isEmpty() == false)
{
writeJavaProperties(javaProperties, javaDict);
}
}
else
{
// Oracle Java Version
// Main class, required
writeKeyStringPair("JVMMainClassName", bundleProperties.getMainClass(), dict);
// Main class arguments, optional
if (bundleProperties.getArguments() != null)
{
writeKey("JVMArguments", dict);
writeArray(Arrays.asList(bundleProperties.getArguments().split("\\s+")), dict);
}
// JVM options and Java properties, optional
if ((bundleProperties.getVMOptions() != null) || !bundleProperties.getJavaProperties().isEmpty())
{
writeKey("JVMOptions", dict);
List<String> jvmOptions = new ArrayList<String>();
if (bundleProperties.getVMOptions() != null)
{
jvmOptions.addAll(Arrays.asList(bundleProperties.getVMOptions().split("\\s+")));
}
Iterator javaPropertiesIterator = bundleProperties.getJavaProperties().entrySet().iterator();
while (javaPropertiesIterator.hasNext())
{
Map.Entry entry = (Map.Entry) javaPropertiesIterator.next();
if (((String) entry.getKey()).startsWith("com.apple."))
{
System.out.println("Deprecated as of 1.4: " + entry.getKey());
continue;
}
jvmOptions.add("-D" + entry.getKey() + '=' + entry.getValue());
}
writeArray(jvmOptions, dict);
}
}
//by Tobias Bley / UltraMixer
writeKeyStringPair("SUFeedURL", bundleProperties.getSUFeedURL(), dict);
//Sparkle Properties
//new since 08/05/2015 by Tobias Bley / UltraMixer
writeKeyStringPair("SUPublicDSAKeyFile", bundleProperties.getSUPublicDSAKeyFile(), dict);
// Services, optional
List services = bundleProperties.getServices();
if (services.size() > 0)
{
writeServices(services, dict);
}
}
private void writeDocumentTypes(List documentTypes, Node appendTo)
{
writeKey("CFBundleDocumentTypes", appendTo);
Node array = createNode("array", appendTo);
Iterator itor = documentTypes.iterator();
while (itor.hasNext())
{
DocumentType documentType = (DocumentType) itor.next();
Node documentDict = createNode("dict", array);
writeKeyStringPair("CFBundleTypeName", documentType.getName(), documentDict);
writeKeyStringPair("CFBundleTypeRole", documentType.getRole(), documentDict);
File iconFile = documentType.getIconFile();
if (iconFile != null)
{
writeKeyStringPair("CFBundleTypeIconFile", iconFile.getName(), documentDict);
}
List extensions = documentType.getExtensions();
if (extensions.isEmpty() == false)
{
writeKey("CFBundleTypeExtensions", documentDict);
writeArray(extensions, documentDict);
}
List osTypes = documentType.getOSTypes();
if (osTypes.isEmpty() == false)
{
writeKey("CFBundleTypeOSTypes", documentDict);
writeArray(osTypes, documentDict);
}
List mimeTypes = documentType.getMimeTypes();
if (mimeTypes.isEmpty() == false)
{
writeKey("CFBundleTypeMIMETypes", documentDict);
writeArray(mimeTypes, documentDict);
}
List UTIs = documentType.getUTIs();
if (UTIs.isEmpty() == false)
{
writeKey("LSItemContentTypes", documentDict);
writeArray(UTIs, documentDict);
}
// Only write this key if true
if (documentType.isBundle())
{
writeKeyStringPair("LSTypeIsPackage", "true", documentDict);
}
}
}
private void writeServices(List services, Node appendTo)
{
writeKey("NSServices", appendTo);
Node array = createNode("array", appendTo);
Iterator itor = services.iterator();
while (itor.hasNext())
{
Service service = (Service) itor.next();
Node serviceDict = createNode("dict", array);
String portName = service.getPortName();
if (portName == null)
{
portName = bundleProperties.getCFBundleName();
}
writeKeyStringPair("NSPortName", portName, serviceDict);
writeKeyStringPair("NSMessage", service.getMessage(), serviceDict);
List sendTypes = service.getSendTypes();
if (!sendTypes.isEmpty())
{
writeKey("NSSendTypes", serviceDict);
writeArray(sendTypes, serviceDict);
}
List returnTypes = service.getReturnTypes();
if (!returnTypes.isEmpty())
{
writeKey("NSReturnTypes", serviceDict);
writeArray(returnTypes, serviceDict);
}
writeKey("NSMenuItem", serviceDict);
Node menuItemDict = createNode("dict", serviceDict);
writeKeyStringPair("default", service.getMenuItem(), menuItemDict);
String keyEquivalent = service.getKeyEquivalent();
if (null != keyEquivalent)
{
writeKey("NSKeyEquivalent", serviceDict);
Node keyEquivalentDict = createNode("dict", serviceDict);
writeKeyStringPair("default", keyEquivalent, keyEquivalentDict);
}
String userData = service.getUserData();
if (null != userData)
{
writeKeyStringPair("NSUserData", userData, serviceDict);
}
String timeout = service.getTimeout();
if (null != timeout)
{
writeKeyStringPair("NSTimeout", timeout, serviceDict);
}
}
}
private void writeClasspath(List classpath, List extraClasspath, Node appendTo)
{
writeKey("ClassPath", appendTo);
classpath.addAll(extraClasspath);
writeArray(classpath, appendTo);
}
private void writeJavaProperties(Hashtable javaProperties, Node appendTo)
{
writeKey("Properties", appendTo);
Node propertiesDict = createNode("dict", appendTo);
for (Iterator i = javaProperties.keySet().iterator(); i.hasNext(); )
{
String key = (String) i.next();
if (key.startsWith("com.apple.") && (bundleProperties.getJavaVersion() >= 1.4))
{
System.out.println("Deprecated as of 1.4: " + key);
continue;
}
writeKeyStringPair(key, (String) javaProperties.get(key), propertiesDict);
}
}
// New in JarBundler 2.2.0; Tobias Bley ---------------------------------
private void writeJVMArchs(List jvmArchs, Node appendTo)
{
writeKey("JVMArchs", appendTo);
writeArray(jvmArchs, appendTo);
}
private void writeLSArchitecturePriority(List lsArchitecturePriority, Node appendTo)
{
writeKey("LSArchitecturePriority", appendTo);
writeArray(lsArchitecturePriority, appendTo);
}
//----------------------------------------------------------------------
private Node createNode(String tag, Node appendTo)
{
Node node = this.document.createElement(tag);
appendTo.appendChild(node);
return node;
}
private void writeKeyStringPair(String key, String string, Node appendTo)
{
if (string == null)
{
return;
}
writeKey(key, appendTo);
writeString(string, appendTo);
}
private void writeKeyBooleanPair(String key, Boolean b, Node appendTo)
{
if (b == null)
{
return;
}
writeKey(key, appendTo);
writeBoolean(b, appendTo);
}
private void writeKey(String key, Node appendTo)
{
Element keyNode = this.document.createElement("key");
appendTo.appendChild(keyNode);
keyNode.appendChild(this.document.createTextNode(key));
}
private void writeString(String string, Node appendTo)
{
Element stringNode = this.document.createElement("string");
stringNode.appendChild(this.document.createTextNode(string));
appendTo.appendChild(stringNode);
}
private void writeArray(List stringList, Node appendTo)
{
Node arrayNode = createNode("array", appendTo);
for (Iterator it = stringList.iterator(); it.hasNext(); )
{
writeString((String) it.next(), arrayNode);
}
}
private void writeBoolean(Boolean b, Node appendTo)
{
Element booleanNode = null;
if (b.booleanValue())
{
booleanNode = this.document.createElement("true");
}
else
{
booleanNode = this.document.createElement("false");
}
appendTo.appendChild(booleanNode);
}
}