PageRenderTime 409ms CodeModel.GetById 363ms app.highlight 38ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-3-pre5/net/sourceforge/jarbundler/JarBundler.java

#
Java | 1480 lines | 713 code | 302 blank | 465 comment | 129 complexity | bedc7a7266063cda3d3cda1a7edc220a MD5 | raw file
   1/*
   2 * A Mac OS X Jar Bundler Ant Task.
   3 *
   4 * Copyright (c) 2003, Seth J. Morabito <sethm@loomcom.com> All rights reserved.
   5 *
   6 * This program is free software; you can redistribute it and/or modify it
   7 * under the terms of the GNU General Public License as published by the Free
   8 * Software Foundation; either version 2 of the License, or (at your option)
   9 * any later version.
  10 *
  11 * This program is distributed in the hope that it will be useful, but WITHOUT
  12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13 * FITNESS FOR A PARTICULAR PURPOSE.  See  the GNU General Public License for
  14 * more details.
  15 *
  16 * You should have received a copy of the GNU General Public License along with
  17 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
  18 * Place - Suite 330, Boston, MA  02111-1307, USA.
  19 */
  20
  21package net.sourceforge.jarbundler;
  22
  23// This package's imports
  24import net.sourceforge.jarbundler.AppBundleProperties;
  25import net.sourceforge.jarbundler.DocumentType;
  26import net.sourceforge.jarbundler.JavaProperty;
  27import net.sourceforge.jarbundler.PropertyListWriter;
  28
  29// Java I/O
  30import java.io.BufferedWriter;
  31import java.io.BufferedReader;
  32import java.io.File;
  33import java.io.FileWriter;
  34import java.io.FileReader;
  35import java.io.IOException;
  36import java.io.InputStream;
  37import java.io.PrintWriter;
  38
  39// Java Utility
  40import java.util.ArrayList;
  41import java.util.HashSet;
  42import java.util.Iterator;
  43import java.util.List;
  44import java.util.Set;
  45
  46// Apache Jakarta
  47import org.apache.tools.ant.BuildException;
  48import org.apache.tools.ant.FileScanner;
  49import org.apache.tools.ant.Project;
  50import org.apache.tools.ant.Task;
  51
  52import org.apache.tools.ant.types.FileList;
  53import org.apache.tools.ant.types.FileSet;
  54import org.apache.tools.ant.types.PatternSet;
  55
  56import org.apache.tools.ant.taskdefs.MatchingTask;
  57import org.apache.tools.ant.taskdefs.Chmod;
  58import org.apache.tools.ant.taskdefs.Delete;
  59
  60import org.apache.tools.ant.util.FileUtils;
  61
  62
  63// Java language imports
  64import java.lang.Boolean;
  65import java.lang.Process;
  66import java.lang.Runtime;
  67import java.lang.String;
  68import java.lang.System;
  69
  70/**
  71 * <p>
  72 * An ant task which creates a Mac OS X Application Bundle for a Java
  73 * application.
  74 * </p>
  75 * 
  76 * <dl>
  77 * <dt>dir</dt>
  78 * <dd>The directory into which to put the new application bundle.</dd>
  79 * <dt>name</dt>
  80 * <dd>The name of the application bundle. Note that the maximum length of this
  81 * name is 16 characters, and it will be silently cropped if it is longer than
  82 * this.</dd>
  83 * <dt>mainclass</dt>
  84 * <dd>The main Java class to call when running the application.</dd>
  85 * </dl>
  86 * 
  87 * <p>
  88 * One of the following three MUST be used:
  89 * 
  90 * <ol>
  91 * <li>jars Space or comma-separated list of JAR files to include.; OR</li>
  92 * <li>One or more nested &lt;jarfileset&gt;s. These are normal ANT FileSets;
  93 * OR </li>
  94 * <li>One or more nested &lt;jarfilelist&gt;s. These are standard ANT
  95 * FileLists. </li>
  96 * </ol>
  97 * 
  98 * <p>
  99 * Optional attributes:
 100 * 
 101 * <p>
 102 * The following attributes are not required, but you can use them to override
 103 * default behavior.
 104 * 
 105 * <dl>
 106 * <dt>verbose
 107 * <dd>If true, show more verbose output while running the task
 108 * 
 109 * <dt>version
 110 * <dd>Version information about your application (e.g., "1.0")
 111 * 
 112 * <dt>infostring
 113 * <dd>String to show in the "Get Info" dialog
 114 * </dl>
 115 * 
 116 * These attributes control the fine-tuning of the "Mac OS X" look and feel.
 117 * 
 118 * <dl>
 119 * <dt>arguments
 120 * <dd>Command line arguments. (no default)
 121 * 
 122 * <dt>smalltabs
 123 * <dd>Use small tabs. (default "false") Deprecated under JVM 1.4.1
 124 * 
 125 * <dt>antialiasedgraphics
 126 * <dd>Use anti-aliased graphics (default "false")
 127 * 
 128 * <dt>antialiasedtext
 129 * <dd>Use anti-aliased text (default "false")
 130 * 
 131 * <dt>bundleid
 132 * <dd>Unique identifier for this bundle, in the form of a Java package. No
 133 * default.
 134 * 
 135 * <dt>buildnumber
 136 * <dd>Unique identifier for this build
 137 * 
 138 * <dt>developmentregion
 139 * <dd>Development Region. Default "English".
 140 * 
 141 * <dt>execs
 142 * <dd>Files to be copied into "Resources/MacOS" and made executable
 143 * 
 144 * <dt>liveresize
 145 * <dd>Use "Live resizing" (default "false") Deprecated under JVM 1.4.1
 146 * 
 147 * 
 148 * <dt>growbox
 149 * <dd>Show growbox (default "true")
 150 * 
 151 * <dt>growboxintrudes
 152 * <dd>Intruding growbox (default "false") Deprecated under JVM 1.4.1
 153 * 
 154 * <dt>screenmenu
 155 * <dd>Put swing menu into Mac OS X menu bar.
 156 * 
 157 * <dt>type
 158 * <dd>Bundle type (default "APPL")
 159 * 
 160 * <dt>signature
 161 * <dd>Bundle Signature (default "????")
 162 * 
 163 * <dt>stubfile
 164 * <dd>The Java Application Stub file to copy for your application (default
 165 * MacOS system stub file)
 166 * </dl>
 167 * 
 168 * <p>
 169 * Rarely used optional attributes.
 170 * <dl>
 171 * <dt>chmod
 172 * <dd>Full path to the chmod command. This almost certainly does NOT need to
 173 * be set.
 174 * </dl>
 175 * 
 176 * <p>
 177 * The task also supports nested &lt;execfileset&gt; and/or &lt;execfilelist&gt;
 178 * elements, and &lt;resourcefileset&gt; and/or &lt;resourcefilelist&gt;
 179 * elements, which are standard Ant FileSet and FileList elements. In the first
 180 * case, the referenced files are copied to the <code>Contents/MacOS</code>
 181 * directory and made executable, and in the second they are copied to the
 182 * <code>Contents/Resources</code> directory and not made executable. If you
 183 * winrces, note that in fact the files are installed in locations which have
 184 * the same relation to the <code>Contents/Resources</code> directory as the
 185 * files in the FileSet or FileList have to the 'dir' attribute. Thus in the
 186 * case:
 187 * 
 188 * <pre>
 189 *   &lt;resourcefileset dir=&quot;builddir/architectures&quot;
 190 *                       includes=&quot;ppc/*.jnilib&quot;/&gt;
 191 * </pre>
 192 * 
 193 * <p>
 194 * the <code>*.jnilib</code> files will be installed in
 195 * <code>Contents/Resources/ppc</code>.
 196 * 
 197 * <p>
 198 * The task supports a nested &lt;javaproperty&gt; element, which allows you to
 199 * specify further properties which are set for the JVM when the application is
 200 * launched. This takes a required <code>key</code> attribute, giving the
 201 * property key, plus an attribute giving the property value, which may be one
 202 * of <code>value</code>, giving the string value of the property,
 203 * <code>file</code>, setting the value of the property to be the absolute
 204 * path of the given file, or <code>path</code>, which sets the value to the
 205 * given path. If you are setting paths here, recall that, within the bundle,
 206 * <code>$APP_PACKAGE</code> is set to the root directory of the bundle (ie,
 207 * the path to the <code>foo.app</code> directory), and <code>$JAVAROOT</code>
 208 * to the directory <code>Contents/Resources/Java</code>.
 209 * 
 210 * <p>
 211 * Minimum example:
 212 * 
 213 * <pre>
 214 *  
 215 *    &lt;jarbundler dir=&quot;release&quot; name=&quot;Bar Project&quot; mainclass=&quot;org.bar.Main&quot;
 216 *        jars=&quot;bin/Bar.jar&quot; /&gt;
 217 * </pre>
 218 * 
 219 * <p>
 220 * Using Filesets
 221 * 
 222 * <pre>
 223 *    &lt;jarbundler dir=&quot;release&quot; name=&quot;Bar Project&quot; mainclass=&quot;org.bar.Main&quot;&gt;
 224 *      &lt;jarfileset dir=&quot;bin&quot;&gt;
 225 *        &lt;include name=&quot;*.jar&quot; /&gt;
 226 *        &lt;exclude name=&quot;test.jar&quot; /&gt;
 227 *      &lt;/jarfileset&gt;
 228 *      &lt;execfileset dir=&quot;execs&quot;&gt;
 229 *        &lt;include name=&quot;**&quot; /&gt;
 230 *      &lt;/execfileset&gt;
 231 *    &lt;/jarbundler&gt;
 232 * </pre>
 233 * 
 234 * <p>
 235 * Much Longer example:
 236 * </p>
 237 * 
 238 * <pre>
 239 *    &lt;jarbundler dir=&quot;release&quot;
 240 *                name=&quot;Foo Project&quot;
 241 *                mainclass=&quot;org.bar.Main&quot;
 242 *                version=&quot;1.0 b 1&quot;
 243 *                infostring=&quot;Foo Project (c) 2002&quot; 
 244 *                type=&quot;APPL&quot;
 245 *                jars=&quot;bin/foo.jar bin/bar.jar&quot;
 246 *                execs=&quot;exec/foobar&quot;
 247 *                signature=&quot;????&quot;
 248 *                workingdirectory=&quot;temp&quot;
 249 *                icon=&quot;resources/foo.icns&quot;
 250 *                jvmversion=&quot;1.4.1+&quot;
 251 *                vmoptions=&quot;-Xmx256m&quot;/&gt;
 252 * </pre>
 253 * 
 254 * http://developer.apple.com/documentation/MacOSX/Conceptual/BPRuntimeConfig/
 255 */
 256public class JarBundler extends MatchingTask {
 257
 258	private static final String DEFAULT_STUB = "/System/Library/Frameworks/JavaVM.framework/Versions/Current/Resources/MacOS/JavaApplicationStub";
 259
 260	private static final String ABOUTMENU_KEY = "com.apple.mrj.application.apple.menu.about.name";
 261	private static final Set menuItems = new HashSet();
 262	private File mAppIcon;
 263
 264	private File mRootDir;
 265
 266	private final List mJavaFileLists = new ArrayList();
 267	private final List mJarFileSets = new ArrayList();
 268
 269	private final List mExecFileLists = new ArrayList();
 270	private final List mExecFileSets = new ArrayList();
 271
 272	private final List mResourceFileLists = new ArrayList();
 273	private final List mResourceFileSets = new ArrayList();
 274
 275	private final List mJarFileLists = new ArrayList();
 276	private final List mJavaFileSets = new ArrayList();
 277
 278	private final List mExtraClassPathFileLists = new ArrayList();
 279	private final List mExtraClassPathFileSets = new ArrayList();
 280
 281	private final List mJarAttrs = new ArrayList();
 282
 283	private final List mExecAttrs = new ArrayList();
 284
 285	private final List mExtraClassPathAttrs = new ArrayList();
 286	
 287	private final List mHelpBooks = new ArrayList();
 288
 289	private boolean mVerbose = false;
 290	private boolean mShowPlist = false;
 291
 292	// Java properties used by Mac OS X Java applications
 293
 294	private File mStubFile = new File(DEFAULT_STUB);
 295
 296	private Boolean mAntiAliasedGraphics = null;
 297
 298	private Boolean mAntiAliasedText = null;
 299
 300	private Boolean mLiveResize = null;
 301
 302	private Boolean mScreenMenuBar = null;
 303
 304	private Boolean mGrowbox = null;
 305
 306	private Boolean mGrowboxIntrudes = null;
 307
 308	// The root of the application bundle
 309	private File bundleDir;
 310
 311	// "Contents" directory
 312	private File mContentsDir;
 313
 314	// "Contents/MacOS" directory
 315	private File mMacOsDir;
 316
 317	// "Contents/Resources" directory
 318	private File mResourcesDir;
 319
 320	// "Contents/Resources/Java" directory
 321	private File mJavaDir;
 322
 323	// Full path to the 'chmod' command. Can be overridden
 324	// with the 'chmod' attribute. Won't cause any harm if
 325	// not set, or if this executable doesn't exist.
 326
 327
 328	private AppBundleProperties bundleProperties = new AppBundleProperties();
 329
 330	// Ant file utilities
 331
 332	private FileUtils mFileUtils = FileUtils.getFileUtils();
 333
 334	/***************************************************************************
 335	 * Retreive task attributes
 336	 **************************************************************************/
 337
 338	/**
 339	 * Arguments to the
 340	 * 
 341	 * @param s
 342	 *            The arguments to pass to the application being launched.
 343	 */
 344	public void setArguments(String s) {
 345		bundleProperties.setArguments(s);
 346	}
 347
 348	/**
 349	 * Override the stub file path to build on non-MacOS platforms
 350	 * 
 351	 * @param file
 352	 *            the path to the stub file
 353	 */
 354	public void setStubFile(File file) {
 355		mStubFile = (file.exists()) ? file : new File(DEFAULT_STUB);
 356		bundleProperties.setCFBundleExecutable(file.getName());
 357	}
 358
 359	/**
 360	 * Setter for the "dir" attribute (required)
 361	 */
 362	public void setDir(File f) {
 363		mRootDir = f;
 364	}
 365
 366	/**
 367	 * Setter for the "name" attribute (required) This attribute names the
 368	 * output application bundle and asks as the CFBundleName if 'bundlename' is
 369	 * not specified
 370	 */
 371	public void setName(String s) {
 372		bundleProperties.setApplicationName(s);
 373	}
 374
 375	/**
 376	 * Setter for the "shortname" attribute (optional) This key identifies the
 377	 * short name of the bundle. This name should be less than 16 characters
 378	 * long and be suitable for displaying in the menu and the About box. The
 379	 * name is (silently) cropped to this if necessary.
 380	 */
 381	public void setShortName(String s) {
 382		bundleProperties.setCFBundleName(s);
 383	}
 384
 385	/**
 386	 * Setter for the "mainclass" attribute (required)
 387	 */
 388	public void setMainClass(String s) {
 389		bundleProperties.setMainClass(s);
 390	}
 391
 392	/**
 393	 * Setter for the "WorkingDirectory" attribute (optional)
 394	 */
 395	public void setWorkingDirectory(String s) {
 396		bundleProperties.setWorkingDirectory(s);
 397	}
 398
 399	/**
 400	 * Setter for the "icon" attribute (optional)
 401	 */
 402
 403	public void setIcon(File f) {
 404		mAppIcon = f;
 405		bundleProperties.setCFBundleIconFile(f.getName());
 406	}
 407
 408	/**
 409	 * Setter for the "bundleid" attribute (optional) This key specifies a
 410	 * unique identifier string for the bundle. This identifier should be in the
 411	 * form of a Java-style package name, for example com.mycompany.myapp. The
 412	 * bundle identifier can be used to locate the bundle at runtime. The
 413	 * preferences system uses this string to identify applications uniquely.
 414	 * 
 415	 * No default.
 416	 */
 417	public void setBundleid(String s) {
 418		bundleProperties.setCFBundleIdentifier(s);
 419	}
 420
 421	/**
 422	 * Setter for the "developmentregion" attribute(optional) Default "English".
 423	 */
 424	public void setDevelopmentregion(String s) {
 425		bundleProperties.setCFBundleDevelopmentRegion(s);
 426	}
 427
 428	/**
 429	 * Setter for the "aboutmenuname" attribute (optional)
 430	 */
 431	public void setAboutmenuname(String s) {
 432		bundleProperties.setCFBundleName(s);
 433	}
 434
 435	/**
 436	 * Setter for the "smalltabs" attribute (optional)
 437	 */
 438	public void setSmallTabs(boolean b) {
 439		bundleProperties.addJavaProperty("com.apple.smallTabs", new Boolean(b)
 440				.toString());
 441	}
 442
 443	/**
 444	 * Setter for the "vmoptions" attribute (optional)
 445	 */
 446	public void setVmoptions(String s) {
 447		bundleProperties.setVMOptions(s);
 448	}
 449
 450	/**
 451	 * Setter for the "antialiasedgraphics" attribute (optional)
 452	 */
 453	public void setAntialiasedgraphics(boolean b) {
 454		mAntiAliasedGraphics = new Boolean(b);
 455	}
 456
 457	/**
 458	 * Setter for the "antialiasedtext" attribute (optional)
 459	 */
 460	public void setAntialiasedtext(boolean b) {
 461		mAntiAliasedText = new Boolean(b);
 462	}
 463
 464	/**
 465	 * Setter for the "screenmenu" attribute (optional)
 466	 */
 467	public void setScreenmenu(boolean b) {
 468		mScreenMenuBar = new Boolean(b);
 469	}
 470
 471	/**
 472	 * Setter for the "growbox" attribute (optional)
 473	 */
 474	public void setGrowbox(boolean b) {
 475		mGrowbox = new Boolean(b);
 476	}
 477
 478	/**
 479	 * Setter for the "growboxintrudes" attribute (optional)
 480	 */
 481	public void setGrowboxintrudes(boolean b) {
 482		mGrowboxIntrudes = new Boolean(b);
 483	}
 484
 485	/**
 486	 * Setter for the "liveresize" attribute (optional)
 487	 */
 488	public void setLiveresize(boolean b) {
 489		mLiveResize = new Boolean(b);
 490	}
 491
 492	/**
 493	 * Setter for the "type" attribute (optional)
 494	 */
 495	public void setType(String s) {
 496		bundleProperties.setCFBundlePackageType(s);
 497	}
 498
 499	/**
 500	 * Setter for the "signature" attribute (optional)
 501	 */
 502	public void setSignature(String s) {
 503		bundleProperties.setCFBundleSignature(s);
 504	}
 505
 506	/**
 507	 * Setter for the "jvmversion" attribute (optional)
 508	 */
 509	public void setJvmversion(String s) {
 510		bundleProperties.setJVMVersion(s);
 511	}
 512
 513	/**
 514	 * Setter for the "infostring" attribute (optional) This key identifies a
 515	 * human-readable plain text string displaying the copyright information for
 516	 * the bundle. The Finder displays this information in the Info window of
 517	 * the bundle. (This string was also known as the long version string in Mac
 518	 * OS 9). The format of the key should be of the following format: "&copy;
 519	 * Great Software, Inc, 1999". You can localize this string by including it
 520	 * in the InfoPlist.strings file of the appropriate .lproj directory.
 521	 */
 522
 523	public void setInfoString(String s) {
 524		bundleProperties.setCFBundleGetInfoString(s);
 525	}
 526
 527	/**
 528	 * Setter for the "shortinfostring" attribute (optional) This key identifies
 529	 * the marketing version of the bundle. The marketing version is a string
 530	 * that usually displays the major and minor version of the bundle. This
 531	 * string is usually of the form n.n.n where n is a number. The first number
 532	 * is the major version number of the bundle. The second and third numbers
 533	 * are minor revision numbers. You may omit minor revision numbers as
 534	 * appropriate. The value of this key is displayed in the default About box
 535	 * for Cocoa applications.
 536	 * 
 537	 * The value for this key differs from the value for "CFBundleVersion",
 538	 * which identifies a specific build number. The CFBundleShortVersionString
 539	 * value represents a more formal version that does not change with every
 540	 * build.
 541	 */
 542	public void setShortInfoString(String s) {
 543		setVersion(s);
 544	}
 545
 546	/**
 547	 * Setter for the "verbose" attribute (optional)
 548	 */
 549	public void setVerbose(boolean verbose) {
 550		this.mVerbose = verbose;
 551	}
 552	public void setShowPlist(boolean showPlist) {
 553		this.mShowPlist = showPlist;
 554	}
 555
 556
 557
 558
 559	/**
 560	 * Setter for the "buildnumber" attribute (optional) This key specifies the
 561	 * exact build version of the bundle. This string is usually of the form
 562	 * nn.n.nxnnn where n is a digit and x is a character from the set [abdf].
 563	 * The first number is the major version number of the bundle and can
 564	 * contain one or two digits to represent a number in the range 0-99. The
 565	 * second and third numbers are minor revision numbers and must be a single
 566	 * numeric digit. The fourth set of digits is the specific build number for
 567	 * the release.
 568	 * 
 569	 * You may omit minor revision and build number information as appropriate.
 570	 * You may also omit major and minor revision information and specify only a
 571	 * build number. For example, valid version numbers include: 1.0.1,
 572	 * 1.2.1b10, 1.2d200, d125, 101, and 1.0.
 573	 * 
 574	 * The value of this key typically changes between builds and is displayed
 575	 * in the Cocoa About panel in parenthesis. To specify the version
 576	 * information of a released bundle, use the CFBundleShortVersionString key.
 577	 */
 578	public void setBuild(String s) {
 579		bundleProperties.setCFBundleVersion(s);
 580	}
 581
 582	/**
 583	 * Setter for the version attribute (optional). It is this property, not
 584	 * CFBundleVersion, which should receive the `short' version string. See for
 585	 * example
 586	 * <http://developer.apple.com/documentation/MacOSX/Conceptual/BPRuntimeConfig/>
 587	 */
 588	public void setVersion(String s) {
 589		bundleProperties.setCFBundleShortVersionString(s);
 590	}
 591
 592	public void setHelpBookFolder(String s) {
 593		bundleProperties.setCFBundleHelpBookFolder(s);
 594	}
 595
 596	public void setHelpBookName(String s) {
 597		bundleProperties.setCFBundleHelpBookName(s);
 598	}
 599
 600	/**
 601	 * Setter for the "jars" attribute (required if no "jarfileset" is present)
 602	 */
 603	public void setJars(String s) {
 604		PatternSet patset = new PatternSet();
 605		patset.setIncludes(s);
 606
 607		String[] jarNames = patset.getIncludePatterns(getProject());
 608
 609		for (int i = 0; i < jarNames.length; i++)
 610			mJarAttrs.add(getProject().resolveFile(jarNames[i]));
 611	}
 612
 613	/**
 614	 * Setter for the "jar" attribute (required if no "jarfileset" is present)
 615	 */
 616	public void setJar(File s) {
 617		mJarAttrs.add(s);
 618	}
 619
 620	/**
 621	 * Setter for the "execs" attribute (optional)
 622	 */
 623	public void setExecs(String s) {
 624		PatternSet patset = new PatternSet();
 625		patset.setIncludes(s);
 626
 627		String[] execNames = patset.getIncludePatterns(getProject());
 628
 629		for (int i = 0; i < execNames.length; i++) {
 630			File f = new File(execNames[i]);
 631			mExecAttrs.add(f);
 632		}
 633	}
 634
 635	/**
 636	 * Setter for the "extraclasspath" attribute (optional)
 637	 */
 638	public void setExtraclasspath(String s) {
 639		PatternSet patset = new PatternSet();
 640		patset.setIncludes(s);
 641
 642		String[] cpNames = patset.getIncludePatterns(getProject());
 643
 644		for (int i = 0; i < cpNames.length; i++) {
 645			File f = new File(cpNames[i]);
 646			mExtraClassPathAttrs.add(f);
 647		}
 648	}
 649
 650	/**
 651	 * Set the 'chmod' executable.
 652	 */
 653	public void setChmod(String s) {
 654		log("The \"chmod\" attribute has deprecaited, using the ANT Chmod task internally");
 655	}
 656
 657	/***************************************************************************
 658	 * Nested tasks - derived from FileList and FileSet
 659	 **************************************************************************/
 660
 661	public void addJarfileset(FileSet fs) {
 662		mJarFileSets.add(fs);
 663	}
 664
 665	public void addJarfilelist(FileList fl) {
 666		mJarFileLists.add(fl);
 667	}
 668
 669	public void addExecfileset(FileSet fs) {
 670		mExecFileSets.add(fs);
 671	}
 672
 673	public void addExecfilelist(FileList fl) {
 674		mExecFileLists.add(fl);
 675	}
 676
 677	public void addResourcefileset(FileSet fs) {
 678		mResourceFileSets.add(fs);
 679	}
 680
 681	public void addResourcefilelist(FileList fl) {
 682		mResourceFileLists.add(fl);
 683	}
 684
 685	public void addJavafileset(FileSet fs) {
 686		mJavaFileSets.add(fs);
 687	}
 688
 689	public void addJavafilelist(FileList fl) {
 690		mJavaFileLists.add(fl);
 691	}
 692
 693	public void addExtraclasspathfileset(FileSet fs) {
 694		mExtraClassPathFileSets.add(fs);
 695	}
 696
 697	public void addExtraclasspathfilelist(FileList fl) {
 698		mExtraClassPathFileLists.add(fl);
 699	}
 700
 701
 702	/***************************************************************************
 703	 * Nested tasks - new tasks with custom attributes
 704	 **************************************************************************/
 705
 706
 707	public void addConfiguredJavaProperty(JavaProperty javaProperty)
 708			throws BuildException {
 709
 710		String name = javaProperty.getName();
 711		String value = javaProperty.getValue();
 712
 713		if ((name == null) || (value == null))
 714			throw new BuildException(
 715					"'<javaproperty>' must have both 'name' and 'value' attibutes");
 716
 717		bundleProperties.addJavaProperty(name, value);
 718	}
 719
 720	public void addConfiguredDocumentType(DocumentType documentType) throws BuildException {
 721
 722		String name = documentType.getName();
 723		String role = documentType.getRole();
 724		List osTypes = documentType.getOSTypes();
 725		List extensions = documentType.getExtensions();
 726		List mimeTypes = documentType.getMimeTypes();
 727
 728		if ((name == null) || (role == null))
 729			throw new BuildException(
 730					"'<documenttype>' must have both a 'name' and a 'role' attibute");
 731
 732		if ((osTypes.isEmpty()) && (extensions.isEmpty()) && (mimeTypes.isEmpty()))
 733			throw new BuildException(
 734					"'<documenttype>' of \""
 735							+ name
 736							+ "\" must have 'osTypes' or 'extensions' or 'mimeTypes'");
 737
 738		bundleProperties.addDocumentType(documentType);
 739	}
 740
 741	public void addConfiguredService(Service service) {
 742	
 743		//if (service.getPortName() == null)
 744		//	throw new BuildException("\"<service>\" must have a \"portName\" attribute");
 745		
 746		if (service.getMessage() == null)
 747			throw new BuildException("\"<service>\" must have a \"message\" attribute");
 748		
 749		String menuItem = service.getMenuItem();
 750		if (menuItem == null)
 751			throw new BuildException("\"<service>\" must have a \"menuItem\" attribute");
 752		if (!menuItems.add(menuItem))
 753			throw new BuildException("\"<service>\" \"menuItem\" value must be unique");
 754		
 755		if (service.getSendTypes().isEmpty() && service.getReturnTypes().isEmpty())
 756			throw new BuildException("\"<service>\" must have either a \"sendTypes\" attribute, a \"returnTypes\" attribute or both");
 757		
 758		String keyEquivalent = service.getKeyEquivalent();
 759		if ((keyEquivalent != null) && (1 != keyEquivalent.length()))
 760			throw new BuildException("\"<service>\" \"keyEquivalent\" must be one character if present");
 761		
 762		String timeoutString = service.getTimeout();
 763		if (timeoutString != null) {
 764			long timeout = -1;
 765			try {
 766				timeout = Long.parseLong(timeoutString);
 767			} catch (NumberFormatException nfe) {
 768				throw new BuildException("\"<service>\" \"timeout\" must be a positive integral number");
 769			}
 770			if (timeout < 0)
 771				throw new BuildException("\"<service>\" \"timeout\" must not be negative");
 772		}
 773		
 774		bundleProperties.addService(service);
 775	}
 776	
 777	public void addConfiguredHelpBook(HelpBook helpBook) {
 778	
 779		// Validity check on 'foldername'
 780		if (helpBook.getFolderName() == null) {
 781			if (bundleProperties.getCFBundleHelpBookFolder() == null)
 782				throw new BuildException("Either the '<helpbook>' attribute 'foldername' or the '<jarbundler>' attribute 'helpbookfolder' must be defined");
 783			helpBook.setFolderName(bundleProperties.getCFBundleHelpBookFolder());
 784		}
 785
 786		// Validity check on 'title'
 787		if (helpBook.getName() == null) {
 788			if (bundleProperties.getCFBundleHelpBookName() == null)
 789				throw new BuildException("Either the '<helpbook>' attribute 'name' or the '<jarbundler>' attribute 'helpbookname' must be defined");
 790			helpBook.setName(bundleProperties.getCFBundleHelpBookName());
 791		}
 792
 793		// Make sure some file were selected...
 794		List fileLists = helpBook.getFileLists();
 795		List fileSets = helpBook.getFileSets();
 796
 797		if ( fileLists.isEmpty() && fileSets.isEmpty() )
 798			throw new BuildException("The '<helpbook>' task must have either " +
 799			                         "'<fileset>' or  '<filelist>' nested tags");
 800
 801
 802		mHelpBooks.add(helpBook);
 803	}
 804
 805
 806
 807	/***************************************************************************
 808	 * Execute the task
 809	 **************************************************************************/
 810
 811	/**
 812	 * The method executing the task
 813	 */
 814
 815	public void execute() throws BuildException {
 816
 817		// Delete any existing Application bundle directory structure
 818
 819		bundleDir = new File(mRootDir, bundleProperties.getApplicationName() + ".app");
 820
 821		if (bundleDir.exists()) {
 822			Delete deleteTask = new Delete();
 823            deleteTask.setProject(getProject());
 824			deleteTask.setDir(bundleDir);
 825			deleteTask.execute();
 826		}
 827
 828		// Validate - look for required attributes
 829		// ///////////////////////////////////////////
 830
 831		if (mRootDir == null)
 832			throw new BuildException("Required attribute \"dir\" is not set.");
 833
 834		if (mJarAttrs.isEmpty() && mJarFileSets.isEmpty()
 835				&& mJarFileLists.isEmpty())
 836			throw new BuildException("Either the attribute \"jar\" must "
 837					+ "be set, or one or more jarfilelists or "
 838					+ "jarfilesets must be added.");
 839
 840		if (!mJarAttrs.isEmpty()
 841				&& (!mJarFileSets.isEmpty() || !mJarFileLists.isEmpty()))
 842			throw new BuildException(
 843					"Cannot set both the attribute "
 844							+ "\"jars\" and use jar filesets/filelists.  Use only one or the other.");
 845
 846		if (bundleProperties.getApplicationName() == null)
 847			throw new BuildException("Required attribute \"name\" is not set.");
 848
 849		if (bundleProperties.getMainClass() == null)
 850			throw new BuildException(
 851					"Required attribute \"mainclass\" is not set.");
 852
 853		// /////////////////////////////////////////////////////////////////////////////////////
 854
 855		// Set up some Java properties
 856
 857		// About Menu, deprecated under 1.4+
 858		if (useOldPropertyNames())
 859			bundleProperties.addJavaProperty(ABOUTMENU_KEY, bundleProperties
 860					.getCFBundleName());
 861
 862		// Anti Aliased Graphics, renamed in 1.4+
 863		String antiAliasedProperty = useOldPropertyNames()
 864				? "com.apple.macosx.AntiAliasedGraphicsOn"
 865				: "apple.awt.antialiasing";
 866
 867		if (mAntiAliasedGraphics != null)
 868			bundleProperties.addJavaProperty(antiAliasedProperty,
 869					mAntiAliasedGraphics.toString());
 870
 871		// Anti Aliased Text, renamed in 1.4+
 872		String antiAliasedTextProperty = useOldPropertyNames()
 873				? "com.apple.macosx.AntiAliasedTextOn"
 874				: "apple.awt.textantialiasing";
 875
 876		if (mAntiAliasedText != null)
 877			bundleProperties.addJavaProperty(antiAliasedTextProperty,
 878					mAntiAliasedText.toString());
 879
 880		// Live Resize, deprecated under 1.4+
 881		if (useOldPropertyNames() && (mLiveResize != null))
 882			bundleProperties.addJavaProperty(
 883					"com.apple.mrj.application.live-resize", mLiveResize
 884							.toString());
 885
 886		// Screen Menu Bar, renamed in 1.4+
 887		String screenMenuBarProperty = useOldPropertyNames()
 888				? "com.apple.macos.useScreenMenuBar"
 889				: "apple.laf.useScreenMenuBar";
 890
 891		if (mScreenMenuBar != null)
 892			bundleProperties.addJavaProperty(screenMenuBarProperty,
 893					mScreenMenuBar.toString());
 894
 895		// Growbox, added with 1.4+
 896		if ((useOldPropertyNames() == false) && (mGrowbox != null))
 897			bundleProperties.addJavaProperty("apple.awt.showGrowBox", mGrowbox
 898					.toString());
 899
 900		// Growbox Intrudes, deprecated under 1.4+
 901		if (useOldPropertyNames() && (mGrowboxIntrudes != null))
 902			bundleProperties.addJavaProperty(
 903					"com.apple.mrj.application.growbox.intrudes",
 904					mGrowboxIntrudes.toString());
 905
 906		if (!mRootDir.exists()
 907				|| (mRootDir.exists() && !mRootDir.isDirectory()))
 908			throw new BuildException(
 909					"Destination directory specified by \"dir\" "
 910							+ "attribute must already exist.");
 911
 912		if (bundleDir.exists())
 913			throw new BuildException("The directory/bundle \""
 914					+ bundleDir.getName()
 915					+ "\" already exists, cannot continue.");
 916
 917		// Status message
 918		log("Creating application bundle: " + bundleDir);
 919
 920		if (!bundleDir.mkdir())
 921			throw new BuildException("Unable to create bundle: " + bundleDir);
 922
 923		// Make the Contents directory
 924		mContentsDir = new File(bundleDir, "Contents");
 925
 926		if (!mContentsDir.mkdir())
 927			throw new BuildException("Unable to create directory "
 928					+ mContentsDir);
 929
 930		// Make the "MacOS" directory
 931		mMacOsDir = new File(mContentsDir, "MacOS");
 932
 933		if (!mMacOsDir.mkdir())
 934			throw new BuildException("Unable to create directory " + mMacOsDir);
 935
 936		// Make the Resources directory
 937		mResourcesDir = new File(mContentsDir, "Resources");
 938
 939		if (!mResourcesDir.mkdir())
 940			throw new BuildException("Unable to create directory "
 941					+ mResourcesDir);
 942
 943		// Make the Resources/Java directory
 944		mJavaDir = new File(mResourcesDir, "Java");
 945
 946		if (!mJavaDir.mkdir())
 947			throw new BuildException("Unable to create directory " + mJavaDir);
 948
 949		// Copy icon file to resource dir. If no icon parameter
 950		// is supplied, the default icon will be used.
 951
 952		if (mAppIcon != null) {
 953		
 954
 955			try {
 956				File dest = new File(mResourcesDir, mAppIcon.getName());
 957
 958				if(mVerbose)
 959					log("Copying application icon file to \"" + bundlePath(dest) + "\"");
 960
 961				mFileUtils.copyFile(mAppIcon, dest);
 962			} catch (IOException ex) {
 963				throw new BuildException("Cannot copy icon file: " + ex);
 964			}
 965		}
 966
 967		// Copy document type icons, if any, to the resource dir
 968		try {
 969			Iterator itor = bundleProperties.getDocumentTypes().iterator();
 970
 971			while (itor.hasNext()) {
 972				DocumentType documentType = (DocumentType) itor.next();
 973				File iconFile = documentType.getIconFile();
 974				if (iconFile != null) {
 975					File dest = new File(mResourcesDir, iconFile.getName());
 976					if(mVerbose)
 977						log("Copying document icon file to \"" + bundlePath(dest) + "\"");
 978					mFileUtils.copyFile(iconFile, dest);
 979				}
 980			}
 981		} catch (IOException ex) {
 982			throw new BuildException("Cannot copy document icon file: " + ex);
 983		}
 984
 985		// Copy application jar(s) from the "jars" attribute (if any)
 986		processJarAttrs();
 987
 988		// Copy application jar(s) from the nested jarfileset element(s)
 989		processJarFileSets();
 990
 991		// Copy application jar(s) from the nested jarfilelist element(s)
 992		processJarFileLists();
 993
 994		// Copy executable(s) from the "execs" attribute (if any)
 995		processExecAttrs();
 996
 997		// Copy executable(s) from the nested execfileset element(s)
 998		processExecFileSets();
 999
1000		// Copy executable(s) from the nested execfilelist element(s)
1001		processExecFileLists();
1002
1003		// Copy resource(s) from the nested resourcefileset element(s)
1004		processResourceFileSets();
1005
1006		// Copy resource(s) from the nested javafileset element(s)
1007		processJavaFileSets();
1008
1009		// Copy resource(s) from the nested resourcefilelist element(s)
1010		processResourceFileLists();
1011
1012		// Copy resource(s) from the nested javafilelist element(s)
1013		processJavaFileLists();
1014
1015		// Add external classpath references from the extraclasspath attributes
1016		processExtraClassPathAttrs();
1017
1018		// Add external classpath references from the nested
1019		// extraclasspathfileset element(s)
1020		processExtraClassPathFileSets();
1021
1022		// Add external classpath references from the nested
1023		// extraclasspathfilelist attributes
1024		processExtraClassPathFileLists();
1025
1026		// Copy HelpBooks into place
1027		copyHelpBooks();
1028
1029		// Copy the JavaApplicationStub file from the Java system directory to
1030		// the MacOS directory
1031		copyApplicationStub();
1032
1033		// Create the Info.plist file
1034		writeInfoPlist();
1035
1036		// Create the PkgInfo file
1037		writePkgInfo();
1038
1039		// Done!
1040	}
1041
1042	/***************************************************************************
1043	 * Private utility methods.
1044	 **************************************************************************/
1045
1046	private void setExecutable(File f) {
1047
1048		Chmod chmodTask = new Chmod();
1049		chmodTask.setProject(getProject());
1050		chmodTask.setFile(f);
1051		chmodTask.setPerm("ugo+rx");
1052
1053		if (mVerbose)
1054			log("Setting \"" + bundlePath(f) + "\" to executable");
1055
1056		chmodTask.execute();
1057
1058	}
1059
1060	/**
1061	 * Utility method to determine whether this app bundle is targeting a 1.3 or
1062	 * 1.4 VM. The Mac OS X 1.3 VM uses different Java property names from the
1063	 * 1.4 VM to hint at native Mac OS X look and feel options. For example, on
1064	 * 1.3 the Java property to tell the VM to display Swing menu bars as screen
1065	 * menus is "com.apple.macos.useScreenMenuBar". Under 1.4, it becomes
1066	 * "apple.laf.useScreenMenuBar". Such is the price of progress, I suppose.
1067	 * 
1068	 * Obviously, this logic may need refactoring in the future.
1069	 */
1070
1071	private boolean useOldPropertyNames() {
1072		return (bundleProperties.getJVMVersion().startsWith("1.3"));
1073	}
1074
1075	private void processJarAttrs() throws BuildException {
1076
1077		try {
1078
1079			for (Iterator jarIter = mJarAttrs.iterator(); jarIter.hasNext();) {
1080				File src = (File) jarIter.next();
1081				File dest = new File(mJavaDir, src.getName());
1082
1083				if (mVerbose) 
1084					log("Copying JAR file to \"" + bundlePath(dest) + "\"");
1085				
1086
1087				mFileUtils.copyFile(src, dest);
1088				bundleProperties.addToClassPath(dest.getName());
1089			}
1090		} catch (IOException ex) {
1091			throw new BuildException("Cannot copy jar file: " + ex);
1092		}
1093	}
1094
1095	private void processJarFileSets() throws BuildException {
1096
1097		for (Iterator jarIter = mJarFileSets.iterator(); jarIter.hasNext();) {
1098
1099			FileSet fs = (FileSet) jarIter.next();
1100
1101			Project p = fs.getProject();
1102			File srcDir = fs.getDir(p);
1103			FileScanner ds = fs.getDirectoryScanner(p);
1104			fs.setupDirectoryScanner(ds, p);
1105			ds.scan();
1106
1107			String[] files = ds.getIncludedFiles();
1108
1109			try {
1110
1111				for (int i = 0; i < files.length; i++) {
1112					String fileName = files[i];
1113					File src = new File(srcDir, fileName);
1114					File dest = new File(mJavaDir, fileName);
1115
1116					if (mVerbose)
1117						log("Copying JAR file to \"" + bundlePath(dest) + "\"");
1118
1119					mFileUtils.copyFile(src, dest);
1120					bundleProperties.addToClassPath(fileName);
1121				}
1122
1123			} catch (IOException ex) {
1124				throw new BuildException("Cannot copy jar file: " + ex);
1125			}
1126		}
1127	}
1128
1129	private void processJarFileLists() throws BuildException {
1130
1131		for (Iterator jarIter = mJarFileLists.iterator(); jarIter.hasNext();) {
1132			FileList fl = (FileList) jarIter.next();
1133			Project p = fl.getProject();
1134			File srcDir = fl.getDir(p);
1135			String[] files = fl.getFiles(p);
1136
1137			try {
1138
1139				for (int i = 0; i < files.length; i++) {
1140					String fileName = files[i];
1141					File src = new File(srcDir, fileName);
1142					File dest = new File(mJavaDir, fileName);
1143
1144					if (mVerbose) 
1145						log("Copying JAR file to \"" + bundlePath(dest) + "\"");
1146					
1147
1148					mFileUtils.copyFile(src, dest);
1149					bundleProperties.addToClassPath(fileName);
1150				}
1151			} catch (IOException ex) {
1152				throw new BuildException("Cannot copy jar file: " + ex);
1153			}
1154		}
1155	}
1156
1157	private void processExtraClassPathAttrs() throws BuildException {
1158
1159		for (Iterator jarIter = mExtraClassPathAttrs.iterator(); jarIter
1160				.hasNext();) {
1161			File src = (File) jarIter.next();
1162			bundleProperties.addToExtraClassPath(src.getPath());
1163		}
1164	}
1165
1166	private void processExtraClassPathFileSets() throws BuildException {
1167
1168		for (Iterator jarIter = mExtraClassPathFileSets.iterator(); jarIter
1169				.hasNext();) {
1170			FileSet fs = (FileSet) jarIter.next();
1171			Project p = fs.getProject();
1172			File srcDir = fs.getDir(p);
1173			FileScanner ds = fs.getDirectoryScanner(p);
1174			fs.setupDirectoryScanner(ds, p);
1175			ds.scan();
1176
1177			String[] files = ds.getIncludedFiles();
1178
1179			for (int i = 0; i < files.length; i++) {
1180				File f = new File(srcDir, files[i]);
1181				bundleProperties.addToExtraClassPath(f.getPath());
1182			}
1183		}
1184	}
1185
1186	private void processExtraClassPathFileLists() throws BuildException {
1187
1188		for (Iterator jarIter = mExtraClassPathFileLists.iterator(); jarIter
1189				.hasNext();) {
1190			FileList fl = (FileList) jarIter.next();
1191			Project p = fl.getProject();
1192			File srcDir = fl.getDir(p);
1193			String[] files = fl.getFiles(p);
1194
1195			for (int i = 0; i < files.length; i++) {
1196				File f = new File(srcDir, files[i]);
1197				bundleProperties.addToExtraClassPath(f.getPath());
1198			}
1199		}
1200	}
1201
1202	private void processExecAttrs() throws BuildException {
1203
1204		try {
1205
1206			for (Iterator execIter = mExecAttrs.iterator(); execIter.hasNext();) {
1207				File src = (File) execIter.next();
1208				File dest = new File(mMacOsDir, src.getName());
1209
1210				if (mVerbose) 
1211					log("Copying exec file to \"" + bundlePath(dest) + "\"");
1212				
1213
1214				mFileUtils.copyFile(src, dest);
1215				setExecutable(dest);
1216			}
1217		} catch (IOException ex) {
1218			throw new BuildException("Cannot copy exec file: " + ex);
1219		}
1220	}
1221
1222	// Methods for copying FileSets into the application bundle ///////////////////////////////
1223
1224	// Files for the Contents/MacOS directory
1225	private void processExecFileSets() {
1226		processCopyingFileSets(mExecFileSets, mMacOsDir, true);
1227	}
1228
1229	// Files for the Contents/Resources directory
1230	private void processResourceFileSets() {
1231		processCopyingFileSets(mResourceFileSets, mResourcesDir, false);
1232	}
1233
1234	// Files for the Contents/Resources/Java directory
1235	private void processJavaFileSets() {
1236		processCopyingFileSets(mJavaFileSets, mJavaDir, false);
1237	}
1238
1239	private void processCopyingFileSets(List fileSets, File targetdir, boolean setExec) {
1240
1241		for (Iterator execIter = fileSets.iterator(); execIter.hasNext();) {
1242			FileSet fs = (FileSet) execIter.next();
1243			Project p = fs.getProject();
1244			File srcDir = fs.getDir(p);
1245			FileScanner ds = fs.getDirectoryScanner(p);
1246			fs.setupDirectoryScanner(ds, p);
1247			ds.scan();
1248
1249			String[] files = ds.getIncludedFiles();
1250
1251			if (files.length == 0) {
1252				// this is probably an error -- warn about it
1253				System.err
1254						.println("WARNING: fileset for copying from directory "
1255								+ srcDir + ": no files found");
1256			} else {
1257				try {
1258					for (int i = 0; i < files.length; i++) {
1259						String fileName = files[i];
1260						File src = new File(srcDir, fileName);
1261						File dest = new File(targetdir, fileName);
1262						
1263						if (mVerbose) 
1264							log("Copying "
1265									+ (setExec ? "exec" : "resource")
1266									+ " file to \"" + bundlePath(dest) +"\"");
1267						
1268						mFileUtils.copyFile(src, dest);
1269						if (setExec)
1270							setExecutable(dest);
1271					}
1272				} catch (IOException ex) {
1273					throw new BuildException("Cannot copy file: " + ex);
1274				}
1275			}
1276		}
1277	}
1278
1279	// Methods for copying FileLists into the application bundle /////////////////////////////
1280
1281	// Files for the Contents/MacOS directory
1282	private void processExecFileLists() throws BuildException {
1283		processCopyingFileLists(mExecFileLists, mMacOsDir, true);
1284	}
1285
1286	// Files for the Contents/Resources directory
1287	private void processResourceFileLists() throws BuildException {
1288		processCopyingFileLists(mResourceFileLists, mResourcesDir, false);
1289	}
1290
1291	// Files for the Contents/Resources/Java directory
1292	private void processJavaFileLists() throws BuildException {
1293		processCopyingFileLists(mJavaFileLists, mJavaDir, false);
1294	}
1295
1296	private void processCopyingFileLists(List fileLists, File targetDir, boolean setExec) throws BuildException {
1297
1298		for (Iterator execIter = fileLists.iterator(); execIter.hasNext();) {
1299
1300			FileList fl = (FileList) execIter.next();
1301			Project p = fl.getProject();
1302			File srcDir = fl.getDir(p);
1303			String[] files = fl.getFiles(p);
1304
1305			if (files.length == 0) {
1306				// this is probably an error -- warn about it
1307				System.err.println("WARNING: filelist for copying from directory "
1308								+ srcDir + ": no files found");
1309			} else {
1310				try {
1311					for (int i = 0; i < files.length; i++) {
1312						String fileName = files[i];
1313						File src = new File(srcDir, fileName);
1314						File dest = new File(targetDir, fileName);
1315						
1316						if (mVerbose) 
1317							log("Copying "
1318									+ (setExec ? "exec" : "resource")
1319									+ " file to \"" + bundlePath(dest) +"\"");
1320						
1321						mFileUtils.copyFile(src, dest);
1322						if (setExec)
1323							setExecutable(dest);
1324					}
1325				} catch (IOException ex) {
1326					throw new BuildException("Cannot copy jar file: " + ex);
1327				}
1328			}
1329		}
1330	}
1331
1332
1333
1334	private void copyHelpBooks() {
1335
1336		for (Iterator itor = mHelpBooks.iterator(); itor.hasNext();) {
1337
1338			HelpBook helpBook = (HelpBook)itor.next();
1339			
1340			String folderName = helpBook.getFolderName();
1341			String name = helpBook.getName();
1342			String locale = helpBook.getLocale();
1343			
1344			List fileLists = helpBook.getFileLists();
1345			List fileSets = helpBook.getFileSets();
1346
1347
1348			File helpBookDir = null;
1349			
1350			if (locale == null) {
1351			
1352				// Set the Bundle entries for a nonlocalized Help Book
1353				if (folderName != null)
1354					bundleProperties.setCFBundleHelpBookFolder(folderName);
1355				
1356				if (name != null)
1357					bundleProperties.setCFBundleHelpBookName(name);
1358				
1359				// The non-localized Help Book is top level "/Resources"
1360				helpBookDir = new File(mResourcesDir, folderName);
1361				helpBookDir.mkdir();
1362
1363				if(mVerbose)
1364					log("Creating Help Book at \"" + 
1365					                    bundlePath(helpBookDir) + "\"");
1366
1367				
1368			} else {
1369
1370				// The localized Help Book is "/Resources/locale.lproj"
1371
1372				File lproj = new File(mResourcesDir, locale + ".lproj");
1373				lproj.mkdir();
1374				helpBookDir = new File(lproj, folderName);
1375				helpBookDir.mkdir();
1376
1377				if(mVerbose)
1378					log("Creating Help Book for \"" + locale +
1379					                    "\" at \"" + bundlePath(helpBookDir)  + "\"");
1380
1381				// Create a local file to override the Bundle settings
1382				File infoPList = new File(lproj, "InfoPlist.strings");
1383				PrintWriter writer = null;
1384				try {
1385 					writer = new PrintWriter(new FileWriter(infoPList));
1386       				writer.println("CFBundleHelpBookFolder = \"" + folderName + "\";");
1387       				writer.println("CFBundleHelpBookName = \"" + name + "\";");
1388       				writer.println("CFBundleName = \"" + bundleProperties.getCFBundleName() + "\";");
1389       			} catch (IOException ioe) {
1390       				throw new BuildException("IOException in writing Help Book locale: " + locale);
1391       			} finally {
1392		        	mFileUtils.close(writer);
1393		        }
1394			}
1395
1396			// Write the Help Book source files into the bundle
1397
1398			processCopyingFileSets(fileSets, helpBookDir, false);
1399			processCopyingFileLists(fileLists, helpBookDir, false);
1400
1401		}
1402	}
1403
1404
1405
1406
1407	// Copy the application stub into the bundle
1408	// /////////////////////////////////////////////
1409
1410	private void copyApplicationStub() throws BuildException {
1411
1412		File newStubFile = new File(mMacOsDir, bundleProperties.getCFBundleExecutable());
1413
1414		if (mVerbose)
1415			log("Copying Java application stub to \"" + bundlePath(newStubFile) + "\"");
1416
1417		try {
1418			mFileUtils.copyFile(mStubFile, newStubFile);
1419		} catch (IOException ex) {
1420			throw new BuildException("Cannot copy Java Application Stub: " + ex);
1421		}
1422
1423		// Set the permissions on the stub file to executable
1424
1425		setExecutable(newStubFile);
1426	}
1427
1428	private void writeInfoPlist() throws BuildException {
1429		PropertyListWriter listWriter = new PropertyListWriter(bundleProperties);
1430		File infoPlist = new File(mContentsDir, "Info.plist");
1431
1432		listWriter.writeFile(infoPlist);
1433		
1434		if (mVerbose) 
1435			log("Creating \"" + bundlePath(infoPlist) + "\" file");
1436
1437
1438		if (mShowPlist) {
1439			try {
1440				BufferedReader in = new BufferedReader(new FileReader(infoPlist));
1441				String str;
1442				while ((str = in.readLine()) != null) 
1443					log(str);
1444				in.close();
1445    		} catch (IOException e) {
1446    			throw new BuildException(e);
1447    		}			
1448		}
1449	}
1450
1451
1452	//
1453	// Write the PkgInfo file into the application bundle
1454	//
1455
1456	private void writePkgInfo() throws BuildException {
1457		File pkgInfo = new File(mContentsDir, "PkgInfo");
1458		PrintWriter writer = null;
1459
1460		try {
1461			writer = new PrintWriter(new BufferedWriter(new FileWriter(pkgInfo)));
1462			writer.print(bundleProperties.getCFBundlePackageType());
1463			writer.println(bundleProperties.getCFBundleSignature());
1464			writer.flush();
1465		} catch (IOException ex) {
1466			throw new BuildException("Cannot create PkgInfo file: " + ex);
1467		} finally {
1468			mFileUtils.close(writer);
1469		}
1470	}
1471
1472	private String bundlePath(File bundleFile) {
1473	
1474		String rootPath = bundleDir.getAbsolutePath();
1475		String thisPath = bundleFile.getAbsolutePath();
1476	
1477		return thisPath.substring(rootPath.length());
1478	
1479	}
1480}