/* * Write the application bundle file: Info.plist * * * Copyright (c) 2003, 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 net.sourceforge.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; import net.sourceforge.jarbundler.AppBundleProperties; // 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.Hashtable; import java.util.Iterator; import java.util.List; // Java language imports import java.lang.Boolean; import java.lang.ClassCastException; import java.lang.Double; import java.lang.String; import java.lang.System; import java.util.Map; // 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; import org.w3c.dom.Attr; /** * 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); // Finder 'Get Info' writeKeyStringPair("CFBundleGetInfoString", bundleProperties.getCFBundleGetInfoString(), 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); // IsAgent, optional if ( bundleProperties.getLSUIElement() != null ) writeKeyBooleanPair( "LSUIElement", bundleProperties.getLSUIElement(), dict ); // Document Types, optional List documentTypes = bundleProperties.getDocumentTypes(); if (documentTypes.size() > 0) writeDocumentTypes(documentTypes, dict); // Java entries in the plist dictionary if (bundleProperties.getJavaVersion() < 1.7) { // Apple Java Version writeKey("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); } } // 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 ); } }