PageRenderTime 67ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/jEdit/tags/jedit_43_with_gnuregexp_microstarxml/org/gjt/sp/jedit/MiscUtilities.java

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