blob: d9cd5255bc9c23872d6cfee431fac21f05c4196c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.netbeans.nbbuild;
import java.io.*;
import java.util.*;
import java.util.Map; // override org.apache.tools.ant.Map
import java.util.zip.*;
import java.util.jar.*;
import org.apache.tools.ant.*;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.tools.ant.types.*;
/** Create a JAR file with locale variants.
* Whenever files are found which should be localized, or are the result
* of localization, places them in separate JAR files named according to the locale,
* in a <samp>locale/</samp> subdirectory of the directory containing the master JAR.
* Each sub-JAR gets a manifest which just has some informational tags
* indicating its purpose (locale and branding):
* <code>X-Informational-Archive-Locale</code> and/or <code>X-Informational-Archive-Branding</code>.
* The values may be e.g. <code>ja</code> or <code>f4j_ce</code>; or <code>-</code>
* if there is no suffix for this JAR.
* You can control the available locales; brandings; and set of files which should
* always be considered part of the localizable base kit.
* You can use the "branding" and "locale" subelements to control the branded
* and localized .jar files that will be produced. Also, you can set the global
* properties "locjar.brands" and "locjar.locales" to comma-separated
* lists of branding or locale identifiers so that NetBeans-based projects can
* brand or localize NetBeans without having to maintain modified versions of all
* the individual Ant scripts
* Originally this Ant task didn't recognize files below a directory with the
* same name as a locale as being localized. Now it does so by default.
* <p>Based on <code>&lt;zip&gt;</code> and <code>&lt;jar&gt;</code> tasks in Ant,
* but not feasible to simply subclass or compose them.
* @see <a href="http://www.netbeans.org/devhome/docs/i18n/index.html">NetBeans I18N documentation</a>
* @author Jesse Glick
*/
public class LocalizedJar extends MatchingTask {
private List<FileSet> localeKits = new LinkedList<> ();
private List<LocaleOrB> locales = new LinkedList<> ();
private List<LocaleOrB> brandings = new LinkedList<> ();
private File jarFile;
private File baseDir;
private boolean doCompress = false;
private static long emptyCrc = new CRC32 ().getValue ();
private List<FileSet> filesets = new LinkedList<> ();
private File manifest;
private boolean checkPathLocale = true ;
private boolean warnMissingDir = false ;
private boolean warnMissingDirSet = false ;
private boolean preserveModuleJar = true;
private boolean alwaysIncludeManifest = false;
/** Locale or branding specifier.
* Represents a complete locale or branding suffix,
* e.g. <code>ja</code> or <code>ja_JP</code>
* (but not simply <code>JP</code>).
* For branding, e.g. <code>f4j</code> or <code>f4j_ce</code>.
* You must include all relevant suffixes if you expect them to match
* (but the task will handle a branding suffix combined with a locale
* suffix, in that order).
*/
public class LocaleOrB {
String n;
/** The suffix. */
public void setName (String n) {
this.n = n;
}
}
/** Distinguish particular files that will be considered localizable.
* This nested <samp>&lt;localekit&gt;</samp> has the same syntax as a FileSet.
* So typically you will build a JAR from a fileset containing all build
* products within a given directory; the "locale kit" should be a fileset
* matching all properties files, for example, and maybe icons which you would
* expect someone to try to localize--in general, anything which will be referenced
* in code using a localized lookup (<code>NbBundle</code>).
* While files with recognized localized (or branded) suffixes automatically go into
* marked JARs in the <samp>locale/</samp> subdirectory, files considered part of the
* locale kit also always go into this subdirectory; if they have no suffix, they will
* be placed in a suffixless locale JAR.
* So localizers can simply look at this JAR file and brand/localize it as they see fit.
*/
public void addLocalekit (FileSet fs) {
localeKits.add (fs);
}
/** Add a recognized locale suffix. */
public LocaleOrB createLocale () {
LocaleOrB l = new LocaleOrB ();
locales.add (l);
return l;
}
/** Add a recognized branding suffix. */
public LocaleOrB createBranding () {
LocaleOrB l = new LocaleOrB ();
brandings.add (l);
return l;
}
/** JAR file to create.
* In fact this is the location of the "base" JAR;
* locale-specific JARs may be created in the <samp>locale/</samp> subdirectory
* of the directory containing this JAR, and will be named according to the name
* of this JAR.
* Compare Ant's <samp>&lt;jar&gt;</samp> task.
*/
public void setJarfile (File jarFile) {
if (! jarFile.getName ().endsWith (".jar")) {
throw new BuildException ("jarfile attribute must be a file with *.jar extension");
}
if (jarFile.getParentFile () == null) {
throw new BuildException ("jarfile attribute must have a containing directory");
}
this.jarFile = jarFile;
}
/** Base directory to JAR.
* Compare Ant's <samp>&lt;jar&gt;</samp> task.
*/
public void setBasedir (File baseDir) {
this.baseDir = baseDir;
}
/** Turn on or off compression (default off).
* Compare Ant's <samp>&lt;jar&gt;</samp> task.
*/
public void setCompress(boolean compress) {
doCompress = compress;
}
/** Turn on/off preserving original module jars with 'code'
* Set it to 'true' to not allow overwriting of module jars
* with localization/branding stuff
*/
public void setPreserveModuleJar(boolean pmj) {
preserveModuleJar = pmj;
}
/** Turn on/off inclusion of manifest even for localized and/or branded
* jars. Set it to true if you want to add special manifest tags
* to all produced jarfiles
*/
public void setAlwaysIncludeManifest(boolean aim) {
alwaysIncludeManifest = aim;
}
/** A set of files to JAR up.
* Compare Ant's <samp>&lt;jar&gt;</samp> task.
*/
public void addFileset (FileSet set) {
filesets.add (set);
}
/** Manifest file for the JAR.
* Compare Ant's <samp>&lt;jar&gt;</samp> task.
*/
public void setManifest (File manifest) {
this.manifest = manifest;
}
/** By default this is true. If set to false, then this task will
* not recognize files below a directory with the same name as a
* locale as being localized (unless the simple filename also
* includes the locale).
*/
public void setCheckPathLocale( boolean doit) {
checkPathLocale = doit ;
}
/** This is false by default, in which case missing dirs in the
* filesets cause a BuildException to be thrown. If true, then
* a warning is printed but the build will continue.
* This task will also look for a global property
* "locjar.warnMissingDir" if this attribute isn't set.
*/
public void setWarnMissingDir( boolean b) {
warnMissingDir = b ;
warnMissingDirSet = true ;
}
public void execute () throws BuildException {
// Sanity checks:
if (baseDir == null && filesets.size () == 0) {
throw new BuildException ("basedir attribute must be set, or at least one fileset must be given!");
}
if (jarFile == null) {
throw new BuildException ("You must specify the JAR file to create!");
}
if (manifest != null && ! manifest.isFile ()) {
throw new BuildException ("The specified manifest does not actually exist.");
}
// If needed, warn that directories are missing. //
if( shouldWarnMissingDir() && warnIfMissingDir()) {
// Stop if dirs were missing. //
return ;
}
// Look for global locales or brandings to use. //
addGlobalLocaleAndBranding() ;
//System.err.println ("Stage #1");
// First find out which files need to be archived.
Map<String, File> allFiles = new HashMap<> (); // all files to do something with; Map<String,File> from JAR path to actual file
// Populate it.
{
List<FileScanner> scanners = new ArrayList<> (filesets.size () + 1);
if (baseDir != null) {
scanners.add (getDirectoryScanner (baseDir));
}
for (FileSet fs: filesets) {
scanners.add(fs.getDirectoryScanner(getProject()));
}
for (FileScanner scanner: scanners) {
File thisBaseDir = scanner.getBasedir ();
String[] files = scanner.getIncludedFiles ();
for (int i = 0; i < files.length; i++) {
String name = files[i].replace (File.separatorChar, '/');
if (name.equalsIgnoreCase ("META-INF/MANIFEST.MF")) {
log ("Warning: ignoring META-INF/MANIFEST.MF found among scanned files", Project.MSG_WARN);
continue;
}
allFiles.put (name, new File (thisBaseDir, files[i]));
}
}
}
//System.err.println ("Stage #2");
// Now find all files which should always be put into a locale
// kit (e.g. dir/locale/name.jar, no special locale or
// branding, but distinguished as localizable/brandable).
Set<File> localeKitFiles = new HashSet<> (); // all locale-kit files
// Populate this one.
{
for (FileSet fs: localeKits) {
FileScanner scanner = fs.getDirectoryScanner(getProject());
File thisBaseDir = scanner.getBasedir ();
String[] files = scanner.getIncludedFiles ();
for (int i = 0; i < files.length; i++) {
localeKitFiles.add (new File (thisBaseDir, files[i]));
}
}
}
//System.err.println ("Stage #3");
// Compute list of supported locales and brandings.
List<String> locales2 = new LinkedList<> ();
List<String> brandings2 = new LinkedList<> (); // all brandings
// Initialize above two.
for (LocaleOrB lob: locales) {
locales2.add (lob.n);
}
for (LocaleOrB lob: brandings) {
brandings2.add (lob.n);
}
class InverseLengthComparator implements Comparator<String> {
public int compare (String s1, String s2) {
return s2.length () - s1.length ();
}
}
Collections.sort (locales2, new InverseLengthComparator ());
Collections.sort (brandings2, new InverseLengthComparator ());
//System.err.println ("Stage #4");
// Analyze where everything goes.
Set<File> jars = new HashSet<> (); //JAR files to build
Map<File,String> localeMarks = new HashMap<> (); // JAR files to locale (or null for basic JAR, "-" for blank)
Map<File,String> brandingMarks = new HashMap<> (); // JAR files to branding (or null for basic JAR, "-" for blank)
Map<File,Map<String,File>> router = new HashMap<> (); // JAR files to map of JAR path to actual file (file may be null for dirs)
{
String localeDir ;
for (Map.Entry<String, File> entry: allFiles.entrySet()) {
String path = entry.getKey ();
log( "==> Examining file: " + path, Project.MSG_DEBUG) ;
File file = entry.getValue ();
// First see if it matches a known branding, locale, or pair of one of each.
String testpath = path;
int idx = testpath.lastIndexOf ('/');
if (idx != -1) testpath = testpath.substring (idx + 1);
idx = testpath.lastIndexOf ('.');
if (idx != -1) testpath = testpath.substring (0, idx);
String thisLocale = null;
for(String tryLocale: locales2) {
if (testpath.endsWith ("_" + tryLocale)) {
thisLocale = tryLocale;
testpath = testpath.substring (0, testpath.length () - 1 - tryLocale.length ());
break;
}
}
String thisBranding = null;
for(String tryBranding: brandings2) {
if (testpath.endsWith ("_" + tryBranding)) {
thisBranding = tryBranding;
break;
}
}
File thisjar = null; // JAR to send this file to
// Check if this file has a parent directory with the //
// same name as one of the locales. //
localeDir = checkInLocaleDir( file, locales2) ;
if( localeDir != null) {
thisLocale = localeDir ;
}
if( thisLocale != null) {
log( " Locale: " + thisLocale, Project.MSG_DEBUG) ;
} else {
log( " Locale not set", Project.MSG_DEBUG) ;
}
if( thisBranding != null) {
log( " Branding: " + thisBranding, Project.MSG_DEBUG) ;
} else {
log( " Branding not set", Project.MSG_DEBUG) ;
}
if( localeKitFiles.contains( file)) {
log( " Localizable file.", Project.MSG_DEBUG) ;
}
if (thisLocale != null || thisBranding != null || localeKitFiles.contains (file)) {
String name = jarFile.getName ();
// We know jarFile is a *.jar so this is safe:
name = name.substring (0, name.length () - 4);
if (thisBranding != null) {
name += '_' + thisBranding;
}
if (thisLocale != null) {
name += '_' + thisLocale;
}
name += ".jar";
if ((preserveModuleJar) && (thisBranding == null) && (thisLocale == null)) {
thisjar = null;
log(" Preserving module file (1): " + jarFile.getName(), Project.MSG_DEBUG);
} else {
thisjar = new File (new File (jarFile.getParentFile (), "locale"), name);
localeMarks.put (thisjar, ((thisLocale != null) ? thisLocale : "-"));
brandingMarks.put (thisjar, ((thisBranding != null) ? thisBranding : "-"));
}
} else {
if (preserveModuleJar) {
thisjar = null;
log(" Preserving module file (2): " + jarFile.getName(), Project.MSG_DEBUG);
} else {
thisjar = jarFile;
localeMarks.put (thisjar, null);
brandingMarks.put (thisjar, null);
}
}
if (thisjar != null) {
log(" Adding file " + thisjar.getName() + " to 'jars' HashSet", Project.MSG_DEBUG);
jars.add (thisjar);
Map<String, File> files = router.get (thisjar);
if (files == null) {
files = new TreeMap<> ();
router.put (thisjar, files);
}
files.put (path, file);
}
}
}
//System.err.println ("Stage #5");
// Go through JARs one by one, and build them (if necessary).
{
List<File> jars2 = new ArrayList<> (jars);
class FileNameComparator implements Comparator<File> {
public int compare (File f1, File f2) {
return f1.toString ().compareTo (f2.toString ());
}
}
Collections.sort (jars2, new FileNameComparator ());
for (File jar: jars2) {
Map<String, File> files = router.get (jar);
if (jar.exists ()) {
// Do an up-to-date check first.
long time = jar.lastModified ();
if (manifest == null || manifest.lastModified () <= time) {
boolean upToDate = true;
for(File f: files.values()) {
if (f.lastModified () > time) {
upToDate = false;
break;
}
}
if (upToDate) {
// Skip this JAR.
continue;
}
}
}
log ("Building localized/branded jar: " + jar);
IOException closing = null;
try {
jar.getParentFile ().mkdirs ();
ZipOutputStream out = new ZipOutputStream (new FileOutputStream (jar));
try {
out.setMethod (doCompress ? ZipOutputStream.DEFLATED : ZipOutputStream.STORED);
String localeMark = localeMarks.get (jar);
String brandingMark = brandingMarks.get (jar);
Set<String> addedDirs = new HashSet<> ();
// Add the manifest.
InputStream is;
long time;
java.util.jar.Manifest mani;
if (manifest != null && localeMark == null && brandingMark == null) {
// Master JAR, and it has a manifest.
is = new FileInputStream (manifest);
time = manifest.lastModified ();
try {
mani = new java.util.jar.Manifest (is);
} finally {
is.close ();
}
} else if ((manifest != null) && (alwaysIncludeManifest)) {
// always include specified manifest in localized/branded jars
// such manifest must not contain attribute OpenIDE-Module
// check supplied manifest and if contains key OpenIDE-Module, issue warning
// and fallback to default Ant's manifest boilerplate (like no manifest was supplied)
is = new FileInputStream (manifest);
time = manifest.lastModified ();
try {
mani = new java.util.jar.Manifest (is);
} finally {
is.close ();
}
Attributes attr = mani.getMainAttributes ();
//check if it's not module manifest for jarfile with localized/branded resources
if ((attr.containsKey ("OpenIDE-Module")) && ((localeMark != null) || (brandingMark != null))){
String lbmsg = "";
if (localeMark != null) {
lbmsg = "locale: '"+localeMark+"' ";
}
if (brandingMark != null) {
lbmsg = "branding: '"+brandingMark+"' ";
}
log("WARNING: Ignoring supplied NetBeans module manifest for "+lbmsg+"jarfile '"+jar+"'. Using default Ant manifest bolilerplate. "
+"Use -verbose option to see more details.", Project.MSG_INFO);
log("WARNING(verbose): Supplied manifest file '"+manifest.getAbsolutePath()+"' contains "
+"key OpenIDE-Module, which cannot be included in manifest of localized "
+"and/or branded jar. Ignoring whole manifest for now. To fix this you have "
+"to avoid using NetBeans module manifest file together with attribute "
+"'allwaysincludemanifest' set to 'true' and non-empty properties 'locjar.locales' "
+"and 'locjar.brands'. You can accomplish that by i.e. using Ant's <jar> task "
+"for regular NetBeans module jarfile packaging and use NetBeans' Ant extension "
+"task <"+this.getTaskName()+"> for localized and/or branded jars. Using default "
+"Ant's manifest boilerplate instead.", Project.MSG_VERBOSE);
try {
is.close();
} finally {
is = MatchingTask.class.getResourceAsStream ("/org/apache/tools/ant/defaultManifest.mf");
time = System.currentTimeMillis ();
try {
mani = new java.util.jar.Manifest (is);
} finally {
is.close ();
}
}
}
} else {
// Some subsidiary JAR.
is = MatchingTask.class.getResourceAsStream ("/org/apache/tools/ant/defaultManifest.mf");
time = System.currentTimeMillis ();
try {
mani = new java.util.jar.Manifest (is);
} finally {
is.close ();
}
}
Attributes attr = mani.getMainAttributes ();
if (! attr.containsKey (Attributes.Name.MANIFEST_VERSION)) {
attr.put (Attributes.Name.MANIFEST_VERSION, "1.0");
}
if (localeMark != null) {
attr.putValue ("X-Informational-Archive-Locale", localeMark);
}
if (brandingMark != null) {
attr.putValue ("X-Informational-Archive-Branding", brandingMark);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream ();
mani.write (baos);
byte[] bytes = baos.toByteArray ();
addToJar (new ByteArrayInputStream (bytes), new ByteArrayInputStream (bytes),
out, "META-INF/MANIFEST.MF", time, addedDirs);
// Now regular files.
for (Map.Entry<String, File> entry: files.entrySet()) {
String path = entry.getKey ();
File file = entry.getValue ();
addToJar (new FileInputStream (file), new FileInputStream (file),
out, path, file.lastModified (), addedDirs);
}
// If desired, write the root of the srcDir to a file. //
writeSrcDir() ;
} finally {
try {
out.close ();
} catch (IOException ex) {
closing = ex;
}
}
if (closing != null) {
// if there was a closing exception and no other one
throw closing;
}
} catch (IOException ioe) {
String msg = "Problem creating JAR: " + ioe.getMessage ();
if (! jar.delete ()) {
msg += " (and the JAR is probably corrupt but I could not delete it)";
}
throw new BuildException(msg, ioe, getLocation());
}
}
}
} // end execute()
private void addToJar (InputStream in1, InputStream in2, ZipOutputStream out,
String path, long lastModified, Set<String> addedDirs) throws IOException {
try {
if (path.endsWith ("/")) {
throw new IOException ("Bad path: " + path);
}
// Add parent dirs as needed:
int pos = -1;
while ((pos = path.indexOf ('/', pos + 1)) != -1) {
String dir = path.substring (0, pos + 1);
if (! addedDirs.contains (dir)) {
addedDirs.add (dir);
ZipEntry ze = new ZipEntry (dir);
ze.setSize (0);
ze.setMethod (ZipEntry.STORED);
ze.setCrc (emptyCrc);
ze.setTime (lastModified);
out.putNextEntry (ze);
}
}
// Add the file itself:
ZipEntry ze = new ZipEntry (path);
ze.setMethod (doCompress ? ZipEntry.DEFLATED : ZipEntry.STORED);
ze.setTime (lastModified);
long size = 0;
CRC32 crc = new CRC32 ();
byte[] buf = new byte[4096];
int read;
while ((read = in1.read (buf)) != -1) {
crc.update (buf, 0, read);
size += read;
}
in1.close ();
ze.setCrc (crc.getValue ());
ze.setSize (size);
out.putNextEntry (ze);
while ((read = in2.read (buf)) != -1) {
out.write (buf, 0, read);
}
} finally {
in2.close ();
in1.close ();
}
} // end addToJar()
// If the name of any parent directory of this file is the same as //
// one of the locales, return the locale. //
protected String checkInLocaleDir( File file,
List<String> locales) {
// See if this functionality is disabled. //
if( !checkPathLocale) {
return null ;
}
int idx ;
String locale_dir, ret = null ;
String path = file.getPath() ;
// For each locale. //
for (String loc: locales) {
// If the path contains a dir with the same name as the //
// locale. //
locale_dir = File.separator + loc + File.separator;
idx = path.indexOf( locale_dir) ;
if( idx != -1) {
// Stop and return this locale. //
ret = loc ;
break ;
}
}
return( ret) ;
}
////////////////////////////////////////////////////////////////////
// This section of code supports the feature that this class will //
// look for global properties that specify locales and brandings //
// that should be used. //
protected void addGlobalLocaleAndBranding() {
addGlobals( getGlobalLocaleVarName(), locales) ;
addGlobals( getOldGlobalLocaleVarName(), locales) ;
addGlobals( getGlobalBrandingVarName(), brandings) ;
addGlobals( getOldGlobalBrandingVarName(), brandings) ;
}
protected String getGlobalLocaleVarName() {
return( new String( "locjar.locales")) ;
}
protected String getGlobalBrandingVarName() {
return( new String( "locjar.brands")) ;
}
// For backwards compatibility. //
protected String getOldGlobalLocaleVarName() {
return( new String( "locjar_global_locales")) ;
}
// For backwards compatibility. //
protected String getOldGlobalBrandingVarName() {
return( new String( "locjar_global_brands")) ;
}
protected void addGlobals( String var_name,
List<LocaleOrB> list) {
String prop = null ;
StringTokenizer tokenizer = null ;
String tok = null ;
LocaleOrB lorb = null ;
// Foreach string in the global list. //
prop = getProject().getProperty( var_name) ;
if( prop != null && !prop.equals( "")) {
tokenizer = new StringTokenizer( prop, ", ") ;
while( tokenizer.hasMoreTokens()) {
tok = tokenizer.nextToken() ;
// Add a new entry in the given list. //
lorb = new LocaleOrB() ;
lorb.setName( tok) ;
list.add( lorb) ;
}
}
}
//////////////////////////////////////////////////////////////////////
protected boolean shouldWarnMissingDir() {
String s ;
boolean ret = false ; // Default false. //
// If the attribute is set, use its value. //
if( warnMissingDirSet) {
ret = warnMissingDir ;
}
// Otherwise use the global property value, if set. //
else {
s = getProject().getProperty("locjar.warnMissingDir");
if( s != null && !s.trim().equals( "")) {
ret = Project.toBoolean(s);
}
}
return( ret) ;
}
// If any dir's don't exist, warn the user and return true. //
protected boolean warnIfMissingDir() {
File dir ;
boolean ret = false ;
// Print warning if the basedir doesn't exist. //
if( baseDir != null && !baseDir.exists()) {
ret = true ;
printMissingDirWarning( baseDir) ;
}
// For each fileset. //
for (FileSet fileset: filesets) {
// Print warning if the dir doesn't exist. //
dir = fileset.getDir(getProject());
if( dir != null && !dir.exists()) {
ret = true ;
printMissingDirWarning( dir) ;
}
}
return( ret) ;
}
// Warn the user that the given dir doesn't exist. //
protected void printMissingDirWarning( File dir) {
log( "WARNING: Skipping this task: Directory " + dir.getPath() + " doesn't exist.") ;
}
protected boolean shouldWriteSrcDir() {
boolean ret = false ;
String s = getProject().getProperty("locjar.writeSrcDir");
if( s != null && Project.toBoolean(s)) {
ret = true ;
}
return( ret) ;
}
protected void writeSrcDir() {
String name ;
int idx, fromIdx ;
OutputStreamWriter osw ;
FileOutputStream fos ;
File file ;
if( shouldWriteSrcDir() && jarFile != null && baseDir != null) {
name = jarFile.getPath() ;
fromIdx = getNetbeansStartIdx() ;
idx = name.indexOf( File.separator+"netbeans"+File.separator, fromIdx) ;
if( idx != -1) {
try {
file = new File( name.substring( 0, idx) + File.separator + "srcdir.properties") ;
fos = new FileOutputStream( file) ;
osw = new OutputStreamWriter( fos) ;
osw.write( "srcdir=" + baseDir + "\n") ;
osw.close() ;
fos.close() ;
}
catch( Exception e) {
System.out.println( "ERROR: " + e.getMessage()) ;
e.printStackTrace() ;
throw new BuildException() ;
}
}
else {
throw new BuildException( "ERROR: Couldn't find netbeans dir to write srcdir.properties to.") ;
}
}
}
// Return the index to start searching from to find the "netbeans"
// directory into which the "srcdir.properties" file will be
// written.
protected int getNetbeansStartIdx() {
int startIdx = 0 ;
int idx ;
idx = baseDir.getPath().lastIndexOf( File.separator+
"netbeans"+File.separator) ;
if( idx != -1) {
startIdx = idx + 1 ;
}
return( startIdx) ;
}
}