PageRenderTime 57ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/MiscUtilities.java

#
Java | 1289 lines | 804 code | 103 blank | 382 comment | 281 complexity | 2a1a20ffb2f0a396fc0d170bb75c30bd 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 java.io.*;
  27. import java.net.MalformedURLException;
  28. import java.net.URL;
  29. import java.nio.charset.MalformedInputException;
  30. import java.util.*;
  31. import java.util.regex.Matcher;
  32. import java.util.regex.Pattern;
  33. import org.gjt.sp.jedit.io.*;
  34. import org.gjt.sp.util.Log;
  35. import org.gjt.sp.util.IOUtilities;
  36. import org.gjt.sp.jedit.buffer.JEditBuffer;
  37. //}}}
  38. /**
  39. * Path name manipulation, string manipulation, and more.<p>
  40. *
  41. * The most frequently used members of this class are:<p>
  42. *
  43. * <b>Some path name methods:</b><p>
  44. * <ul>
  45. * <li>{@link #getFileName(String)}</li>
  46. * <li>{@link #getParentOfPath(String)}</li>
  47. * <li>{@link #constructPath(String,String)}</li>
  48. * </ul>
  49. *
  50. * @author Slava Pestov
  51. * @author John Gellene (API documentation)
  52. * @version $Id: MiscUtilities.java 19871 2011-08-29 21:31:58Z ezust $
  53. */
  54. public class MiscUtilities
  55. {
  56. //{{{ Path name methods
  57. //{{{ canonPath() method
  58. /**
  59. * @return the canonical form of the specified path name. Currently
  60. * only expands a leading <code>~</code>. <b>For local path names
  61. * only.</b>
  62. * @param path The path name
  63. * @since jEdit 4.0pre2
  64. */
  65. public static String canonPath(String path)
  66. {
  67. if(path.length() == 0)
  68. return path;
  69. if(path.startsWith("file://"))
  70. path = path.substring("file://".length());
  71. else if(path.startsWith("file:"))
  72. path = path.substring("file:".length());
  73. else if(isURL(path))
  74. return path;
  75. if(File.separatorChar == '\\')
  76. {
  77. // get rid of mixed paths on Windows
  78. path = path.replace('/','\\');
  79. // also get rid of trailing spaces on Windows
  80. int trim = path.length();
  81. while(path.charAt(trim - 1) == ' ')
  82. trim--;
  83. if (path.charAt(trim - 1) == '\\')
  84. while (trim > 1 && path.charAt(trim - 2) == '\\')
  85. {
  86. trim--;
  87. }
  88. path = path.substring(0,trim);
  89. }
  90. if(path.startsWith('~' + File.separator))
  91. {
  92. path = path.substring(2);
  93. String home = System.getProperty("user.home");
  94. if(home.endsWith(File.separator))
  95. return home + path;
  96. else
  97. return home + File.separator + path;
  98. }
  99. else if("~".equals(path))
  100. return System.getProperty("user.home");
  101. else if ("-".equals(path))
  102. return getParentOfPath(jEdit.getActiveView().getBuffer().getPath());
  103. else
  104. return path;
  105. } //}}}
  106. //{{{ expandVariables() method
  107. static final String varPatternString = "(\\$([a-zA-Z0-9_]+))";
  108. static final String varPatternString2 = "(\\$\\{([^}]+)\\})";
  109. static final String winPatternString = "(%([^%]+)%)";
  110. static final Pattern varPattern = Pattern.compile(varPatternString);
  111. static final Pattern varPattern2 = Pattern.compile(varPatternString2);
  112. static final Pattern winPattern = Pattern.compile(winPatternString);
  113. /** Accepts a string from the user which may contain variables of various syntaxes.
  114. * The function supports the following expansion syntaxes:
  115. * $varname
  116. * ${varname} (on non-windows)
  117. * %varname% (on Windows)
  118. * And expand each of these by looking at the system environment variables for possible
  119. * expansions.
  120. * @return a string which is either the unchanged input string, or one with expanded variables.
  121. * @since 4.3pre7
  122. * @see #abbreviate
  123. * @author ezust
  124. */
  125. public static String expandVariables(String arg)
  126. {
  127. Pattern p = varPattern;
  128. Matcher m = p.matcher(arg);
  129. if (!m.find())
  130. {
  131. if (OperatingSystem.isWindows())
  132. p = winPattern;
  133. else p = varPattern2;
  134. m = p.matcher(arg);
  135. if (!m.find()) // no variables to substitute
  136. return arg;
  137. }
  138. String varName = m.group(2);
  139. String expansion = System.getenv(varName);
  140. if (expansion == null)
  141. { // try everything uppercase?
  142. varName = varName.toUpperCase();
  143. String uparg = arg.toUpperCase();
  144. m = p.matcher(uparg);
  145. expansion = System.getenv(varName);
  146. }
  147. if (expansion != null)
  148. {
  149. expansion = expansion.replace("\\", "\\\\");
  150. return m.replaceFirst(expansion);
  151. }
  152. return arg;
  153. } //}}}
  154. //{{{ abbreviate() method
  155. /** @return an abbreviated path, replacing values with variables, if a prefix exists.
  156. * @see #expandVariables
  157. * @since jEdit 4.3pre16
  158. */
  159. public static String abbreviate(String path)
  160. {
  161. if (svc == null)
  162. svc = new VarCompressor();
  163. return svc.compress(path);
  164. } //}}}
  165. //{{{ resolveSymlinks() method
  166. /**
  167. * Resolves any symbolic links in the path name specified
  168. * using <code>File.getCanonicalPath()</code>. <b>For local path
  169. * names only.</b>
  170. * @since jEdit 4.2pre1
  171. */
  172. public static String resolveSymlinks(String path)
  173. {
  174. if(isURL(path))
  175. return path;
  176. // 2 aug 2003: OS/2 Java has a broken getCanonicalPath()
  177. if(OperatingSystem.isOS2())
  178. return path;
  179. // 18 nov 2003: calling this on a drive letter on Windows causes
  180. // drive access
  181. if(OperatingSystem.isDOSDerived())
  182. {
  183. if(path.length() == 2 || path.length() == 3)
  184. {
  185. if(path.charAt(1) == ':')
  186. return path;
  187. }
  188. }
  189. try
  190. {
  191. return new File(path).getCanonicalPath();
  192. }
  193. catch(IOException io)
  194. {
  195. return path;
  196. }
  197. } //}}}
  198. //{{{ isAbsolutePath() method
  199. /**
  200. * Returns if the specified path name is an absolute path or URL.
  201. * @since jEdit 4.1pre11
  202. */
  203. public static boolean isAbsolutePath(String path)
  204. {
  205. if(isURL(path))
  206. return true;
  207. else if(path.startsWith("~/") || path.startsWith('~' + File.separator) || "~".equals(path))
  208. return true;
  209. else if ("-".equals(path))
  210. return true;
  211. else if(OperatingSystem.isDOSDerived())
  212. {
  213. if(path.length() == 2 && path.charAt(1) == ':')
  214. return true;
  215. if(path.length() > 2 && path.charAt(1) == ':'
  216. && (path.charAt(2) == '\\'
  217. || path.charAt(2) == '/'))
  218. return true;
  219. if(path.startsWith("\\\\")
  220. || path.startsWith("//"))
  221. return true;
  222. }
  223. // not sure if this is correct for OpenVMS.
  224. else if(OperatingSystem.isUnix()
  225. || OperatingSystem.isVMS())
  226. {
  227. // nice and simple
  228. if(path.length() > 0 && path.charAt(0) == '/')
  229. return true;
  230. }
  231. return false;
  232. } //}}}
  233. //{{{ constructPath() methods
  234. /**
  235. * Constructs an absolute path name from a directory and another
  236. * path name. This method is VFS-aware.
  237. * @param parent The directory
  238. * @param path The path name
  239. */
  240. public static String constructPath(String parent, String path)
  241. {
  242. if(isAbsolutePath(path))
  243. return canonPath(path);
  244. if (parent == null)
  245. parent = System.getProperty("user.dir");
  246. if (path == null || path.length() == 0)
  247. return parent;
  248. // have to handle this case specially on windows.
  249. // insert \ between, eg A: and myfile.txt.
  250. if(OperatingSystem.isDOSDerived())
  251. {
  252. if(path.length() == 2 && path.charAt(1) == ':')
  253. return path;
  254. else if(path.length() > 2 && path.charAt(1) == ':'
  255. && path.charAt(2) != '\\')
  256. {
  257. path = path.substring(0,2) + '\\'
  258. + path.substring(2);
  259. return canonPath(path);
  260. }
  261. }
  262. String dd = ".." + File.separator;
  263. String d = '.' + File.separator;
  264. for(;;)
  265. {
  266. if(".".equals(path))
  267. return parent;
  268. else if("..".equals(path))
  269. return getParentOfPath(parent);
  270. else if(path.startsWith(dd) || path.startsWith("../"))
  271. {
  272. parent = getParentOfPath(parent);
  273. path = path.substring(3);
  274. }
  275. else if(path.startsWith(d) || path.startsWith("./"))
  276. path = path.substring(2);
  277. else
  278. break;
  279. }
  280. if(path.length() == 0)
  281. return parent;
  282. if(OperatingSystem.isDOSDerived()
  283. && !isURL(parent)
  284. && path.charAt(0) == '\\')
  285. parent = parent.substring(0,2);
  286. VFS vfs = VFSManager.getVFSForPath(parent);
  287. return canonPath(vfs.constructPath(parent,path));
  288. }
  289. /**
  290. * Constructs an absolute path name from three path components.
  291. * This method is VFS-aware.
  292. * @param parent The parent directory
  293. * @param path1 The first path
  294. * @param path2 The second path
  295. */
  296. public static String constructPath(String parent,
  297. String path1, String path2)
  298. {
  299. return constructPath(constructPath(parent,path1),path2);
  300. } //}}}
  301. //{{{ concatPath() method
  302. /**
  303. * Like {@link #constructPath}, except <code>path</code> will be
  304. * appended to <code>parent</code> even if it is absolute.
  305. * <b>For local path names only.</b>.
  306. *
  307. * @param parent the parent path
  308. * @param path the path to append to the parent
  309. */
  310. public static String concatPath(String parent, String path)
  311. {
  312. parent = canonPath(parent);
  313. path = canonPath(path);
  314. // Make all child paths relative.
  315. if (path.startsWith(File.separator))
  316. path = path.substring(1);
  317. else if (path.length() >= 3 && path.charAt(1) == ':')
  318. path = path.replace(':', File.separatorChar);
  319. if (parent == null)
  320. parent = System.getProperty("user.dir");
  321. if (parent.endsWith(File.separator))
  322. return parent + path;
  323. else
  324. return parent + File.separator + path;
  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. //{{{ getLastSeparatorIndex() method
  342. /**
  343. * Return the last index of either / or the OS-specific file
  344. * separator.
  345. * @param path The path
  346. * @since jEdit 4.3pre3
  347. */
  348. public static int getLastSeparatorIndex(String path)
  349. {
  350. int start = getPathStart(path);
  351. if(start != 0)
  352. path = path.substring(start);
  353. int index = Math.max(
  354. path.lastIndexOf('/'), path.lastIndexOf(File.separatorChar));
  355. if(index == -1)
  356. return index;
  357. else
  358. return index + start;
  359. } //}}}
  360. //{{{ getFileExtension() method
  361. /**
  362. * Returns the extension of the specified filename, or an empty
  363. * string if there is none.
  364. * @param path The path
  365. */
  366. public static String getFileExtension(String path)
  367. {
  368. int fsIndex = getLastSeparatorIndex(path);
  369. int index = path.lastIndexOf('.');
  370. // there could be a dot in the path and no file extension
  371. if(index == -1 || index < fsIndex )
  372. return "";
  373. else
  374. return path.substring(index);
  375. } //}}}
  376. //{{{ getFileName() method
  377. /**
  378. * Returns the last component of the specified path.
  379. * This method is VFS-aware.
  380. * @param path The path name
  381. */
  382. public static String getFileName(String path)
  383. {
  384. return VFSManager.getVFSForPath(path).getFileName(path);
  385. } //}}}
  386. //{{{ getFileNameNoExtension() method
  387. /**
  388. * Returns the last component of the specified path name without the
  389. * trailing extension (if there is one).
  390. * @param path The path name
  391. * @since jEdit 4.0pre8
  392. */
  393. public static String getFileNameNoExtension(String path)
  394. {
  395. String name = getFileName(path);
  396. int index = name.indexOf('.');
  397. if(index == -1)
  398. return name;
  399. else
  400. return name.substring(0,index);
  401. } //}}}
  402. //{{{ getParentOfPath() method
  403. /**
  404. * Returns the parent of the specified path. This method is VFS-aware.
  405. * @param path The path name
  406. * @since jEdit 2.6pre5
  407. */
  408. public static String getParentOfPath(String path)
  409. {
  410. return VFSManager.getVFSForPath(path).getParentOfPath(path);
  411. } //}}}
  412. //{{{ getProtocolOfURL() method
  413. /**
  414. * Returns the protocol specified by a URL.
  415. * @param url The URL
  416. * @since jEdit 2.6pre5
  417. */
  418. public static String getProtocolOfURL(String url)
  419. {
  420. return url.substring(0,url.indexOf(':'));
  421. } //}}}
  422. //{{{ isURL() method
  423. /**
  424. * Checks if the specified string is a URL.
  425. * @param str The string to check
  426. * @return True if the string is a URL, false otherwise
  427. */
  428. public static boolean isURL(String str)
  429. {
  430. int fsIndex = getLastSeparatorIndex(str);
  431. if(fsIndex == 0) // /etc/passwd
  432. return false;
  433. else if(fsIndex == 2) // C:\AUTOEXEC.BAT
  434. return false;
  435. int cIndex = str.indexOf(':');
  436. if(cIndex <= 1) // D:\WINDOWS, or doesn't contain : at all
  437. return false;
  438. String protocol = str.substring(0,cIndex);
  439. VFS vfs = VFSManager.getVFSForProtocol(protocol);
  440. if(vfs != null && !(vfs instanceof UrlVFS))
  441. return true;
  442. try
  443. {
  444. new URL(str);
  445. return true;
  446. }
  447. catch(MalformedURLException mf)
  448. {
  449. return false;
  450. }
  451. } //}}}
  452. //{{{ saveBackup() methods
  453. /**
  454. * Saves a backup (optionally numbered) of a file.
  455. * @param file A local file
  456. * @param backups The number of backups. Must be >= 1. If > 1, backup
  457. * files will be numbered.
  458. * @param backupPrefix The backup file name prefix
  459. * @param backupSuffix The backup file name suffix
  460. * @param backupDirectory The directory where to save backups; if null,
  461. * they will be saved in the same directory as the file itself.
  462. * @since jEdit 4.0pre1
  463. */
  464. public static void saveBackup(File file, int backups,
  465. String backupPrefix, String backupSuffix,
  466. String backupDirectory)
  467. {
  468. saveBackup(file,backups,backupPrefix,backupSuffix,backupDirectory,0);
  469. }
  470. /**
  471. * Saves a backup (optionally numbered) of a file.
  472. * @param file A local file
  473. * @param backups The number of backups. Must be >= 1. If > 1, backup
  474. * files will be numbered.
  475. * @param backupPrefix The backup file name prefix
  476. * @param backupSuffix The backup file name suffix
  477. * @param backupDirectory The directory where to save backups; if null,
  478. * they will be saved in the same directory as the file itself.
  479. * @param backupTimeDistance The minimum time in minutes when a backup
  480. * version 1 shall be moved into version 2; if 0, backups are always
  481. * moved.
  482. * @since jEdit 4.2pre5
  483. */
  484. public static void saveBackup(File file, int backups,
  485. String backupPrefix, String backupSuffix,
  486. String backupDirectory, int backupTimeDistance)
  487. {
  488. if(backupPrefix == null)
  489. backupPrefix = "";
  490. if(backupSuffix == null)
  491. backupSuffix = "";
  492. String name = file.getName();
  493. // If backups is 1, create ~ file
  494. if(backups == 1)
  495. {
  496. File backupFile = new File(backupDirectory,
  497. backupPrefix + name + backupSuffix);
  498. long modTime = backupFile.lastModified();
  499. /* if backup file was created less than
  500. * 'backupTimeDistance' ago, we do not
  501. * create the backup */
  502. if(System.currentTimeMillis() - modTime
  503. >= backupTimeDistance)
  504. {
  505. Log.log(Log.DEBUG,MiscUtilities.class,
  506. "Saving backup of file \"" +
  507. file.getAbsolutePath() + "\" to \"" +
  508. backupFile.getAbsolutePath() + '"');
  509. backupFile.delete();
  510. if (!file.renameTo(backupFile))
  511. IOUtilities.moveFile(file, backupFile);
  512. }
  513. }
  514. // If backups > 1, move old ~n~ files, create ~1~ file
  515. else
  516. {
  517. /* delete a backup created using above method */
  518. new File(backupDirectory,
  519. backupPrefix + name + backupSuffix
  520. + backups + backupSuffix).delete();
  521. File firstBackup = new File(backupDirectory,
  522. backupPrefix + name + backupSuffix
  523. + '1' + backupSuffix);
  524. long modTime = firstBackup.lastModified();
  525. /* if backup file was created less than
  526. * 'backupTimeDistance' ago, we do not
  527. * create the backup */
  528. if(System.currentTimeMillis() - modTime
  529. >= backupTimeDistance)
  530. {
  531. for(int i = backups - 1; i > 0; i--)
  532. {
  533. File backup = new File(backupDirectory,
  534. backupPrefix + name
  535. + backupSuffix + i
  536. + backupSuffix);
  537. backup.renameTo(new File(backupDirectory,
  538. backupPrefix + name
  539. + backupSuffix + (i + 1)
  540. + backupSuffix));
  541. }
  542. File backupFile = new File(backupDirectory,
  543. backupPrefix + name + backupSuffix
  544. + '1' + backupSuffix);
  545. Log.log(Log.DEBUG,MiscUtilities.class,
  546. "Saving backup of file \"" +
  547. file.getAbsolutePath() + "\" to \"" +
  548. backupFile.getAbsolutePath() + '"');
  549. if (!file.renameTo(backupFile))
  550. IOUtilities.moveFile(file, backupFile);
  551. }
  552. }
  553. } //}}}
  554. //{{{ isBinary() methods
  555. /**
  556. * Check if an InputStream is binary.
  557. * First this tries encoding auto detection. If an encoding is
  558. * detected, the stream should be a text stream. Otherwise, this
  559. * will check the first characters 100
  560. * (jEdit property vfs.binaryCheck.length) in the system default
  561. * encoding. If more than 1 (jEdit property vfs.binaryCheck.count)
  562. * NUL(\u0000) was found, the stream is declared binary.
  563. *
  564. * This is not 100% because sometimes the autodetection could fail.
  565. *
  566. * This method will not close the stream. You have to do it yourself
  567. *
  568. * @param in the stream
  569. * @return <code>true</code> if the stream was detected as binary
  570. * @throws IOException IOException If an I/O error occurs
  571. * @since jEdit 4.3pre10
  572. */
  573. public static boolean isBinary(InputStream in) throws IOException
  574. {
  575. AutoDetection.Result detection = new AutoDetection.Result(in);
  576. // If an encoding is detected, this is a text stream
  577. if (detection.getDetectedEncoding() != null)
  578. {
  579. return false;
  580. }
  581. // Read the stream in system default encoding. The encoding
  582. // might be wrong. But enough for binary detection.
  583. try
  584. {
  585. return containsNullCharacter(
  586. new InputStreamReader(detection.getRewindedStream()));
  587. }
  588. catch (MalformedInputException mie)
  589. {
  590. // This error probably means the input is binary.
  591. return true;
  592. }
  593. } //}}}
  594. //{{{ isBackup() method
  595. /**
  596. * Check if the filename is a backup file.
  597. * @param filename the filename to check
  598. * @return true if this is a backup file.
  599. * @since jEdit 4.3pre5
  600. */
  601. public static boolean isBackup(String filename)
  602. {
  603. if (filename.startsWith("#")) return true;
  604. if (filename.endsWith("~")) return true;
  605. if (filename.endsWith(".bak")) return true;
  606. return false;
  607. } //}}}
  608. //{{{ autodetect() method
  609. /**
  610. * Tries to detect if the stream is gzipped, and if it has an encoding
  611. * specified with an XML PI.
  612. *
  613. * @param in the input stream reader that must be autodetected
  614. * @param buffer a buffer. It can be null if you only want to autodetect the encoding of a file
  615. * @return a Reader using the detected encoding
  616. * @throws IOException io exception during read
  617. * @since jEdit 4.3pre5
  618. */
  619. public static Reader autodetect(InputStream in, Buffer buffer) throws IOException
  620. {
  621. String encoding;
  622. if (buffer == null)
  623. encoding = System.getProperty("file.encoding");
  624. else
  625. encoding = buffer.getStringProperty(JEditBuffer.ENCODING);
  626. boolean gzipped = false;
  627. if (buffer == null || buffer.getBooleanProperty(Buffer.ENCODING_AUTODETECT))
  628. {
  629. AutoDetection.Result detection = new AutoDetection.Result(in);
  630. gzipped = detection.streamIsGzipped();
  631. if (gzipped)
  632. {
  633. Log.log(Log.DEBUG, MiscUtilities.class
  634. , "Stream is Gzipped");
  635. }
  636. String detected = detection.getDetectedEncoding();
  637. if (detected != null)
  638. {
  639. encoding = detected;
  640. Log.log(Log.DEBUG, MiscUtilities.class
  641. , "Stream encoding detected is " + detected);
  642. }
  643. in = detection.getRewindedStream();
  644. }
  645. else
  646. {
  647. // Make the stream buffered in the same way.
  648. in = AutoDetection.getMarkedStream(in);
  649. }
  650. Reader result = EncodingServer.getTextReader(in, encoding);
  651. if (buffer != null)
  652. {
  653. // Store the successful properties.
  654. if (gzipped)
  655. {
  656. buffer.setBooleanProperty(Buffer.GZIPPED,true);
  657. }
  658. buffer.setProperty(JEditBuffer.ENCODING, encoding);
  659. }
  660. return result;
  661. } //}}}
  662. //{{{ fileToClass() method
  663. /**
  664. * Converts a file name to a class name. All slash characters are
  665. * replaced with periods and the trailing '.class' is removed.
  666. * @param name The file name
  667. */
  668. public static String fileToClass(String name)
  669. {
  670. char[] clsName = name.toCharArray();
  671. for(int i = clsName.length - 6; i >= 0; i--)
  672. if(clsName[i] == '/')
  673. clsName[i] = '.';
  674. return new String(clsName,0,clsName.length - 6);
  675. } //}}}
  676. //{{{ classToFile() method
  677. /**
  678. * Converts a class name to a file name. All periods are replaced
  679. * with slashes and the '.class' extension is added.
  680. * @param name The class name
  681. */
  682. public static String classToFile(String name)
  683. {
  684. return name.replace('.','/').concat(".class");
  685. } //}}}
  686. //{{{ pathsEqual() method
  687. /**
  688. * @param p1 A path name
  689. * @param p2 A path name
  690. * @return True if both paths are equal, ignoring trailing slashes, as
  691. * well as case insensitivity on Windows.
  692. * @since jEdit 4.3pre2
  693. */
  694. public static boolean pathsEqual(String p1, String p2)
  695. {
  696. VFS v1 = VFSManager.getVFSForPath(p1);
  697. VFS v2 = VFSManager.getVFSForPath(p2);
  698. if(v1 != v2)
  699. return false;
  700. if(p1.endsWith("/") || p1.endsWith(File.separator))
  701. p1 = p1.substring(0,p1.length() - 1);
  702. if(p2.endsWith("/") || p2.endsWith(File.separator))
  703. p2 = p2.substring(0,p2.length() - 1);
  704. if((v1.getCapabilities() & VFS.CASE_INSENSITIVE_CAP) != 0)
  705. return p1.equalsIgnoreCase(p2);
  706. else
  707. return p1.equals(p2);
  708. } //}}}
  709. //}}}
  710. //{{{ Text methods
  711. //{{{ escapesToChars() method
  712. /**
  713. * Converts "\n" and "\t" escapes in the specified string to
  714. * newlines and tabs.
  715. * @param str The string
  716. * @since jEdit 2.3pre1
  717. */
  718. public static String escapesToChars(String str)
  719. {
  720. StringBuilder buf = new StringBuilder();
  721. for(int i = 0; i < str.length(); i++)
  722. {
  723. char c = str.charAt(i);
  724. switch(c)
  725. {
  726. case '\\':
  727. if(i == str.length() - 1)
  728. {
  729. buf.append('\\');
  730. break;
  731. }
  732. c = str.charAt(++i);
  733. switch(c)
  734. {
  735. case 'n':
  736. buf.append('\n');
  737. break;
  738. case 't':
  739. buf.append('\t');
  740. break;
  741. default:
  742. buf.append(c);
  743. break;
  744. }
  745. break;
  746. default:
  747. buf.append(c);
  748. }
  749. }
  750. return buf.toString();
  751. } //}}}
  752. //{{{ getLongestPrefix() methods
  753. /**
  754. * Returns the longest common prefix in the given set of strings.
  755. * @param str The strings
  756. * @param ignoreCase If true, case insensitive
  757. * @since jEdit 4.2pre2
  758. */
  759. public static String getLongestPrefix(List<String> str, boolean ignoreCase)
  760. {
  761. if(str.isEmpty())
  762. return "";
  763. int prefixLength = 0;
  764. loop: for(;;)
  765. {
  766. String s = str.get(0);
  767. if(prefixLength >= s.length())
  768. break loop;
  769. char ch = s.charAt(prefixLength);
  770. for(int i = 1; i < str.size(); i++)
  771. {
  772. s = str.get(i);
  773. if(prefixLength >= s.length())
  774. break loop;
  775. if(!compareChars(s.charAt(prefixLength),ch,ignoreCase))
  776. break loop;
  777. }
  778. prefixLength++;
  779. }
  780. return str.get(0).substring(0,prefixLength);
  781. }
  782. /**
  783. * Returns the longest common prefix in the given set of strings.
  784. * @param str The strings
  785. * @param ignoreCase If true, case insensitive
  786. * @since jEdit 4.2pre2
  787. */
  788. public static String getLongestPrefix(String[] str, boolean ignoreCase)
  789. {
  790. return getLongestPrefix((Object[])str,ignoreCase);
  791. }
  792. /**
  793. * Returns the longest common prefix in the given set of strings.
  794. * @param str The strings (calls <code>toString()</code> on each object)
  795. * @param ignoreCase If true, case insensitive
  796. * @since jEdit 4.2pre6
  797. */
  798. public static String getLongestPrefix(Object[] str, boolean ignoreCase)
  799. {
  800. if(str.length == 0)
  801. return "";
  802. int prefixLength = 0;
  803. String first = str[0].toString();
  804. loop: for(;;)
  805. {
  806. if(prefixLength >= first.length())
  807. break loop;
  808. char ch = first.charAt(prefixLength);
  809. for(int i = 1; i < str.length; i++)
  810. {
  811. String s = str[i].toString();
  812. if(prefixLength >= s.length())
  813. break loop;
  814. if(!compareChars(s.charAt(prefixLength),ch,ignoreCase))
  815. break loop;
  816. }
  817. prefixLength++;
  818. }
  819. return first.substring(0,prefixLength);
  820. } //}}}
  821. //}}}
  822. //{{{ buildToVersion() method
  823. /**
  824. * Converts an internal version number (build) into a
  825. * `human-readable' form.
  826. * @param build The build
  827. */
  828. public static String buildToVersion(String build)
  829. {
  830. if(build.length() != 11)
  831. return "<unknown version: " + build + '>';
  832. // First 2 chars are the major version number
  833. int major = Integer.parseInt(build.substring(0,2));
  834. // Second 2 are the minor number
  835. int minor = Integer.parseInt(build.substring(3,5));
  836. // Then the pre-release status
  837. int beta = Integer.parseInt(build.substring(6,8));
  838. // Finally the bug fix release
  839. int bugfix = Integer.parseInt(build.substring(9,11));
  840. return major + "." + minor
  841. + (beta != 99 ? "pre" + beta :
  842. (bugfix != 0 ? "." + bugfix : ""));
  843. } //}}}
  844. //{{{ isToolsJarAvailable() method
  845. /**
  846. * If on JDK 1.2 or higher, make sure that tools.jar is available.
  847. * This method should be called by plugins requiring the classes
  848. * in this library.
  849. * <p>
  850. * tools.jar is searched for in the following places:
  851. * <ol>
  852. * <li>the classpath that was used when jEdit was started,
  853. * <li>jEdit's jars folder in the user's home,
  854. * <li>jEdit's system jars folder,
  855. * <li><i>java.home</i>/lib/. In this case, tools.jar is added to
  856. * jEdit's list of known jars using jEdit.addPluginJAR(),
  857. * so that it gets loaded through JARClassLoader.
  858. * </ol><p>
  859. *
  860. * On older JDK's this method does not perform any checks, and returns
  861. * <code>true</code> (even though there is no tools.jar).
  862. *
  863. * @return <code>false</code> if and only if on JDK 1.2 and tools.jar
  864. * could not be found. In this case it prints some warnings on Log,
  865. * too, about the places where it was searched for.
  866. * @since jEdit 3.2.2
  867. */
  868. public static boolean isToolsJarAvailable()
  869. {
  870. Log.log(Log.DEBUG, MiscUtilities.class,"Searching for tools.jar...");
  871. Collection<String> paths = new LinkedList<String>();
  872. //{{{ 1. Check whether tools.jar is in the system classpath:
  873. paths.add("System classpath: "
  874. + System.getProperty("java.class.path"));
  875. try
  876. {
  877. // Either class sun.tools.javac.Main or
  878. // com.sun.tools.javac.Main must be there:
  879. try
  880. {
  881. Class.forName("sun.tools.javac.Main");
  882. }
  883. catch(ClassNotFoundException e1)
  884. {
  885. Class.forName("com.sun.tools.javac.Main");
  886. }
  887. Log.log(Log.DEBUG, MiscUtilities.class,
  888. "- is in classpath. Fine.");
  889. return true;
  890. }
  891. catch(ClassNotFoundException e)
  892. {
  893. //Log.log(Log.DEBUG, MiscUtilities.class,
  894. // "- is not in system classpath.");
  895. } //}}}
  896. //{{{ 2. Check whether it is in the jEdit user settings jars folder:
  897. String settingsDir = jEdit.getSettingsDirectory();
  898. if(settingsDir != null)
  899. {
  900. String toolsPath = constructPath(settingsDir, "jars",
  901. "tools.jar");
  902. paths.add(toolsPath);
  903. if(new File(toolsPath).exists())
  904. {
  905. Log.log(Log.DEBUG, MiscUtilities.class,
  906. "- is in the user's jars folder. Fine.");
  907. // jEdit will load it automatically
  908. return true;
  909. }
  910. } //}}}
  911. //{{{ 3. Check whether it is in jEdit's system jars folder:
  912. String jEditDir = jEdit.getJEditHome();
  913. if(jEditDir != null)
  914. {
  915. String toolsPath = constructPath(jEditDir, "jars", "tools.jar");
  916. paths.add(toolsPath);
  917. if(new File(toolsPath).exists())
  918. {
  919. Log.log(Log.DEBUG, MiscUtilities.class,
  920. "- is in jEdit's system jars folder. Fine.");
  921. // jEdit will load it automatically
  922. return true;
  923. }
  924. } //}}}
  925. //{{{ 4. Check whether it is in <java.home>/lib:
  926. String toolsPath = System.getProperty("java.home");
  927. if(toolsPath.toLowerCase().endsWith(File.separator + "jre"))
  928. toolsPath = toolsPath.substring(0, toolsPath.length() - 4);
  929. toolsPath = constructPath(toolsPath, "lib", "tools.jar");
  930. paths.add(toolsPath);
  931. if(!new File(toolsPath).exists())
  932. {
  933. Log.log(Log.WARNING, MiscUtilities.class,
  934. "Could not find tools.jar.\n"
  935. + "I checked the following locations:\n"
  936. + paths.toString());
  937. return false;
  938. } //}}}
  939. //{{{ Load it, if not yet done:
  940. PluginJAR jar = jEdit.getPluginJAR(toolsPath);
  941. if(jar == null)
  942. {
  943. Log.log(Log.DEBUG, MiscUtilities.class,
  944. "- adding " + toolsPath + " to jEdit plugins.");
  945. jEdit.addPluginJAR(toolsPath);
  946. }
  947. else
  948. Log.log(Log.DEBUG, MiscUtilities.class,
  949. "- has been loaded before.");
  950. //}}}
  951. return true;
  952. } //}}}
  953. //{{{ parsePermissions() method
  954. /**
  955. * Parse a Unix-style permission string (rwxrwxrwx).
  956. * @param s The string (must be 9 characters long).
  957. * @since jEdit 4.1pre8
  958. */
  959. public static int parsePermissions(String s)
  960. {
  961. int permissions = 0;
  962. if(s.length() == 9)
  963. {
  964. if(s.charAt(0) == 'r')
  965. permissions += 0400;
  966. if(s.charAt(1) == 'w')
  967. permissions += 0200;
  968. if(s.charAt(2) == 'x')
  969. permissions += 0100;
  970. else if(s.charAt(2) == 's')
  971. permissions += 04100;
  972. else if(s.charAt(2) == 'S')
  973. permissions += 04000;
  974. if(s.charAt(3) == 'r')
  975. permissions += 040;
  976. if(s.charAt(4) == 'w')
  977. permissions += 020;
  978. if(s.charAt(5) == 'x')
  979. permissions += 010;
  980. else if(s.charAt(5) == 's')
  981. permissions += 02010;
  982. else if(s.charAt(5) == 'S')
  983. permissions += 02000;
  984. if(s.charAt(6) == 'r')
  985. permissions += 04;
  986. if(s.charAt(7) == 'w')
  987. permissions += 02;
  988. if(s.charAt(8) == 'x')
  989. permissions += 01;
  990. else if(s.charAt(8) == 't')
  991. permissions += 01001;
  992. else if(s.charAt(8) == 'T')
  993. permissions += 01000;
  994. }
  995. return permissions;
  996. } //}}}
  997. //{{{ getEncodings() methods
  998. /**
  999. * Returns a list of supported character encodings.
  1000. * @since jEdit 4.3pre5
  1001. * @param getSelected Whether to return just the selected encodings or all.
  1002. */
  1003. public static String[] getEncodings(boolean getSelected)
  1004. {
  1005. Set<String> set;
  1006. if (getSelected)
  1007. {
  1008. set = EncodingServer.getSelectedNames();
  1009. }
  1010. else
  1011. {
  1012. set = EncodingServer.getAvailableNames();
  1013. }
  1014. return set.toArray(new String[set.size()]);
  1015. } //}}}
  1016. //{{{ throwableToString() method
  1017. /**
  1018. * Returns a string containing the stack trace of the given throwable.
  1019. * @since jEdit 4.2pre6
  1020. */
  1021. public static String throwableToString(Throwable t)
  1022. {
  1023. StringWriter s = new StringWriter();
  1024. t.printStackTrace(new PrintWriter(s));
  1025. return s.toString();
  1026. } //}}}
  1027. //{{{ Private members
  1028. private MiscUtilities() {}
  1029. //{{{ compareChars() method
  1030. /**
  1031. * Compares two chars.
  1032. * should this be public?
  1033. * @param ch1 the first char
  1034. * @param ch2 the second char
  1035. * @param ignoreCase true if you want to ignore case
  1036. */
  1037. private static boolean compareChars(char ch1, char ch2, boolean ignoreCase)
  1038. {
  1039. if(ignoreCase)
  1040. return Character.toUpperCase(ch1) == Character.toUpperCase(ch2);
  1041. else
  1042. return ch1 == ch2;
  1043. } //}}}
  1044. //{{{ getPathStart() method
  1045. private static int getPathStart(String path)
  1046. {
  1047. if(path.startsWith("/"))
  1048. return 0;
  1049. else if(OperatingSystem.isDOSDerived()
  1050. && path.length() >= 3
  1051. && path.charAt(1) == ':'
  1052. && (path.charAt(2) == '/'
  1053. || path.charAt(2) == '\\'))
  1054. return 3;
  1055. else
  1056. return 0;
  1057. } //}}}
  1058. //{{{ containsNullCharacter() method
  1059. private static boolean containsNullCharacter(Reader reader)
  1060. throws IOException
  1061. {
  1062. int nbChars = jEdit.getIntegerProperty("vfs.binaryCheck.length",100);
  1063. int authorized = jEdit.getIntegerProperty("vfs.binaryCheck.count",1);
  1064. for (long i = 0L;i < nbChars;i++)
  1065. {
  1066. int c = reader.read();
  1067. if (c == -1)
  1068. return false;
  1069. if (c == 0)
  1070. {
  1071. authorized--;
  1072. if (authorized == 0)
  1073. return true;
  1074. }
  1075. }
  1076. return false;
  1077. } //}}}
  1078. //}}}
  1079. static VarCompressor svc = null;
  1080. //{{{ VarCompressor class
  1081. /**
  1082. * Singleton class for quickly "compressing" paths into variable-prefixed values.
  1083. * @author alan ezust
  1084. */
  1085. static class VarCompressor
  1086. {
  1087. /** a reverse mapping of values to environment variable names */
  1088. final Map<String, String> prefixMap = new HashMap<String, String>();
  1089. /** previously compressed strings saved for quick access later */
  1090. final Map<String, String> previous = new HashMap<String, String>();
  1091. //{{{ VarCompressor constructor
  1092. VarCompressor()
  1093. {
  1094. ProcessBuilder pb = new ProcessBuilder();
  1095. Map<String, String> env = pb.environment();
  1096. if (OperatingSystem.isUnix())
  1097. prefixMap.put(System.getProperty("user.home"), "~");
  1098. for (Map.Entry<String, String> entry: env.entrySet())
  1099. {
  1100. String k = entry.getKey();
  1101. if (k.equalsIgnoreCase("pwd") || k.equalsIgnoreCase("oldpwd")) continue;
  1102. if (!Character.isLetter(k.charAt(0))) continue;
  1103. String v = entry.getValue();
  1104. // only add possible candidates to the prefix map
  1105. if (!canBePathPrefix(v)) continue;
  1106. // no need for trailing file separator
  1107. if (v.endsWith(File.separator))
  1108. v = v.substring(0, v.length()-1);
  1109. // check if it is actually shorter
  1110. if (OperatingSystem.isWindows())
  1111. if (k.length()+2 > v.length()) continue; // gets replaced by %FOO%
  1112. else
  1113. if (k.length()+1 > v.length()) continue; // gets replaced by $FOO
  1114. if (OperatingSystem.isWindows())
  1115. {
  1116. // no case sensitivity, might as well convert to lower case
  1117. v = v.toLowerCase();
  1118. k = k.toLowerCase();
  1119. }
  1120. if (prefixMap.containsKey(v))
  1121. {
  1122. String otherKey = prefixMap.get(v);
  1123. if (otherKey.length() < k.length()) continue;
  1124. }
  1125. prefixMap.put(v, k);
  1126. }
  1127. } //}}}
  1128. //{{{ compress() method
  1129. String compress(String path)
  1130. {
  1131. String original = path;
  1132. if (previous.containsKey(path))
  1133. {
  1134. return previous.get(path);
  1135. }
  1136. String bestPrefix = "/";
  1137. String verifiedPrefix = bestPrefix;
  1138. for (String tryPrefix : prefixMap.keySet())
  1139. {
  1140. if (tryPrefix.length() < bestPrefix.length()) continue;
  1141. if (OperatingSystem.isWindows() &&
  1142. path.toLowerCase().startsWith(tryPrefix))
  1143. bestPrefix = tryPrefix;
  1144. else if (path.startsWith(tryPrefix))
  1145. {
  1146. bestPrefix = tryPrefix;
  1147. }
  1148. // Only use prefix if it is a directory-prefix of the path
  1149. if (!bestPrefix.equals(verifiedPrefix))
  1150. {
  1151. String remainder = original.substring(bestPrefix.length());
  1152. if (remainder.length() < 1 || remainder.startsWith(File.separator))
  1153. verifiedPrefix = bestPrefix;
  1154. else bestPrefix = verifiedPrefix;
  1155. }
  1156. }
  1157. if (bestPrefix.length() > 1)
  1158. {
  1159. String remainder = original.substring(bestPrefix.length());
  1160. String envvar = prefixMap.get(bestPrefix);
  1161. if (envvar.equals("~"))
  1162. path = envvar + remainder;
  1163. else if (OperatingSystem.isWindows())
  1164. path = '%' + envvar.toUpperCase() + '%' + remainder;
  1165. else
  1166. path = '$' + envvar + remainder;
  1167. }
  1168. previous.put(original, path);
  1169. return path;
  1170. } //}}}
  1171. //{{{ canBePathPrefix() method
  1172. // Returns true if the argument may absolutely point a directory.
  1173. // For speed, no access to file system or network should happen.
  1174. private boolean canBePathPrefix(String s)
  1175. {
  1176. // Do not use File#isDirectory() since it causes
  1177. // access to file system or network to check if
  1178. // the directory is actually exists.
  1179. return !s.contains(File.pathSeparator)
  1180. && new File(s).isAbsolute();
  1181. } //}}}
  1182. } //}}}
  1183. }