PageRenderTime 195ms CodeModel.GetById 74ms app.highlight 105ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-3-pre5/org/gjt/sp/jedit/MiscUtilities.java

#
Java | 1972 lines | 1139 code | 162 blank | 671 comment | 343 complexity | 6283d17e12280140fa2f021207069bfd MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/*
   2 * MiscUtilities.java - Various miscallaneous utility functions
   3 * :tabSize=8:indentSize=8:noTabs=false:
   4 * :folding=explicit:collapseFolds=1:
   5 *
   6 * Copyright (C) 1999, 2005 Slava Pestov
   7 * Portions copyright (C) 2000 Richard S. Hall
   8 * Portions copyright (C) 2001 Dirk Moebius
   9 *
  10 * This program is free software; you can redistribute it and/or
  11 * modify it under the terms of the GNU General Public License
  12 * as published by the Free Software Foundation; either version 2
  13 * of the License, or any later version.
  14 *
  15 * This program is distributed in the hope that it will be useful,
  16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  18 * GNU General Public License for more details.
  19 *
  20 * You should have received a copy of the GNU General Public License
  21 * along with this program; if not, write to the Free Software
  22 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  23 */
  24
  25package org.gjt.sp.jedit;
  26
  27//{{{ Imports
  28import javax.swing.text.Segment;
  29import javax.swing.JMenuItem;
  30import java.io.*;
  31import java.net.MalformedURLException;
  32import java.net.URL;
  33import java.nio.charset.Charset;
  34import java.text.DecimalFormat;
  35import java.util.*;
  36import java.util.List;
  37import java.util.zip.GZIPInputStream;
  38
  39import org.xml.sax.InputSource;
  40import org.xml.sax.SAXException;
  41import org.xml.sax.SAXParseException;
  42import org.xml.sax.XMLReader;
  43import org.xml.sax.helpers.DefaultHandler;
  44import org.xml.sax.helpers.XMLReaderFactory;
  45
  46import org.gjt.sp.jedit.io.*;
  47import org.gjt.sp.util.Log;
  48import org.gjt.sp.util.ProgressObserver;
  49import org.gjt.sp.util.StandardUtilities;
  50import org.gjt.sp.util.IOUtilities;
  51import org.gjt.sp.jedit.menu.EnhancedMenuItem;
  52import org.gjt.sp.jedit.buffer.BufferIORequest;
  53import org.gjt.sp.jedit.buffer.JEditBuffer;
  54//}}}
  55
  56/**
  57 * Path name manipulation, string manipulation, and more.<p>
  58 *
  59 * The most frequently used members of this class are:<p>
  60 *
  61 * <b>Some path name methods:</b><p>
  62 * <ul>
  63 * <li>{@link #getFileName(String)}</li>
  64 * <li>{@link #getParentOfPath(String)}</li>
  65 * <li>{@link #constructPath(String,String)}</li>
  66 * </ul>
  67 * <b>String comparison:</b><p>
  68
  69 * A {@link #compareStrings(String,String,boolean)} method that unlike
  70 * <function>String.compareTo()</function>, correctly recognizes and handles
  71 * embedded numbers.<p>
  72 *
  73 * This class also defines several inner classes for use with the
  74 * sorting features of the Java collections API:
  75 *
  76 * <ul>
  77 * <li>{@link MiscUtilities.StringCompare}</li>
  78 * <li>{@link MiscUtilities.StringICaseCompare}</li>
  79 * <li>{@link MiscUtilities.MenuItemCompare}</li>
  80 * </ul>
  81 *
  82 * For example, you might call:<p>
  83 *
  84 * <code>Arrays.sort(myListOfStrings,
  85 *     new MiscUtilities.StringICaseCompare());</code>
  86 *
  87 * @author Slava Pestov
  88 * @author John Gellene (API documentation)
  89 * @version $Id: MiscUtilities.java 5487 2006-06-23 22:58:12Z kpouer $
  90 */
  91public class MiscUtilities
  92{
  93	/**
  94	 * This encoding is not supported by Java, yet it is useful.
  95	 * A UTF-8 file that begins with 0xEFBBBF.
  96	 */
  97	public static final String UTF_8_Y = "UTF-8Y";
  98
  99	//{{{ Path name methods
 100
 101	//{{{ canonPath() method
 102	/**
 103	 * Returns the canonical form of the specified path name. Currently
 104	 * only expands a leading <code>~</code>. <b>For local path names
 105	 * only.</b>
 106	 * @param path The path name
 107	 * @since jEdit 4.0pre2
 108	 */
 109	public static String canonPath(String path)
 110	{
 111		if(path.length() == 0)
 112			return path;
 113
 114		if(path.startsWith("file://"))
 115			path = path.substring("file://".length());
 116		else if(path.startsWith("file:"))
 117			path = path.substring("file:".length());
 118		else if(isURL(path))
 119			return path;
 120
 121		if(File.separatorChar == '\\')
 122			{
 123				// get rid of mixed paths on Windows
 124				path = path.replace('/','\\');
 125				// also get rid of trailing spaces on Windows
 126				int trim = path.length();
 127				while(path.charAt(trim - 1) == ' ')
 128					trim--;
 129				path = path.substring(0,trim);
 130			}
 131		else if(OperatingSystem.isMacOS())
 132			{
 133				// do the same on OS X
 134				path = path.replace(':','/');
 135			}
 136
 137		if(path.startsWith('~' + File.separator))
 138			{
 139				path = path.substring(2);
 140				String home = System.getProperty("user.home");
 141
 142				if(home.endsWith(File.separator))
 143					return home + path;
 144				else
 145					return home + File.separator + path;
 146			}
 147		else if(path.equals("~"))
 148			return System.getProperty("user.home");
 149		else
 150			return path;
 151	} //}}}
 152
 153	//{{{ resolveSymlinks() method
 154	/**
 155	 * Resolves any symbolic links in the path name specified
 156	 * using <code>File.getCanonicalPath()</code>. <b>For local path
 157	 * names only.</b>
 158	 * @since jEdit 4.2pre1
 159	 */
 160	public static String resolveSymlinks(String path)
 161	{
 162		if(isURL(path))
 163			return path;
 164
 165		// 2 aug 2003: OS/2 Java has a broken getCanonicalPath()
 166		if(OperatingSystem.isOS2())
 167			return path;
 168		// 18 nov 2003: calling this on a drive letter on Windows causes
 169		// drive access
 170		if(OperatingSystem.isDOSDerived())
 171			{
 172				if(path.length() == 2 || path.length() == 3)
 173					{
 174						if(path.charAt(1) == ':')
 175							return path;
 176					}
 177			}
 178		try
 179			{
 180				return new File(path).getCanonicalPath();
 181			}
 182		catch(IOException io)
 183			{
 184				return path;
 185			}
 186	} //}}}
 187
 188	//{{{ isAbsolutePath() method
 189	/**
 190	 * Returns if the specified path name is an absolute path or URL.
 191	 * @since jEdit 4.1pre11
 192	 */
 193	public static boolean isAbsolutePath(String path)
 194	{
 195		if(isURL(path))
 196			return true;
 197		else if(path.startsWith("~/") || path.startsWith("~" + File.separator) || path.equals("~"))
 198			return true;
 199		else if(OperatingSystem.isDOSDerived())
 200			{
 201				if(path.length() == 2 && path.charAt(1) == ':')
 202					return true;
 203				if(path.length() > 2 && path.charAt(1) == ':'
 204				   && (path.charAt(2) == '\\'
 205					   || path.charAt(2) == '/'))
 206					return true;
 207				if(path.startsWith("\\\\")
 208				   || path.startsWith("//"))
 209					return true;
 210			}
 211		// not sure if this is correct for OpenVMS.
 212		else if(OperatingSystem.isUnix()
 213				|| OperatingSystem.isVMS())
 214			{
 215				// nice and simple
 216				if(path.length() > 0 && path.charAt(0) == '/')
 217					return true;
 218			}
 219
 220		return false;
 221	} //}}}
 222
 223	//{{{ constructPath() method
 224	/**
 225	 * Constructs an absolute path name from a directory and another
 226	 * path name. This method is VFS-aware.
 227	 * @param parent The directory
 228	 * @param path The path name
 229	 */
 230	public static String constructPath(String parent, String path)
 231	{
 232		if(isAbsolutePath(path))
 233			return canonPath(path);
 234
 235		// have to handle this case specially on windows.
 236		// insert \ between, eg A: and myfile.txt.
 237		if(OperatingSystem.isDOSDerived())
 238			{
 239				if(path.length() == 2 && path.charAt(1) == ':')
 240					return path;
 241				else if(path.length() > 2 && path.charAt(1) == ':'
 242						&& path.charAt(2) != '\\')
 243					{
 244						path = path.substring(0,2) + '\\'
 245							+ path.substring(2);
 246						return canonPath(path);
 247					}
 248			}
 249
 250		String dd = ".." + File.separator;
 251		String d = '.' + File.separator;
 252
 253		if(parent == null)
 254			parent = System.getProperty("user.dir");
 255
 256		for(;;)
 257			{
 258				if(path.equals("."))
 259					return parent;
 260				else if(path.equals(".."))
 261					return getParentOfPath(parent);
 262				else if(path.startsWith(dd) || path.startsWith("../"))
 263					{
 264						parent = getParentOfPath(parent);
 265						path = path.substring(3);
 266					}
 267				else if(path.startsWith(d) || path.startsWith("./"))
 268					path = path.substring(2);
 269				else
 270					break;
 271			}
 272
 273		if(OperatingSystem.isDOSDerived()
 274		   && !isURL(parent)
 275		   && path.charAt(0) == '\\')
 276			parent = parent.substring(0,2);
 277
 278		VFS vfs = VFSManager.getVFSForPath(parent);
 279
 280		return canonPath(vfs.constructPath(parent,path));
 281	} //}}}
 282
 283	//{{{ constructPath() method
 284	/**
 285	 * Constructs an absolute path name from three path components.
 286	 * This method is VFS-aware.
 287	 * @param parent The parent directory
 288	 * @param path1 The first path
 289	 * @param path2 The second path
 290	 */
 291	public static String constructPath(String parent,
 292									   String path1, String path2)
 293	{
 294		return constructPath(constructPath(parent,path1),path2);
 295	} //}}}
 296
 297	//{{{ concatPath() method
 298	/**
 299	 * Like {@link #constructPath}, except <code>path</code> will be
 300	 * appended to <code>parent</code> even if it is absolute.
 301	 * <b>For local path names only.</b>.
 302	 *
 303	 * @param path
 304	 * @param parent
 305	 */
 306	public static String concatPath(String parent, String path)
 307	{
 308		parent = canonPath(parent);
 309		path = canonPath(path);
 310
 311		// Make all child paths relative.
 312		if (path.startsWith(File.separator))
 313			path = path.substring(1);
 314		else if ((path.length() >= 3) && (path.charAt(1) == ':'))
 315			path = path.replace(':', File.separatorChar);
 316
 317		if (parent == null)
 318			parent = System.getProperty("user.dir");
 319
 320		if (parent.endsWith(File.separator))
 321			return parent + path;
 322		else
 323			return parent + File.separator + path;
 324	} //}}}
 325
 326	//{{{ getFirstSeparatorIndex() method
 327	/**
 328	 * Return the first index of either / or the OS-specific file
 329	 * separator.
 330	 * @param path The path
 331	 * @since jEdit 4.3pre3
 332	 */
 333	public static int getFirstSeparatorIndex(String path)
 334	{
 335		int start = getPathStart(path);
 336		int index = path.indexOf('/',start);
 337		if(index == -1)
 338			index = path.indexOf(File.separatorChar,start);
 339		return index;
 340	} //}}}
 341
 342	//{{{ getLastSeparatorIndex() method
 343	/**
 344	 * Return the last index of either / or the OS-specific file
 345	 * separator.
 346	 * @param path The path
 347	 * @since jEdit 4.3pre3
 348	 */
 349	public static int getLastSeparatorIndex(String path)
 350	{
 351		int start = getPathStart(path);
 352		if(start != 0)
 353			path = path.substring(start);
 354		int index = Math.max(path.lastIndexOf('/'),
 355							 path.lastIndexOf(File.separatorChar));
 356		if(index == -1)
 357			return index;
 358		else
 359			return index + start;
 360	} //}}}
 361
 362	//{{{ getFileExtension() method
 363	/**
 364	 * Returns the extension of the specified filename, or an empty
 365	 * string if there is none.
 366	 * @param path The path
 367	 */
 368	public static String getFileExtension(String path)
 369	{
 370		int fsIndex = getLastSeparatorIndex(path);
 371		int index = path.indexOf('.',fsIndex);
 372		if(index == -1)
 373			return "";
 374		else
 375			return path.substring(index);
 376	} //}}}
 377
 378	//{{{ getFileName() method
 379	/**
 380	 * Returns the last component of the specified path.
 381	 * This method is VFS-aware.
 382	 * @param path The path name
 383	 */
 384	public static String getFileName(String path)
 385	{
 386		return VFSManager.getVFSForPath(path).getFileName(path);
 387	} //}}}
 388
 389	//{{{ getFileNameNoExtension() method
 390	/**
 391	 * Returns the last component of the specified path name without the
 392	 * trailing extension (if there is one).
 393	 * @param path The path name
 394	 * @since jEdit 4.0pre8
 395	 */
 396	public static String getFileNameNoExtension(String path)
 397	{
 398		String name = getFileName(path);
 399		int index = name.indexOf('.');
 400		if(index == -1)
 401			return name;
 402		else
 403			return name.substring(0,index);
 404	} //}}}
 405
 406	//{{{ getFileParent() method
 407	/**
 408	 * @deprecated Call getParentOfPath() instead
 409	 */
 410	public static String getFileParent(String path)
 411	{
 412		return getParentOfPath(path);
 413	} //}}}
 414
 415	//{{{ getParentOfPath() method
 416	/**
 417	 * Returns the parent of the specified path. This method is VFS-aware.
 418	 * @param path The path name
 419	 * @since jEdit 2.6pre5
 420	 */
 421	public static String getParentOfPath(String path)
 422	{
 423		return VFSManager.getVFSForPath(path).getParentOfPath(path);
 424	} //}}}
 425
 426	//{{{ getFileProtocol() method
 427	/**
 428	 * @deprecated Call getProtocolOfURL() instead
 429	 */
 430	public static String getFileProtocol(String url)
 431	{
 432		return getProtocolOfURL(url);
 433	} //}}}
 434
 435	//{{{ getProtocolOfURL() method
 436	/**
 437	 * Returns the protocol specified by a URL.
 438	 * @param url The URL
 439	 * @since jEdit 2.6pre5
 440	 */
 441	public static String getProtocolOfURL(String url)
 442	{
 443		return url.substring(0,url.indexOf(':'));
 444	} //}}}
 445
 446	//{{{ isURL() method
 447	/**
 448	 * Checks if the specified string is a URL.
 449	 * @param str The string to check
 450	 * @return True if the string is a URL, false otherwise
 451	 */
 452	public static boolean isURL(String str)
 453	{
 454		int fsIndex = getLastSeparatorIndex(str);
 455		if(fsIndex == 0) // /etc/passwd
 456			return false;
 457		else if(fsIndex == 2) // C:\AUTOEXEC.BAT
 458			return false;
 459
 460		int cIndex = str.indexOf(':');
 461		if(cIndex <= 1) // D:\WINDOWS, or doesn't contain : at all
 462			return false;
 463
 464		String protocol = str.substring(0,cIndex);
 465		VFS vfs = VFSManager.getVFSForProtocol(protocol);
 466		if(vfs != null && !(vfs instanceof UrlVFS))
 467			return true;
 468
 469		try
 470			{
 471				new URL(str);
 472				return true;
 473			}
 474		catch(MalformedURLException mf)
 475			{
 476				return false;
 477			}
 478	} //}}}
 479
 480	//{{{ saveBackup() method
 481	/**
 482	 * Saves a backup (optionally numbered) of a file.
 483	 * @param file A local file
 484	 * @param backups The number of backups. Must be >= 1. If > 1, backup
 485	 * files will be numbered.
 486	 * @param backupPrefix The backup file name prefix
 487	 * @param backupSuffix The backup file name suffix
 488	 * @param backupDirectory The directory where to save backups; if null,
 489	 * they will be saved in the same directory as the file itself.
 490	 * @since jEdit 4.0pre1
 491	 */
 492	public static void saveBackup(File file, int backups,
 493								  String backupPrefix, String backupSuffix,
 494								  String backupDirectory)
 495	{
 496		saveBackup(file,backups,backupPrefix,backupSuffix,backupDirectory,0);
 497	} //}}}
 498
 499	//{{{ saveBackup() method
 500	/**
 501	 * Saves a backup (optionally numbered) of a file.
 502	 * @param file A local file
 503	 * @param backups The number of backups. Must be >= 1. If > 1, backup
 504	 * files will be numbered.
 505	 * @param backupPrefix The backup file name prefix
 506	 * @param backupSuffix The backup file name suffix
 507	 * @param backupDirectory The directory where to save backups; if null,
 508	 * they will be saved in the same directory as the file itself.
 509	 * @param backupTimeDistance The minimum time in minutes when a backup
 510	 * version 1 shall be moved into version 2; if 0, backups are always
 511	 * moved.
 512	 * @since jEdit 4.2pre5
 513	 */
 514	public static void saveBackup(File file, int backups,
 515								  String backupPrefix, String backupSuffix,
 516								  String backupDirectory, int backupTimeDistance)
 517	{
 518		if(backupPrefix == null)
 519			backupPrefix = "";
 520		if(backupSuffix == null)
 521			backupSuffix = "";
 522
 523		String name = file.getName();
 524
 525		// If backups is 1, create ~ file
 526		if(backups == 1)
 527			{
 528				File backupFile = new File(backupDirectory,
 529										   backupPrefix + name + backupSuffix);
 530				long modTime = backupFile.lastModified();
 531				/* if backup file was created less than
 532				 * 'backupTimeDistance' ago, we do not
 533				 * create the backup */
 534				if(System.currentTimeMillis() - modTime
 535				   >= backupTimeDistance)
 536					{
 537						backupFile.delete();
 538						if (!file.renameTo(backupFile))
 539							moveFile(file, backupFile);
 540					}
 541			}
 542		// If backups > 1, move old ~n~ files, create ~1~ file
 543		else
 544			{
 545				/* delete a backup created using above method */
 546				new File(backupDirectory,
 547						 backupPrefix + name + backupSuffix
 548						 + backups + backupSuffix).delete();
 549
 550				File firstBackup = new File(backupDirectory,
 551											backupPrefix + name + backupSuffix
 552											+ "1" + backupSuffix);
 553				long modTime = firstBackup.lastModified();
 554				/* if backup file was created less than
 555				 * 'backupTimeDistance' ago, we do not
 556				 * create the backup */
 557				if(System.currentTimeMillis() - modTime
 558				   >= backupTimeDistance)
 559					{
 560						for(int i = backups - 1; i > 0; i--)
 561							{
 562								File backup = new File(backupDirectory,
 563													   backupPrefix + name
 564													   + backupSuffix + i
 565													   + backupSuffix);
 566
 567								backup.renameTo(
 568												new File(backupDirectory,
 569														 backupPrefix + name
 570														 + backupSuffix + (i+1)
 571														 + backupSuffix));
 572							}
 573
 574						File backupFile = new File(backupDirectory,
 575												   backupPrefix + name + backupSuffix
 576												   + "1" + backupSuffix);
 577						if (!file.renameTo(backupFile))
 578							moveFile(file, backupFile);
 579					}
 580			}
 581	} //}}}
 582
 583	//{{{ moveFile() method
 584	/**
 585	 * Moves the source file to the destination.
 586	 *
 587	 * If the destination cannot be created or is a read-only file, the
 588	 * method returns <code>false</code>. Otherwise, the contents of the
 589	 * source are copied to the destination, the source is deleted,
 590	 * and <code>true</code> is returned.
 591	 *
 592	 * @param source The source file to move.
 593	 * @param dest   The destination where to move the file.
 594	 * @return true on success, false otherwise.
 595	 *
 596	 * @since jEdit 4.3pre1
 597	 */
 598	public static boolean moveFile(File source, File dest)
 599	{
 600		boolean ok = false;
 601
 602		if ((dest.exists() && dest.canWrite())
 603			|| (!dest.exists() && dest.getParentFile().canWrite()))
 604			{
 605				OutputStream fos = null;
 606				InputStream fis = null;
 607				try
 608					{
 609						fos = new FileOutputStream(dest);
 610						fis = new FileInputStream(source);
 611						ok = copyStream(32768,null,fis,fos,false);
 612					}
 613				catch (IOException ioe)
 614					{
 615						Log.log(Log.WARNING, MiscUtilities.class,
 616								"Error moving file: " + ioe + " : " + ioe.getMessage());
 617					}
 618				finally
 619					{
 620						try
 621							{
 622								if(fos != null)
 623									fos.close();
 624								if(fis != null)
 625									fis.close();
 626							}
 627						catch(Exception e)
 628							{
 629								Log.log(Log.ERROR,MiscUtilities.class,e);
 630							}
 631					}
 632
 633				if(ok)
 634					source.delete();
 635			}
 636		return ok;
 637	} //}}}
 638
 639	//{{{ copyStream() method
 640	/**
 641	 * Copy an input stream to an output stream.
 642	 *
 643	 * @param bufferSize the size of the buffer
 644	 * @param progress the progress observer it could be null
 645	 * @param in the input stream
 646	 * @param out the output stream
 647	 * @param canStop if true, the copy can be stopped by interrupting the thread
 648	 * @return <code>true</code> if the copy was done, <code>false</code> if it was interrupted
 649	 * @throws IOException  IOException If an I/O error occurs
 650	 * @since jEdit 4.3pre3
 651	 * @deprecated use {@link IOUtilities#copyStream(int, org.gjt.sp.util.ProgressObserver, java.io.InputStream, java.io.OutputStream, boolean)}
 652	 */
 653	public static boolean copyStream(int bufferSize, ProgressObserver progress,
 654									 InputStream in, OutputStream out, boolean canStop)
 655		throws IOException
 656	{
 657		return IOUtilities.copyStream(bufferSize, progress, in, out, canStop);
 658	} //}}}
 659
 660	//{{{ copyStream() method
 661	/**
 662	 * Copy an input stream to an output stream with a buffer of 4096 bytes.
 663	 *
 664	 * @param progress the progress observer it could be null
 665	 * @param in the input stream
 666	 * @param out the output stream
 667	 * @param canStop if true, the copy can be stopped by interrupting the thread
 668	 * @return <code>true</code> if the copy was done, <code>false</code> if it was interrupted
 669	 * @throws IOException  IOException If an I/O error occurs
 670	 * @since jEdit 4.3pre3
 671	 * @deprecated use {@link IOUtilities#copyStream(org.gjt.sp.util.ProgressObserver, java.io.InputStream, java.io.OutputStream, boolean)}
 672	 */
 673	public static boolean copyStream(ProgressObserver progress,
 674									 InputStream in, OutputStream out, boolean canStop)
 675		throws IOException
 676	{
 677		return copyStream(4096,progress, in, out, canStop);
 678	} //}}}
 679
 680	//{{{ isBinaryFile() method
 681	/**
 682	* Check if a Reader is binary.
 683	* To check if a file is binary, we will check the first characters 100
 684	* (jEdit property vfs.binaryCheck.length)
 685	* If more than 1 (jEdit property vfs.binaryCheck.count), the
 686	* file is declared binary.
 687	* This is not 100% because sometimes the autodetection could fail.
 688	* This method will not close your reader. You have to do it yourself
 689	*
 690	* @param reader the reader
 691	* @return <code>true</code> if the Reader was detected as binary
 692	* @throws IOException IOException If an I/O error occurs
 693	* @since jEdit 4.3pre5
 694	*/
 695	public static boolean isBinary(Reader reader)
 696	throws IOException
 697	{
 698		int nbChars = jEdit.getIntegerProperty("vfs.binaryCheck.length",100);
 699		int authorized = jEdit.getIntegerProperty("vfs.binaryCheck.count",1);
 700		for (long i = 0L;i < nbChars;i++)
 701		{
 702			int c = reader.read();
 703			if (c == -1)
 704				return false;
 705			if (c == 0)
 706			{
 707				authorized--;
 708				if (authorized == 0)
 709					return true;
 710			}
 711		}
 712		return false;
 713	} //}}}
 714
 715	//{{{ isBackup() method
 716	/**
 717	 * Check if the filename is a backup file.
 718	 * @param filename the filename to check
 719	 * @return true if this is a backup file.
 720	 * @since jEdit 4.3pre5
 721	 */
 722	public static boolean isBackup( String filename ) {
 723		if (filename.startsWith("#")) return true;
 724		if (filename.endsWith("~")) return true;
 725		if (filename.endsWith(".bak")) return true;
 726		return false;
 727	} //}}}
 728
 729
 730	//{{{ autodetect() method
 731	/**
 732	 * Tries to detect if the stream is gzipped, and if it has an encoding
 733	 * specified with an XML PI.
 734	 *
 735	 * @param in the input stream reader that must be autodetected
 736	 * @param buffer a buffer. It can be null if you only want to autodetect the encoding of a file
 737	 * @return a reader using the detected encoding
 738	 * @throws IOException io exception during read
 739	 * @since jEdit 4.3pre5
 740	 */
 741	public static Reader autodetect(InputStream in, Buffer buffer) throws IOException
 742	{
 743		in = new BufferedInputStream(in);
 744
 745		String encoding;
 746		if (buffer == null)
 747			encoding = System.getProperty("file.encoding");
 748		else
 749			encoding = buffer.getStringProperty(JEditBuffer.ENCODING);
 750
 751		if(!in.markSupported())
 752			Log.log(Log.WARNING,MiscUtilities.class,"Mark not supported: " + in);
 753		else if(buffer == null || buffer.getBooleanProperty(Buffer.ENCODING_AUTODETECT))
 754		{
 755			in.mark(BufferIORequest.XML_PI_LENGTH);
 756			int b1 = in.read();
 757			int b2 = in.read();
 758			int b3 = in.read();
 759
 760			if(b1 == BufferIORequest.GZIP_MAGIC_1 && b2 == BufferIORequest.GZIP_MAGIC_2)
 761			{
 762				in.reset();
 763				in = new GZIPInputStream(in);
 764				if (buffer != null)
 765					buffer.setBooleanProperty(Buffer.GZIPPED,true);
 766				// auto-detect encoding within the gzip stream.
 767				return autodetect(in, buffer);
 768			}
 769			else if (b1 == BufferIORequest.UNICODE_MAGIC_1
 770				&& b2 == BufferIORequest.UNICODE_MAGIC_2)
 771			{
 772				in.reset();
 773				in.read();
 774				in.read();
 775				encoding = "UTF-16BE";
 776				if (buffer != null)
 777					buffer.setProperty(JEditBuffer.ENCODING,encoding);
 778			}
 779			else if (b1 == BufferIORequest.UNICODE_MAGIC_2
 780				&& b2 == BufferIORequest.UNICODE_MAGIC_1)
 781			{
 782				in.reset();
 783				in.read();
 784				in.read();
 785				encoding = "UTF-16LE";
 786				if (buffer != null)
 787					buffer.setProperty(JEditBuffer.ENCODING,encoding);
 788			}
 789			else if(b1 == BufferIORequest.UTF8_MAGIC_1 && b2 == BufferIORequest.UTF8_MAGIC_2
 790				&& b3 == BufferIORequest.UTF8_MAGIC_3)
 791			{
 792				// do not reset the stream and just treat it
 793				// like a normal UTF-8 file.
 794				if (buffer != null)
 795					buffer.setProperty(JEditBuffer.ENCODING, MiscUtilities.UTF_8_Y);
 796
 797				encoding = "UTF-8";
 798			}
 799			else
 800			{
 801				in.reset();
 802
 803				byte[] _xmlPI = new byte[BufferIORequest.XML_PI_LENGTH];
 804				int offset = 0;
 805				int count;
 806				while((count = in.read(_xmlPI,offset,
 807					BufferIORequest.XML_PI_LENGTH - offset)) != -1)
 808				{
 809					offset += count;
 810					if(offset == BufferIORequest.XML_PI_LENGTH)
 811						break;
 812				}
 813
 814				String xmlEncoding = getXMLEncoding(new String(
 815					_xmlPI,0,offset,"ASCII"));
 816				if(xmlEncoding != null)
 817				{
 818					encoding = xmlEncoding;
 819					if (buffer != null)
 820						buffer.setProperty(JEditBuffer.ENCODING,encoding);
 821				}
 822
 823				if(encoding.equals(MiscUtilities.UTF_8_Y))
 824					encoding = "UTF-8";
 825
 826				in.reset();
 827			}
 828		}
 829
 830		return new InputStreamReader(in,encoding);
 831	} //}}}
 832
 833	//{{{ getXMLEncoding() method
 834	/**
 835	 * Extract XML encoding name from PI.
 836	 */
 837	private static String getXMLEncoding(String xmlPI)
 838	{
 839		if(!xmlPI.startsWith("<?xml"))
 840			return null;
 841
 842		int index = xmlPI.indexOf("encoding=");
 843		if(index == -1 || index + 9 == xmlPI.length())
 844			return null;
 845
 846		char ch = xmlPI.charAt(index + 9);
 847		int endIndex = xmlPI.indexOf(ch,index + 10);
 848		if(endIndex == -1)
 849			return null;
 850
 851		String encoding = xmlPI.substring(index + 10,endIndex);
 852
 853		if(Charset.isSupported(encoding))
 854			return encoding;
 855		else
 856		{
 857			Log.log(Log.WARNING,MiscUtilities.class,"XML PI specifies "
 858				+ "unsupported encoding: " + encoding);
 859			return null;
 860		}
 861	} //}}}
 862
 863	//{{{ closeQuietly() method
 864	/**
 865	 * Method that will close an {@link InputStream} ignoring it if it is null and ignoring exceptions.
 866	 *
 867	 * @param in the InputStream to close.
 868	 * @since jEdit 4.3pre3
 869	 * @deprecated use {@link IOUtilities#closeQuietly(java.io.InputStream)}
 870	 */
 871	public static void closeQuietly(InputStream in)
 872	{
 873		IOUtilities.closeQuietly(in);
 874	} //}}}
 875
 876	//{{{ copyStream() method
 877	/**
 878	 * Method that will close an {@link OutputStream} ignoring it if it is null and ignoring exceptions.
 879	 *
 880	 * @param out the OutputStream to close.
 881	 * @since jEdit 4.3pre3
 882	 * @deprecated use {@link IOUtilities#closeQuietly(java.io.OutputStream)}
 883	 */
 884	public static void closeQuietly(OutputStream out)
 885	{
 886		IOUtilities.closeQuietly(out);
 887	} //}}}
 888
 889	//{{{ fileToClass() method
 890	/**
 891	 * Converts a file name to a class name. All slash characters are
 892	 * replaced with periods and the trailing '.class' is removed.
 893	 * @param name The file name
 894	 */
 895	public static String fileToClass(String name)
 896	{
 897		char[] clsName = name.toCharArray();
 898		for(int i = clsName.length - 6; i >= 0; i--)
 899			if(clsName[i] == '/')
 900				clsName[i] = '.';
 901		return new String(clsName,0,clsName.length - 6);
 902	} //}}}
 903
 904	//{{{ classToFile() method
 905	/**
 906	 * Converts a class name to a file name. All periods are replaced
 907	 * with slashes and the '.class' extension is added.
 908	 * @param name The class name
 909	 */
 910	public static String classToFile(String name)
 911	{
 912		return name.replace('.','/').concat(".class");
 913	} //}}}
 914
 915	//{{{ pathsEqual() method
 916	/**
 917	 * @param p1 A path name
 918	 * @param p2 A path name
 919	 * @return True if both paths are equal, ignoring trailing slashes, as
 920	 * well as case insensitivity on Windows.
 921	 * @since jEdit 4.3pre2
 922	 */
 923	public static boolean pathsEqual(String p1, String p2)
 924	{
 925		VFS v1 = VFSManager.getVFSForPath(p1);
 926		VFS v2 = VFSManager.getVFSForPath(p2);
 927
 928		if(v1 != v2)
 929			return false;
 930
 931		if(p1.endsWith("/") || p1.endsWith(File.separator))
 932			p1 = p1.substring(0,p1.length() - 1);
 933
 934		if(p2.endsWith("/") || p2.endsWith(File.separator))
 935			p2 = p2.substring(0,p2.length() - 1);
 936
 937		if((v1.getCapabilities() & VFS.CASE_INSENSITIVE_CAP) != 0)
 938			return p1.equalsIgnoreCase(p2);
 939		else
 940			return p1.equals(p2);
 941	} //}}}
 942
 943	//}}}
 944
 945	//{{{ Text methods
 946
 947	//{{{ getLeadingWhiteSpace() method
 948	/**
 949	 * Returns the number of leading white space characters in the
 950	 * specified string.
 951	 * @param str The string
 952	 * @deprecated use {@link StandardUtilities#getLeadingWhiteSpace(String)}
 953	 */
 954	public static int getLeadingWhiteSpace(String str)
 955	{
 956		return StandardUtilities.getLeadingWhiteSpace(str);
 957	} //}}}
 958
 959	//{{{ getTrailingWhiteSpace() method
 960	/**
 961	 * Returns the number of trailing whitespace characters in the
 962	 * specified string.
 963	 * @param str The string
 964	 * @since jEdit 2.5pre5
 965	 * @deprecated use {@link StandardUtilities#getTrailingWhiteSpace(String)}
 966	 */
 967	public static int getTrailingWhiteSpace(String str)
 968	{
 969		return StandardUtilities.getTrailingWhiteSpace(str);
 970	} //}}}
 971
 972	//{{{ getLeadingWhiteSpaceWidth() method
 973	/**
 974	 * Returns the width of the leading white space in the specified
 975	 * string.
 976	 * @param str The string
 977	 * @param tabSize The tab size
 978	 * @deprecated use {@link StandardUtilities#getLeadingWhiteSpace(String)}
 979	 */
 980	public static int getLeadingWhiteSpaceWidth(String str, int tabSize)
 981	{
 982		return StandardUtilities.getLeadingWhiteSpaceWidth(str, tabSize);
 983	} //}}}
 984
 985	//{{{ getVirtualWidth() method
 986	/**
 987	 * Returns the virtual column number (taking tabs into account) of the
 988	 * specified offset in the segment.
 989	 *
 990	 * @param seg The segment
 991	 * @param tabSize The tab size
 992	 * @since jEdit 4.1pre1
 993	 * @deprecated use {@link StandardUtilities#getVirtualWidth(javax.swing.text.Segment, int)}
 994	 */
 995	public static int getVirtualWidth(Segment seg, int tabSize)
 996	{
 997		return StandardUtilities.getVirtualWidth(seg, tabSize);
 998	} //}}}
 999
1000	//{{{ getOffsetOfVirtualColumn() method
1001	/**
1002	 * Returns the array offset of a virtual column number (taking tabs
1003	 * into account) in the segment.
1004	 *
1005	 * @param seg The segment
1006	 * @param tabSize The tab size
1007	 * @param column The virtual column number
1008	 * @param totalVirtualWidth If this array is non-null, the total
1009	 * virtual width will be stored in its first location if this method
1010	 * returns -1.
1011	 *
1012	 * @return -1 if the column is out of bounds
1013	 *
1014	 * @since jEdit 4.1pre1
1015	 * @deprecated use {@link StandardUtilities#getVirtualWidth(javax.swing.text.Segment, int)}
1016	 */
1017	public static int getOffsetOfVirtualColumn(Segment seg, int tabSize,
1018					    int column, int[] totalVirtualWidth)
1019	{
1020		return StandardUtilities.getOffsetOfVirtualColumn(seg, tabSize, column, totalVirtualWidth);
1021	} //}}}
1022
1023	//{{{ createWhiteSpace() method
1024	/**
1025	 * Creates a string of white space with the specified length.<p>
1026	 *
1027	 * To get a whitespace string tuned to the current buffer's
1028	 * settings, call this method as follows:
1029	 *
1030	 * <pre>myWhitespace = MiscUtilities.createWhiteSpace(myLength,
1031	 *     (buffer.getBooleanProperty("noTabs") ? 0
1032	 *     : buffer.getTabSize()));</pre>
1033	 *
1034	 * @param len The length
1035	 * @param tabSize The tab size, or 0 if tabs are not to be used
1036	 * @deprecated use {@link StandardUtilities#createWhiteSpace(int, int)}
1037	 */
1038	public static String createWhiteSpace(int len, int tabSize)
1039	{
1040		return StandardUtilities.createWhiteSpace(len,tabSize,0);
1041	} //}}}
1042
1043	//{{{ createWhiteSpace() method
1044	/**
1045	 * Creates a string of white space with the specified length.<p>
1046	 *
1047	 * To get a whitespace string tuned to the current buffer's
1048	 * settings, call this method as follows:
1049	 *
1050	 * <pre>myWhitespace = MiscUtilities.createWhiteSpace(myLength,
1051	 *     (buffer.getBooleanProperty("noTabs") ? 0
1052	 *     : buffer.getTabSize()));</pre>
1053	 *
1054	 * @param len The length
1055	 * @param tabSize The tab size, or 0 if tabs are not to be used
1056	 * @param start The start offset, for tab alignment
1057	 * @since jEdit 4.2pre1
1058	 * @deprecated use {@link StandardUtilities#createWhiteSpace(int, int, int)}
1059	 */
1060	public static String createWhiteSpace(int len, int tabSize, int start)
1061	{
1062		return StandardUtilities.createWhiteSpace(len, tabSize, start);
1063	} //}}}
1064
1065	//{{{ globToRE() method
1066	/**
1067	 * Converts a Unix-style glob to a regular expression.<p>
1068	 *
1069	 * ? becomes ., * becomes .*, {aa,bb} becomes (aa|bb).
1070	 * @param glob The glob pattern
1071	 */
1072	public static String globToRE(String glob)
1073	{
1074		final Object NEG = new Object();
1075		final Object GROUP = new Object();
1076		Stack state = new Stack();
1077
1078		StringBuffer buf = new StringBuffer();
1079		boolean backslash = false;
1080
1081		for(int i = 0; i < glob.length(); i++)
1082		{
1083			char c = glob.charAt(i);
1084			if(backslash)
1085			{
1086				buf.append('\\');
1087				buf.append(c);
1088				backslash = false;
1089				continue;
1090			}
1091
1092			switch(c)
1093			{
1094			case '\\':
1095				backslash = true;
1096				break;
1097			case '?':
1098				buf.append('.');
1099				break;
1100			case '.':
1101			case '+':
1102			case '(':
1103			case ')':
1104				buf.append('\\');
1105				buf.append(c);
1106				break;
1107			case '*':
1108				buf.append(".*");
1109				break;
1110			case '|':
1111				if(backslash)
1112					buf.append("\\|");
1113				else
1114					buf.append('|');
1115				break;
1116			case '{':
1117				buf.append('(');
1118				if(i + 1 != glob.length() && glob.charAt(i + 1) == '!')
1119				{
1120					buf.append('?');
1121					state.push(NEG);
1122				}
1123				else
1124					state.push(GROUP);
1125				break;
1126			case ',':
1127				if(!state.isEmpty() && state.peek() == GROUP)
1128					buf.append('|');
1129				else
1130					buf.append(',');
1131				break;
1132			case '}':
1133				if(!state.isEmpty())
1134				{
1135					buf.append(")");
1136					if(state.pop() == NEG)
1137						buf.append(".*");
1138				}
1139				else
1140					buf.append('}');
1141				break;
1142			default:
1143				buf.append(c);
1144			}
1145		}
1146
1147		return buf.toString();
1148	} //}}}
1149
1150	//{{{ escapesToChars() method
1151	/**
1152	 * Converts "\n" and "\t" escapes in the specified string to
1153	 * newlines and tabs.
1154	 * @param str The string
1155	 * @since jEdit 2.3pre1
1156	 */
1157	public static String escapesToChars(String str)
1158	{
1159		StringBuffer buf = new StringBuffer();
1160		for(int i = 0; i < str.length(); i++)
1161		{
1162			char c = str.charAt(i);
1163			switch(c)
1164			{
1165			case '\\':
1166				if(i == str.length() - 1)
1167				{
1168					buf.append('\\');
1169					break;
1170				}
1171				c = str.charAt(++i);
1172				switch(c)
1173				{
1174				case 'n':
1175					buf.append('\n');
1176					break;
1177				case 't':
1178					buf.append('\t');
1179					break;
1180				default:
1181					buf.append(c);
1182					break;
1183				}
1184				break;
1185			default:
1186				buf.append(c);
1187			}
1188		}
1189		return buf.toString();
1190	} //}}}
1191
1192	//{{{ charsToEscapes() method
1193	/**
1194	 * Escapes newlines, tabs, backslashes, and quotes in the specified
1195	 * string.
1196	 * @param str The string
1197	 * @since jEdit 2.3pre1
1198	 */
1199	public static String charsToEscapes(String str)
1200	{
1201		return charsToEscapes(str,"\n\t\\\"'");
1202	} //}}}
1203
1204	//{{{ charsToEscapes() method
1205	/**
1206	 * Escapes the specified characters in the specified string.
1207	 * @param str The string
1208	 * @param toEscape Any characters that require escaping
1209	 * @since jEdit 4.1pre3
1210	 */
1211	public static String charsToEscapes(String str, String toEscape)
1212	{
1213		StringBuffer buf = new StringBuffer();
1214		for(int i = 0; i < str.length(); i++)
1215		{
1216			char c = str.charAt(i);
1217			if(toEscape.indexOf(c) != -1)
1218			{
1219				if(c == '\n')
1220					buf.append("\\n");
1221				else if(c == '\t')
1222					buf.append("\\t");
1223				else
1224				{
1225					buf.append('\\');
1226					buf.append(c);
1227				}
1228			}
1229			else
1230				buf.append(c);
1231		}
1232		return buf.toString();
1233	} //}}}
1234
1235	//{{{ compareVersions() method
1236	/**
1237	 * @deprecated Call <code>compareStrings()</code> instead
1238	 */
1239	public static int compareVersions(String v1, String v2)
1240	{
1241		return compareStrings(v1,v2,false);
1242	} //}}}
1243
1244	//{{{ compareStrings() method
1245	/**
1246	 * Compares two strings.<p>
1247	 *
1248	 * Unlike <function>String.compareTo()</function>,
1249	 * this method correctly recognizes and handles embedded numbers.
1250	 * For example, it places "My file 2" before "My file 10".<p>
1251	 *
1252	 * @param str1 The first string
1253	 * @param str2 The second string
1254	 * @param ignoreCase If true, case will be ignored
1255	 * @return negative If str1 &lt; str2, 0 if both are the same,
1256	 * positive if str1 &gt; str2
1257	 * @since jEdit 4.0pre1
1258	 */
1259	public static int compareStrings(String str1, String str2, boolean ignoreCase)
1260	{
1261		char[] char1 = str1.toCharArray();
1262		char[] char2 = str2.toCharArray();
1263
1264		int len = Math.min(char1.length,char2.length);
1265
1266		for(int i = 0, j = 0; i < len && j < len; i++, j++)
1267		{
1268			char ch1 = char1[i];
1269			char ch2 = char2[j];
1270			if(Character.isDigit(ch1) && Character.isDigit(ch2)
1271				&& ch1 != '0' && ch2 != '0')
1272			{
1273				int _i = i + 1;
1274				int _j = j + 1;
1275
1276				for(; _i < char1.length; _i++)
1277				{
1278					if(!Character.isDigit(char1[_i]))
1279					{
1280						//_i--;
1281						break;
1282					}
1283				}
1284
1285				for(; _j < char2.length; _j++)
1286				{
1287					if(!Character.isDigit(char2[_j]))
1288					{
1289						//_j--;
1290						break;
1291					}
1292				}
1293
1294				int len1 = _i - i;
1295				int len2 = _j - j;
1296				if(len1 > len2)
1297					return 1;
1298				else if(len1 < len2)
1299					return -1;
1300				else
1301				{
1302					for(int k = 0; k < len1; k++)
1303					{
1304						ch1 = char1[i + k];
1305						ch2 = char2[j + k];
1306						if(ch1 != ch2)
1307							return ch1 - ch2;
1308					}
1309				}
1310
1311				i = _i - 1;
1312				j = _j - 1;
1313			}
1314			else
1315			{
1316				if(ignoreCase)
1317				{
1318					ch1 = Character.toLowerCase(ch1);
1319					ch2 = Character.toLowerCase(ch2);
1320				}
1321
1322				if(ch1 != ch2)
1323					return ch1 - ch2;
1324			}
1325		}
1326
1327		return char1.length - char2.length;
1328	} //}}}
1329
1330	//{{{ stringsEqual() method
1331	/**
1332	 * @deprecated Call <code>objectsEqual()</code> instead.
1333	 */
1334	public static boolean stringsEqual(String s1, String s2)
1335	{
1336		return objectsEqual(s1,s2);
1337	} //}}}
1338
1339	//{{{ objectsEqual() method
1340	/**
1341	 * Returns if two strings are equal. This correctly handles null pointers,
1342	 * as opposed to calling <code>o1.equals(o2)</code>.
1343	 * @since jEdit 4.2pre1
1344	 */
1345	public static boolean objectsEqual(Object o1, Object o2)
1346	{
1347		if(o1 == null)
1348		{
1349			if(o2 == null)
1350				return true;
1351			else
1352				return false;
1353		}
1354		else if(o2 == null)
1355			return false;
1356		else
1357			return o1.equals(o2);
1358	} //}}}
1359
1360	//{{{ charsToEntities() method
1361	/**
1362	 * Converts &lt;, &gt;, &amp; in the string to their HTML entity
1363	 * equivalents.
1364	 * @param str The string
1365	 * @since jEdit 4.2pre1
1366	 */
1367	public static String charsToEntities(String str)
1368	{
1369		StringBuffer buf = new StringBuffer(str.length());
1370		for(int i = 0; i < str.length(); i++)
1371		{
1372			char ch = str.charAt(i);
1373			switch(ch)
1374			{
1375			case '<':
1376				buf.append("&lt;");
1377				break;
1378			case '>':
1379				buf.append("&gt;");
1380				break;
1381			case '&':
1382				buf.append("&amp;");
1383				break;
1384			default:
1385				buf.append(ch);
1386				break;
1387			}
1388		}
1389		return buf.toString();
1390	} //}}}
1391
1392	//{{{ formatFileSize() method
1393	public static final DecimalFormat KB_FORMAT = new DecimalFormat("#.# KB");
1394	public static final DecimalFormat MB_FORMAT = new DecimalFormat("#.# MB");
1395
1396	/**
1397	 * Formats the given file size into a nice string (123 bytes, 10.6 KB,
1398	 * 1.2 MB).
1399	 * @param length The size
1400	 * @since jEdit 4.2pre1
1401	 */
1402	public static String formatFileSize(long length)
1403	{
1404		if(length < 1024)
1405			return length + " bytes";
1406		else if(length < 1024*1024)
1407			return KB_FORMAT.format((double)length / 1024);
1408		else
1409			return MB_FORMAT.format((double)length / 1024 / 1024);
1410	} //}}}
1411
1412	//{{{ getLongestPrefix() method
1413	/**
1414	 * Returns the longest common prefix in the given set of strings.
1415	 * @param str The strings
1416	 * @param ignoreCase If true, case insensitive
1417	 * @since jEdit 4.2pre2
1418	 */
1419	public static String getLongestPrefix(List str, boolean ignoreCase)
1420	{
1421		if(str.size() == 0)
1422			return "";
1423
1424		int prefixLength = 0;
1425
1426loop:		for(;;)
1427		{
1428			String s = str.get(0).toString();
1429			if(prefixLength >= s.length())
1430				break loop;
1431			char ch = s.charAt(prefixLength);
1432			for(int i = 1; i < str.size(); i++)
1433			{
1434				s = str.get(i).toString();
1435				if(prefixLength >= s.length())
1436					break loop;
1437				if(!compareChars(s.charAt(prefixLength),ch,ignoreCase))
1438					break loop;
1439			}
1440			prefixLength++;
1441		}
1442
1443		return str.get(0).toString().substring(0,prefixLength);
1444	} //}}}
1445
1446	//{{{ getLongestPrefix() method
1447	/**
1448	 * Returns the longest common prefix in the given set of strings.
1449	 * @param str The strings
1450	 * @param ignoreCase If true, case insensitive
1451	 * @since jEdit 4.2pre2
1452	 */
1453	public static String getLongestPrefix(String[] str, boolean ignoreCase)
1454	{
1455		return getLongestPrefix((Object[])str,ignoreCase);
1456	} //}}}
1457
1458	//{{{ getLongestPrefix() method
1459	/**
1460	 * Returns the longest common prefix in the given set of strings.
1461	 * @param str The strings (calls <code>toString()</code> on each object)
1462	 * @param ignoreCase If true, case insensitive
1463	 * @since jEdit 4.2pre6
1464	 */
1465	public static String getLongestPrefix(Object[] str, boolean ignoreCase)
1466	{
1467		if(str.length == 0)
1468			return "";
1469
1470		int prefixLength = 0;
1471
1472		String first = str[0].toString();
1473
1474loop:		for(;;)
1475		{
1476			if(prefixLength >= first.length())
1477				break loop;
1478			char ch = first.charAt(prefixLength);
1479			for(int i = 1; i < str.length; i++)
1480			{
1481				String s = str[i].toString();
1482				if(prefixLength >= s.length())
1483					break loop;
1484				if(!compareChars(s.charAt(prefixLength),ch,ignoreCase))
1485					break loop;
1486			}
1487			prefixLength++;
1488		}
1489
1490		return first.substring(0,prefixLength);
1491	} //}}}
1492
1493	//}}}
1494
1495	//{{{ Sorting methods
1496
1497	//{{{ quicksort() method
1498	/**
1499	 * Sorts the specified array. Equivalent to calling
1500	 * <code>Arrays.sort()</code>.
1501	 * @param obj The array
1502	 * @param compare Compares the objects
1503	 * @since jEdit 4.0pre4
1504	 * @deprecated use <code>Arrays.sort()</code>
1505	 */
1506	public static void quicksort(Object[] obj, Comparator compare)
1507	{
1508		Arrays.sort(obj,compare);
1509	} //}}}
1510
1511	//{{{ quicksort() method
1512	/**
1513	 * Sorts the specified vector.
1514	 * @param vector The vector
1515	 * @param compare Compares the objects
1516	 * @since jEdit 4.0pre4
1517	 * @deprecated <code>Collections.sort()</code>
1518	 */
1519	public static void quicksort(Vector vector, Comparator compare)
1520	{
1521		Collections.sort(vector,compare);
1522	} //}}}
1523
1524	//{{{ quicksort() method
1525	/**
1526	 * Sorts the specified list.
1527	 * @param list The list
1528	 * @param compare Compares the objects
1529	 * @since jEdit 4.0pre4
1530	 * @deprecated <code>Collections.sort()</code>
1531	 */
1532	public static void quicksort(List list, Comparator compare)
1533	{
1534		Collections.sort(list,compare);
1535	} //}}}
1536
1537	//{{{ quicksort() method
1538	/**
1539	 * Sorts the specified array. Equivalent to calling
1540	 * <code>Arrays.sort()</code>.
1541	 * @param obj The array
1542	 * @param compare Compares the objects
1543	 * @deprecated use <code>Arrays.sort()</code>
1544	 */
1545	public static void quicksort(Object[] obj, Compare compare)
1546	{
1547		Arrays.sort(obj,compare);
1548	} //}}}
1549
1550	//{{{ quicksort() method
1551	/**
1552	 * Sorts the specified vector.
1553	 * @param vector The vector
1554	 * @param compare Compares the objects
1555	 * @deprecated <code>Collections.sort()</code>
1556	 */
1557	public static void quicksort(Vector vector, Compare compare)
1558	{
1559		Collections.sort(vector,compare);
1560	} //}}}
1561
1562	//{{{ Compare interface
1563	/**
1564	 * An interface for comparing objects. This is a hold-over from
1565	 * they days when jEdit had its own sorting API due to JDK 1.1
1566	 * compatibility requirements. Use <code>java.util.Comparable</code>
1567	 * instead.
1568	 * @deprecated
1569	 */
1570	public interface Compare extends Comparator
1571	{
1572		int compare(Object obj1, Object obj2);
1573	} //}}}
1574
1575	//{{{ StringCompare class
1576	/**
1577	 * Compares strings.
1578	 */
1579	public static class StringCompare implements Compare
1580	{
1581		public int compare(Object obj1, Object obj2)
1582		{
1583			return compareStrings(obj1.toString(),
1584				obj2.toString(),false);
1585		}
1586	} //}}}
1587
1588	//{{{ StringICaseCompare class
1589	/**
1590	 * Compares strings ignoring case.
1591	 */
1592	public static class StringICaseCompare implements Compare
1593	{
1594		public int compare(Object obj1, Object obj2)
1595		{
1596			return compareStrings(obj1.toString(),
1597				obj2.toString(),true);
1598		}
1599	} //}}}
1600
1601	//{{{ MenuItemCompare class
1602	/**
1603	 * Compares menu item labels.
1604	 */
1605	public static class MenuItemCompare implements Compare
1606	{
1607		public int compare(Object obj1, Object obj2)
1608		{
1609			boolean obj1E, obj2E;
1610			obj1E = obj1 instanceof EnhancedMenuItem;
1611			obj2E = obj2 instanceof EnhancedMenuItem;
1612            if(obj1E && !obj2E)
1613                  return 1;
1614            else if(obj2E && !obj1E)
1615                  return -1;
1616			else
1617				return compareStrings(((JMenuItem)obj1).getText(),
1618									  ((JMenuItem)obj2).getText(),true);
1619		}
1620	} //}}}
1621
1622	//}}}
1623
1624	//{{{ buildToVersion() method
1625	/**
1626	 * Converts an internal version number (build) into a
1627	 * `human-readable' form.
1628	 * @param build The build
1629	 */
1630	public static String buildToVersion(String build)
1631	{
1632		if(build.length() != 11)
1633			return "<unknown version: " + build + ">";
1634		// First 2 chars are the major version number
1635		int major = Integer.parseInt(build.substring(0,2));
1636		// Second 2 are the minor number
1637		int minor = Integer.parseInt(build.substring(3,5));
1638		// Then the pre-release status
1639		int beta = Integer.parseInt(build.substring(6,8));
1640		// Finally the bug fix release
1641		int bugfix = Integer.parseInt(build.substring(9,11));
1642
1643		return major + "." + minor
1644			+ (beta != 99 ? "pre" + beta :
1645			(bugfix != 0 ? "." + bugfix : "final"));
1646	} //}}}
1647
1648	//{{{ isToolsJarAvailable() method
1649	/**
1650	 * If on JDK 1.2 or higher, make sure that tools.jar is available.
1651	 * This method should be called by plugins requiring the classes
1652	 * in this library.
1653	 * <p>
1654	 * tools.jar is searched for in the following places:
1655	 * <ol>
1656	 *   <li>the classpath that was used when jEdit was started,
1657	 *   <li>jEdit's jars folder in the user's home,
1658	 *   <li>jEdit's system jars folder,
1659	 *   <li><i>java.home</i>/lib/. In this case, tools.jar is added to
1660	 *       jEdit's list of known jars using jEdit.addPluginJAR(),
1661	 *       so that it gets loaded through JARClassLoader.
1662	 * </ol><p>
1663	 *
1664	 * On older JDK's this method does not perform any checks, and returns
1665	 * <code>true</code> (even though there is no tools.jar).
1666	 *
1667	 * @return <code>false</code> if and only if on JDK 1.2 and tools.jar
1668	 *    could not be found. In this case it prints some warnings on Log,
1669	 *    too, about the places where it was searched for.
1670	 * @since jEdit 3.2.2
1671	 */
1672	public static boolean isToolsJarAvailable()
1673	{
1674		Log.log(Log.DEBUG, MiscUtilities.class,"Searching for tools.jar...");
1675
1676		Vector paths = new Vector();
1677
1678		//{{{ 1. Check whether tools.jar is in the system classpath:
1679		paths.addElement("System classpath: "
1680			+ System.getProperty("java.class.path"));
1681
1682		try
1683		{
1684			// Either class sun.tools.javac.Main or
1685			// com.sun.tools.javac.Main must be there:
1686			try
1687			{
1688				Class.forName("sun.tools.javac.Main");
1689			}
1690			catch(ClassNotFoundException e1)
1691			{
1692				Class.forName("com.sun.tools.javac.Main");
1693			}
1694			Log.log(Log.DEBUG, MiscUtilities.class,
1695				"- is in classpath. Fine.");
1696			return true;
1697		}
1698		catch(ClassNotFoundException e)
1699		{
1700			//Log.log(Log.DEBUG, MiscUtilities.class,
1701			//	"- is not in system classpath.");
1702		} //}}}
1703
1704		//{{{ 2. Check whether it is in the jEdit user settings jars folder:
1705		String settingsDir = jEdit.getSettingsDirectory();
1706		if(settingsDir != null)
1707		{
1708			String toolsPath = constructPath(settingsDir, "jars",
1709				"tools.jar");
1710			paths.addElement(toolsPath);
1711			if(new File(toolsPath).exists())
1712			{
1713				Log.log(Log.DEBUG, MiscUtilities.class,
1714					"- is in the user's jars folder. Fine.");
1715				// jEdit will load it automatically
1716				return true;
1717			}
1718		} //}}}
1719
1720		//{{{ 3. Check whether it is in jEdit's system jars folder:
1721		String jEditDir = jEdit.getJEditHome();
1722		if(jEditDir != null)
1723		{
1724			String toolsPath = constructPath(jEditDir, "jars", "tools.jar");
1725			paths.addElement(toolsPath);
1726			if(new File(toolsPath).exists())
1727			{
1728				Log.log(Log.DEBUG, MiscUtilities.class,
1729					"- is in jEdit's system jars folder. Fine.");
1730				// jEdit will load it automatically
1731				return true;
1732			}
1733		} //}}}
1734
1735		//{{{ 4. Check whether it is in <java.home>/lib:
1736		String toolsPath = System.getProperty("java.home");
1737		if(toolsPath.toLowerCase().endsWith(File.separator + "jre"))
1738			toolsPath = toolsPath.substring(0, toolsPath.length() - 4);
1739		toolsPath = constructPath(toolsPath, "lib", "tools.jar");
1740		paths.addElement(toolsPath);
1741
1742		if(!(new File(toolsPath).exists()))
1743		{
1744			Log.log(Log.WARNING, MiscUtilities.class,
1745				"Could not find tools.jar.\n"
1746				+ "I checked the following locations:\n"
1747				+ paths.toString());
1748			return false;
1749		} //}}}
1750
1751		//{{{ Load it, if not yet done:
1752		PluginJAR jar = jEdit.getPluginJAR(toolsPath);
1753		if(jar == null)
1754		{
1755			Log.log(Log.DEBUG, MiscUtilities.class,
1756				"- adding " + toolsPath + " to jEdit plugins.");
1757			jEdit.addPluginJAR(toolsPath);
1758		}
1759		else
1760			Log.log(Log.DEBUG, MiscUtilities.class,
1761				"- has been loaded before.");
1762		//}}}
1763
1764		return true;
1765	} //}}}
1766
1767	//{{{ parsePermissions() method
1768	/**
1769	 * Parse a Unix-style permission string (rwxrwxrwx).
1770	 * @param s The string (must be 9 characters long).
1771	 * @since jEdit 4.1pre8
1772	 */
1773	public static int parsePermissions(String s)
1774	{
1775		int permissions = 0;
1776
1777		if(s.length() == 9)
1778		{
1779			if(s.charAt(0) == 'r')
1780				permissions += 0400;
1781			if(s.charAt(1) == 'w')
1782				permissions += 0200;
1783			if(s.charAt(2) == 'x')
1784				permissions += 0100;
1785			else if(s.charAt(2) == 's')
1786				permissions += 04100;
1787			else if(s.charAt(2) == 'S')
1788				permissions += 04000;
1789			if(s.charAt(3) == 'r')
1790				permissions += 040;
1791			if(s.charAt(4) == 'w')
1792				permissions += 020;
1793			if(s.charAt(5) == 'x')
1794				permissions += 010;
1795			else if(s.charAt(5) == 's')
1796				permissions += 02010;
1797			else if(s.charAt(5) == 'S')
1798				permissions += 02000;
1799			if(s.charAt(6) == 'r')
1800				permissions += 04;
1801			if(s.charAt(7) == 'w')
1802				permissions += 02;
1803			if(s.charAt(8) == 'x')
1804				permissions += 01;
1805			else if(s.charAt(8) == 't')
1806				permissions += 01001;
1807			else if(s.charAt(8) == 'T')
1808				permissions += 01000;
1809		}
1810
1811		return permissions;
1812	} //}}}
1813
1814	//{{{ getEncodings() method
1815	/**
1816	 * Returns a list of supported character encodings.
1817	 * @since jEdit 4.2pre5
1818	 */
1819	public static String[] getEncodings()
1820	{
1821		List returnValue = new ArrayList();
1822
1823		Map map = Charset.availableCharsets();
1824		Iterator iter = map.keySet().iterator();
1825
1826		returnValue.add(UTF_8_Y);
1827
1828		while(iter.hasNext())
1829			returnValue.add(iter.next());
1830
1831		return (String[])returnValue.toArray(
1832			new String[returnValue.size()]);
1833	} //}}}
1834
1835	//{{{ throwableToString() method
1836	/**
1837	 * Returns a string containing the stack trace of the given throwable.
1838	 * @since jEdit 4.2pre6
1839	 */
1840	public static String throwableToString(Throwable t)
1841	{
1842		StringWriter s = new StringWriter();
1843		t.printStackTrace(new PrintWriter(s));
1844		return s.toString();
1845	} //}}}
1846
1847	//{{{ parseXML() method
1848	/**
1849	 * Convenience method for parsing an XML file. This method will
1850	 * wrap the resource in an InputSource and set the source's
1851	 * systemId to "jedit.jar" (so the source should be able to
1852	 * handle any external entities by itself).
1853	 *
1854	 * <p>SAX Errors are caught and are not propagated to the caller;
1855	 * instead, an error message is printed to jEdit's activity
1856	 * log. So, if you need custom error handling, <b>do not use
1857	 * this method</b>.
1858	 *
1859	 * <p>The given stream is closed before the method returns,
1860	 * regardless whether there were errors or not.</p>
1861	 *
1862	 * @return Whether any error occured during parsing.
1863	 * @since jEdit 4.3pre5
1864	 */
1865	public static boolean parseXML(InputStream in, DefaultHandler handler)
1866		throws IOException
1867	{
1868		Reader r = null;
1869		try
1870		{
1871			XMLReader parser = XMLReaderFactory.createXMLReader();
1872			r = new BufferedReader(new InputStreamReader(in));
1873			InputSource isrc = new InputSource(r);
1874			isrc.setSystemId("jedit.jar");
1875			parser.setContentHandler(handler);
1876			parser.setDTDHandler(handler);
1877			parser.setEntityResolver(handler);
1878			parser.setErrorHandler(handler);
1879			parser.parse(isrc);
1880		}
1881		catch(SAXParseException se)
1882		{
1883			int line = se.getLineNumber();
1884			String message = se.getMessage();
1885			Log.log(Log.ERROR,MiscUtilities.class,
1886				"while parsing from " + in + ": SAXParseException: line " + line + ": " , se);
1887			return true;
1888		}
1889		catch(SAXException e)
1890		{
1891			Log.log(Log.ERROR,Registers.class,e);
1892			return true;
1893		}
1894		finally
1895		{
1896			try
1897			{
1898				if(in != null)
1899					in.close();
1900			}
1901			catch(IOException io)
1902			{
1903				Log.log(Log.ERROR,MiscUtilities.class,io);
1904			}
1905		}
1906		return false;
1907	} //}}}
1908
1909	//{{{ resolveEnt

Large files files are truncated, but you can click here to view the full file