PageRenderTime 66ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/jEdit/branches/4.5.x/org/gjt/sp/jedit/MiscUtilities.java

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