PageRenderTime 56ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/jEdit/trunk/org/gjt/sp/jedit/MiscUtilities.java

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