001: /*
002: * Write the application bundle file: Info.plist
003: *
004: * Copyright (c) 2006, William A. Gilbert <gilbert@informagen.com> All rights reserved.
005: *
006: * This program is free software; you can redistribute it and/or modify it
007: * under the terms of the GNU General Public License as published by the Free
008: * Software Foundation; either version 2 of the License, or (at your option)
009: * any later version.
010: *
011: * This program is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
014: * more details.
015: *
016: * You should have received a copy of the GNU General Public License along with
017: * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
018: * Place - Suite 330, Boston, MA 02111-1307, USA.
019: */
020:
021: package net.sourceforge.jarbundler;
022:
023: // This package's imports
024: import net.sourceforge.jarbundler.AppBundleProperties;
025:
026: // Java I/O
027: import java.io.BufferedWriter;
028: import java.io.File;
029: import java.io.FileOutputStream;
030: import java.io.IOException;
031: import java.io.OutputStreamWriter;
032: import java.io.Writer;
033:
034: // Java Utility
035: import java.util.Hashtable;
036: import java.util.Iterator;
037: import java.util.List;
038:
039: // Java language imports
040: import java.lang.Boolean;
041: import java.lang.ClassCastException;
042: import java.lang.Double;
043: import java.lang.String;
044: import java.lang.System;
045:
046: // Apache Ant
047: import org.apache.tools.ant.BuildException;
048: import org.apache.tools.ant.util.FileUtils;
049:
050: // Java XML DOM creation
051: import javax.xml.parsers.DocumentBuilderFactory;
052: import javax.xml.parsers.DocumentBuilder;
053: import javax.xml.parsers.ParserConfigurationException;
054:
055: // W3C DOM
056: import org.w3c.dom.Document;
057: import org.w3c.dom.DOMImplementation;
058: import org.w3c.dom.Node;
059: import org.w3c.dom.Element;
060: import org.w3c.dom.Attr;
061:
062: // Xerces serializer
063: import org.apache.xml.serialize.OutputFormat;
064: import org.apache.xml.serialize.XMLSerializer;
065: import org.apache.xml.serialize.LineSeparator;
066:
067: /**
068: * Write out a Java application bundle property list file. For descriptions of
069: * the property list keys, see <a
070: * href="http://developer.apple.com/documentation/MacOSX/Conceptual/BPRuntimeConfig/Articles/PListKeys.html"
071: * >Apple docs</a>.
072: */
073:
074: public class PropertyListWriter {
075:
076: // Our application bundle properties
077: private AppBundleProperties bundleProperties;
078:
079: private double version = 1.3;
080:
081: // DOM version of Info.plist file
082: private Document document = null;
083:
084: private FileUtils fileUtils = FileUtils.getFileUtils();
085:
086: /**
087: * Create a new Property List writer.
088: */
089: public PropertyListWriter(AppBundleProperties bundleProperties) {
090: this .bundleProperties = bundleProperties;
091: setJavaVersion(bundleProperties.getJVMVersion());
092: }
093:
094: private void setJavaVersion(String version) {
095:
096: if (version == null)
097: return;
098:
099: this .version = Double.valueOf(version.substring(0, 3))
100: .doubleValue();
101: }
102:
103: public void writeFile(File fileName) throws BuildException {
104:
105: Writer writer = null;
106:
107: try {
108:
109: this .document = createDOM();
110: buildDOM();
111:
112: // Serialize the DOM into the writer
113: writer = new BufferedWriter(new OutputStreamWriter(
114: new FileOutputStream(fileName), "UTF-8"));
115: // Prettify the XML Two space indenting, no line wrapping
116: OutputFormat outputFormat = new OutputFormat();
117: outputFormat.setMethod("xml");
118: outputFormat.setIndenting(true);
119: outputFormat.setIndent(2);
120: outputFormat.setLineWidth(0);
121:
122: // Create a DOM serlializer and write the XML
123: XMLSerializer serializer = new XMLSerializer(writer,
124: outputFormat);
125: serializer.asDOMSerializer();
126: serializer.serialize(this .document);
127:
128: } catch (ParserConfigurationException pce) {
129: throw new BuildException(pce);
130: } catch (IOException ex) {
131: throw new BuildException("Unable to write \"" + fileName
132: + "\"");
133: } finally {
134: fileUtils.close(writer);
135: }
136:
137: }
138:
139: private Document createDOM() throws ParserConfigurationException {
140:
141: DocumentBuilderFactory factory = DocumentBuilderFactory
142: .newInstance();
143: DocumentBuilder documentBuilder = factory.newDocumentBuilder();
144: DOMImplementation domImpl = documentBuilder
145: .getDOMImplementation();
146:
147: // We needed to reference using the full class name here because we already have
148: // a class named "DocumentType"
149:
150: org.w3c.dom.DocumentType doctype = domImpl.createDocumentType(
151: "plist", "-//Apple Computer//DTD PLIST 1.0//EN",
152: "http://www.apple.com/DTDs/PropertyList-1.0.dtd");
153:
154: return domImpl.createDocument(null, "plist", doctype);
155: }
156:
157: private void buildDOM() {
158:
159: Element plist = this .document.getDocumentElement();
160: plist.setAttribute("version", "1.0");
161:
162: // Open the top level dictionary, <dict>
163:
164: Node dict = createNode("dict", plist);
165:
166: // Application short name i.e. About menu name
167: writeKeyStringPair("CFBundleName", bundleProperties
168: .getCFBundleName(), dict);
169:
170: // Finder 'Version' label, defaults to "1.0"
171: writeKeyStringPair("CFBundleShortVersionString",
172: bundleProperties.getCFBundleShortVersionString(), dict);
173:
174: // Finder 'Get Info'
175: writeKeyStringPair("CFBundleGetInfoString", bundleProperties
176: .getCFBundleGetInfoString(), dict);
177:
178: // Mac OS X required key, defaults to "false"
179: writeKeyStringPair(
180: "CFBundleAllowMixedLocalizations",
181: (bundleProperties.getCFBundleAllowMixedLocalizations() ? "true"
182: : "false"), dict);
183:
184: // Mac OS X required, defaults to "6.0"
185: writeKeyStringPair("CFBundleInfoDictionaryVersion",
186: bundleProperties.getCFBundleInfoDictionaryVersion(),
187: dict);
188:
189: // Bundle Executable name, required, defaults to "JavaApplicationStub"
190: writeKeyStringPair("CFBundleExecutable", bundleProperties
191: .getCFBundleExecutable(), dict);
192:
193: // Bundle Development Region, required, defaults to "English"
194: writeKeyStringPair("CFBundleDevelopmentRegion",
195: bundleProperties.getCFBundleDevelopmentRegion(), dict);
196:
197: // Bundle Package Type, required, defaults tp "APPL"
198: writeKeyStringPair("CFBundlePackageType", bundleProperties
199: .getCFBundlePackageType(), dict);
200:
201: // Bundle Signature, required, defaults tp "????"
202: writeKeyStringPair("CFBundleSignature", bundleProperties
203: .getCFBundleSignature(), dict);
204:
205: // Application build number, optional
206: if (bundleProperties.getCFBundleVersion() != null)
207: writeKeyStringPair("CFBundleVersion", bundleProperties
208: .getCFBundleVersion(), dict);
209:
210: // Application Icon file, optional
211: if (bundleProperties.getCFBundleIconFile() != null)
212: writeKeyStringPair("CFBundleIconFile", bundleProperties
213: .getCFBundleIconFile(), dict);
214:
215: // Bundle Identifier, optional
216: if (bundleProperties.getCFBundleIdentifier() != null)
217: writeKeyStringPair("CFBundleIdentifier", bundleProperties
218: .getCFBundleIdentifier(), dict);
219:
220: // Help Book Folder, optional
221: if (bundleProperties.getCFBundleHelpBookFolder() != null)
222: writeKeyStringPair("CFBundleHelpBookFolder",
223: bundleProperties.getCFBundleHelpBookFolder(), dict);
224:
225: // Help Book Name, optional
226: if (bundleProperties.getCFBundleHelpBookName() != null)
227: writeKeyStringPair("CFBundleHelpBookName", bundleProperties
228: .getCFBundleHelpBookName(), dict);
229:
230: // Document Types, optional
231: List documentTypes = bundleProperties.getDocumentTypes();
232:
233: if (documentTypes.size() > 0)
234: writeDocumentTypes(documentTypes, dict);
235:
236: // Java entry in the plist dictionary
237: writeKey("Java", dict);
238: Node javaDict = createNode("dict", dict);
239:
240: // Main class, required
241: writeKeyStringPair("MainClass",
242: bundleProperties.getMainClass(), javaDict);
243:
244: // Target JVM version, optional but recommended
245: if (bundleProperties.getJVMVersion() != null)
246: writeKeyStringPair("JVMVersion", bundleProperties
247: .getJVMVersion(), javaDict);
248:
249: // Classpath is composed of two types, required
250: // 1: Jars bundled into the JAVA_ROOT of the application
251: // 2: External directories or files with an absolute path
252:
253: List classPath = bundleProperties.getClassPath();
254: List extraClassPath = bundleProperties.getExtraClassPath();
255:
256: if ((classPath.size() > 0) || (extraClassPath.size() > 0))
257: writeClasspath(classPath, extraClassPath, javaDict);
258:
259: // JVM options, optional
260: if (bundleProperties.getVMOptions() != null)
261: writeKeyStringPair("VMOptions", bundleProperties
262: .getVMOptions(), javaDict);
263:
264: // Working directory, optional
265: if (bundleProperties.getWorkingDirectory() != null)
266: writeKeyStringPair("WorkingDirectory", bundleProperties
267: .getWorkingDirectory(), javaDict);
268:
269: // Main class arguments, optional
270: if (bundleProperties.getArguments() != null)
271: writeKeyStringPair("Arguments", bundleProperties
272: .getArguments(), javaDict);
273:
274: // Java properties, optional
275: Hashtable javaProperties = bundleProperties.getJavaProperties();
276:
277: if (javaProperties.isEmpty() == false)
278: writeJavaProperties(javaProperties, javaDict);
279:
280: // Services, optional
281: List services = bundleProperties.getServices();
282: if (services.size() > 0)
283: writeServices(services, dict);
284:
285: }
286:
287: private void writeDocumentTypes(List documentTypes, Node appendTo) {
288:
289: writeKey("CFBundleDocumentTypes", appendTo);
290:
291: Node array = createNode("array", appendTo);
292:
293: Iterator itor = documentTypes.iterator();
294:
295: while (itor.hasNext()) {
296:
297: DocumentType documentType = (DocumentType) itor.next();
298:
299: Node documentDict = createNode("dict", array);
300:
301: writeKeyStringPair("CFBundleTypeName", documentType
302: .getName(), documentDict);
303: writeKeyStringPair("CFBundleTypeRole", documentType
304: .getRole(), documentDict);
305:
306: File iconFile = documentType.getIconFile();
307:
308: if (iconFile != null)
309: writeKeyStringPair("CFBundleTypeIconFile", iconFile
310: .getName(), documentDict);
311:
312: List extensions = documentType.getExtensions();
313:
314: if (extensions.isEmpty() == false) {
315: writeKey("CFBundleTypeExtensions", documentDict);
316: writeArray(extensions, documentDict);
317: }
318:
319: List osTypes = documentType.getOSTypes();
320:
321: if (osTypes.isEmpty() == false) {
322: writeKey("CFBundleTypeOSTypes", documentDict);
323: writeArray(osTypes, documentDict);
324: }
325:
326: List mimeTypes = documentType.getMimeTypes();
327:
328: if (mimeTypes.isEmpty() == false) {
329: writeKey("CFBundleTypeMIMETypes", documentDict);
330: writeArray(mimeTypes, documentDict);
331: }
332:
333: // Only write this key if true
334: if (documentType.isBundle())
335: writeKeyStringPair("LSTypeIsPackage", "true",
336: documentDict);
337: }
338: }
339:
340: private void writeServices(List services, Node appendTo) {
341:
342: writeKey("NSServices", appendTo);
343: Node array = createNode("array", appendTo);
344: Iterator itor = services.iterator();
345:
346: while (itor.hasNext()) {
347: Service service = (Service) itor.next();
348: Node serviceDict = createNode("dict", array);
349:
350: String portName = service.getPortName();
351: if (portName == null)
352: portName = bundleProperties.getCFBundleName();
353:
354: writeKeyStringPair("NSPortName", portName, serviceDict);
355: writeKeyStringPair("NSMessage", service.getMessage(),
356: serviceDict);
357:
358: List sendTypes = service.getSendTypes();
359: if (!sendTypes.isEmpty()) {
360: writeKey("NSSendTypes", serviceDict);
361: writeArray(sendTypes, serviceDict);
362: }
363:
364: List returnTypes = service.getReturnTypes();
365: if (!returnTypes.isEmpty()) {
366: writeKey("NSReturnTypes", serviceDict);
367: writeArray(returnTypes, serviceDict);
368: }
369:
370: writeKey("NSMenuItem", serviceDict);
371: Node menuItemDict = createNode("dict", serviceDict);
372: writeKeyStringPair("default", service.getMenuItem(),
373: menuItemDict);
374:
375: String keyEquivalent = service.getKeyEquivalent();
376: if (null != keyEquivalent) {
377: writeKey("NSKeyEquivalent", serviceDict);
378: Node keyEquivalentDict = createNode("dict", serviceDict);
379: writeKeyStringPair("default", keyEquivalent,
380: keyEquivalentDict);
381: }
382:
383: String userData = service.getUserData();
384: if (null != userData)
385: writeKeyStringPair("NSUserData", userData, serviceDict);
386:
387: String timeout = service.getTimeout();
388: if (null != timeout)
389: writeKeyStringPair("NSTimeout", timeout, serviceDict);
390: }
391: }
392:
393: private void writeClasspath(List classpath, List extraClasspath,
394: Node appendTo) {
395: writeKey("ClassPath", appendTo);
396: classpath.addAll(extraClasspath);
397: writeArray(classpath, appendTo);
398: }
399:
400: private void writeJavaProperties(Hashtable javaProperties,
401: Node appendTo) {
402:
403: writeKey("Properties", appendTo);
404:
405: Node propertiesDict = createNode("dict", appendTo);
406:
407: for (Iterator i = javaProperties.keySet().iterator(); i
408: .hasNext();) {
409: String key = (String) i.next();
410:
411: if (key.startsWith("com.apple.") && (version >= 1.4)) {
412: System.out.println("Deprecated as of 1.4: " + key);
413: continue;
414: }
415:
416: writeKeyStringPair(key, (String) javaProperties.get(key),
417: propertiesDict);
418: }
419: }
420:
421: private Node createNode(String tag, Node appendTo) {
422: Node node = this .document.createElement(tag);
423: appendTo.appendChild(node);
424: return node;
425: }
426:
427: private void writeKeyStringPair(String key, String string,
428: Node appendTo) {
429:
430: if (string == null)
431: return;
432:
433: writeKey(key, appendTo);
434: writeString(string, appendTo);
435: }
436:
437: private void writeKey(String key, Node appendTo) {
438: Element keyNode = this .document.createElement("key");
439: appendTo.appendChild(keyNode);
440: keyNode.appendChild(this .document.createTextNode(key));
441: }
442:
443: private void writeString(String string, Node appendTo) {
444: Element stringNode = this .document.createElement("string");
445: stringNode.appendChild(this .document.createTextNode(string));
446: appendTo.appendChild(stringNode);
447: }
448:
449: private void writeArray(List stringList, Node appendTo) {
450:
451: Node arrayNode = createNode("array", appendTo);
452:
453: for (Iterator it = stringList.iterator(); it.hasNext();)
454: writeString((String) it.next(), arrayNode);
455:
456: }
457:
458: }
|