PageRenderTime 56ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/okapi/core/src/main/java/net/sf/okapi/common/Util.java

https://bitbucket.org/Xavier_Richez/okapi
Java | 1900 lines | 1391 code | 72 blank | 437 comment | 178 complexity | fdd81e050829d2b546bc08b0bd1cd20b MD5 | raw file

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

  1. /*===========================================================================
  2. Copyright (C) 2008-2017 by the Okapi Framework contributors
  3. -----------------------------------------------------------------------------
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. ============================================================================*/
  14. package net.sf.okapi.common;
  15. import java.io.BufferedOutputStream;
  16. import java.io.File;
  17. import java.io.FileNotFoundException;
  18. import java.io.FileOutputStream;
  19. import java.io.FilenameFilter;
  20. import java.io.IOException;
  21. import java.io.OutputStreamWriter;
  22. import java.io.PrintWriter;
  23. import java.io.PushbackReader;
  24. import java.io.Reader;
  25. import java.io.UnsupportedEncodingException;
  26. import java.io.Writer;
  27. import java.net.MalformedURLException;
  28. import java.net.URI;
  29. import java.net.URISyntaxException;
  30. import java.net.URL;
  31. import java.net.URLDecoder;
  32. import java.net.URLEncoder;
  33. import java.nio.ByteBuffer;
  34. import java.nio.CharBuffer;
  35. import java.nio.charset.CharacterCodingException;
  36. import java.nio.charset.Charset;
  37. import java.nio.charset.CharsetEncoder;
  38. import java.util.Comparator;
  39. import java.util.List;
  40. import java.util.Locale;
  41. import java.util.Map;
  42. import java.util.Map.Entry;
  43. import java.util.regex.Matcher;
  44. import java.util.regex.Pattern;
  45. import javax.xml.xpath.XPathFactory;
  46. import net.sf.okapi.common.exceptions.OkapiException;
  47. import net.sf.okapi.common.exceptions.OkapiIOException;
  48. import net.sf.okapi.common.exceptions.OkapiUnsupportedEncodingException;
  49. import org.w3c.dom.Node;
  50. /**
  51. * Collection of various all-purpose helper functions.
  52. */
  53. public final class Util {
  54. /**
  55. * Name of the root directory variable.
  56. */
  57. public static final String ROOT_DIRECTORY_VAR = "${rootDir}";
  58. /**
  59. * Name of the input root directory variable.
  60. */
  61. public static final String INPUT_ROOT_DIRECTORY_VAR = "${inputRootDir}";
  62. /**
  63. * Enumeration of supported OSes
  64. */
  65. public static enum SUPPORTED_OS {
  66. WINDOWS,
  67. MAC,
  68. LINUX
  69. }
  70. /**
  71. * Line-break string for DOS/Windows.
  72. */
  73. public static final String LINEBREAK_DOS = "\r\n";
  74. /**
  75. * Line-break string for Unix/Linux
  76. */
  77. public static final String LINEBREAK_UNIX = "\n";
  78. /**
  79. * Line-break string for Macintosh
  80. */
  81. public static final String LINEBREAK_MAC = "\r";
  82. /**
  83. * Default RTF style for starting an external code.
  84. */
  85. public static final String RTF_STARTCODE = "{\\cs5\\f1\\cf15\\lang1024 ";
  86. /**
  87. * Default RTF style for ending an external code.
  88. */
  89. public static final String RTF_ENDCODE = "}";
  90. /**
  91. * Default RTF style for starting an internal code.
  92. */
  93. public static final String RTF_STARTINLINE = "{\\cs6\\f1\\cf6\\lang1024 ";
  94. /**
  95. * Default RTF style for ending an internal code.
  96. */
  97. public static final String RTF_ENDINLINE = "}";
  98. /**
  99. * Default RTF style for starting an in-between source/target marker.
  100. */
  101. public static final String RTF_STARTMARKER = "{\\cs15\\v\\cf12\\sub\\f2 \\{0>}{\\v\\f1 ";
  102. /**
  103. * Default RTF style for the first half of a middle part of an in-between source/target marker.
  104. */
  105. public static final String RTF_MIDMARKER1 = "}{\\cs15\\v\\cf12\\sub\\f2 <\\}";
  106. /**
  107. * Default RTF style for the second half of a middle part of an in-between source/target marker.
  108. */
  109. public static final String RTF_MIDMARKER2 = "\\{>}";
  110. /**
  111. * Default RTF style for ending an in-between source/target marker.
  112. */
  113. public static final String RTF_ENDMARKER = "{\\cs15\\v\\cf12\\sub\\f2 <0\\}}";
  114. private static final String NEWLINES_REGEX = "\r(\n)?";
  115. private static final Pattern NEWLINES_REGEX_PATTERN = Pattern.compile(NEWLINES_REGEX);
  116. /**
  117. * Any variable of the type ${varname}.
  118. */
  119. private static final String VARIABLE_REGEX = "\\$\\{([^\\}]+)\\}";
  120. private static final Pattern VARIABLE_REGEX_PATTERN = Pattern.compile(VARIABLE_REGEX);
  121. /**
  122. * Shared flag indicating a translation that was generated using machine translation.
  123. */
  124. public static final String MTFLAG = "MT!";
  125. // Used by openURL()
  126. private static final String[] browsers = { "firefox", "opera", "konqueror", "epiphany",
  127. "seamonkey", "galeon", "kazehakase", "mozilla", "netscape" };
  128. /**
  129. * Converts all \r\n and \r to linefeed (\n)
  130. * @param text
  131. * the text to convert
  132. * @return converted string
  133. */
  134. static public String normalizeNewlines(String text) {
  135. return NEWLINES_REGEX_PATTERN.matcher(text).replaceAll("\n");
  136. }
  137. /**
  138. * Removes from the start of a string any of the specified characters.
  139. *
  140. * @param text
  141. * string to trim.
  142. * @param chars
  143. * list of the characters to trim.
  144. * @return The trimmed string.
  145. */
  146. static public String trimStart (String text,
  147. String chars)
  148. {
  149. if ( text == null ) return text;
  150. int n = 0;
  151. while ( n < text.length() ) {
  152. if ( chars.indexOf(text.charAt(n)) == -1 ) {
  153. break;
  154. }
  155. n++;
  156. }
  157. if ( n >= text.length() ) return "";
  158. if ( n > 0 ) return text.substring(n);
  159. return text;
  160. }
  161. /**
  162. * Removes from the end of a string any of the specified characters.
  163. * @param text
  164. * string to trim.
  165. * @param chars
  166. * list of the characters to trim.
  167. * @return the trimmed string.
  168. */
  169. static public String trimEnd (String text,
  170. String chars)
  171. {
  172. if ( text == null ) return text;
  173. int n = text.length() - 1;
  174. while ( n >= 0 ) {
  175. if (chars.indexOf(text.charAt(n)) == -1)
  176. break;
  177. n--;
  178. }
  179. if ( n < 0 ) return "";
  180. if ( n > 0 ) return text.substring(0, n + 1);
  181. return text;
  182. }
  183. /**
  184. * Gets the directory name of a full path.
  185. *
  186. * @param path
  187. * full path from where to extract the directory name. The path
  188. * can be a URL path (e.g. "/C:/test/file.ext").
  189. * @return The directory name (without the final separator), or an empty
  190. * string if path is a filename.
  191. */
  192. static public String getDirectoryName (String path) {
  193. String tmp = path.replace('\\', '/'); // Normalize separators (some path are mixed)
  194. int n = tmp.lastIndexOf('/');
  195. if ( n > 0 ) {
  196. return path.substring(0, n);
  197. }
  198. else {
  199. return "";
  200. }
  201. }
  202. /**
  203. * Determines if a given path ends with a file name separator for the current platform.
  204. * If not, the file separator is appended to the path.
  205. * @param path the given path.
  206. * @param forceForwardSlash true if the ending separator must be a forward slash.
  207. * @return the given path ending with the file name separator.
  208. */
  209. static public String ensureSeparator (String path,
  210. boolean forceForwardSlash)
  211. {
  212. if ( isEmpty(path) ) return path;
  213. if ( path.endsWith("/") ) return path;
  214. if ( path.endsWith(File.separator) ) {
  215. if ( forceForwardSlash ) {
  216. path = path.substring(0, path.length()-1);
  217. }
  218. else {
  219. return path;
  220. }
  221. }
  222. if ( forceForwardSlash ) {
  223. return path + "/";
  224. }
  225. else {
  226. return path + File.separator;
  227. }
  228. }
  229. /**
  230. * Determines if a given path starts with a file name separator for the current platform.
  231. * If not, the file separator is prefixed to the path.
  232. * @param path the given path.
  233. * @param forceForwardSlash true if the leading separator must be a forward slash.
  234. * @return the given path starting with the file name separator.
  235. */
  236. static public String ensureLeadingSeparator (String path,
  237. boolean forceForwardSlash)
  238. {
  239. if ( isEmpty(path) ) return path;
  240. if ( path.startsWith("/") ) return path;
  241. if ( path.startsWith(File.separator) ) {
  242. if ( forceForwardSlash ) {
  243. path = path.substring(1, path.length());
  244. }
  245. else {
  246. return path;
  247. }
  248. }
  249. if ( forceForwardSlash ) {
  250. return "/" + path;
  251. }
  252. else {
  253. return File.separator + path;
  254. }
  255. }
  256. /**
  257. * Replaces unsupported characters in a given short file name (no directory path) with a given replacer.
  258. * @param fileName the given short file name
  259. * @param replacer the given replacer
  260. * @return the given file name with fixed unsupported characters
  261. */
  262. public static String fixFilename(String fileName, String replacer) {
  263. if (Util.isEmpty(fileName)) return "";
  264. if (Util.isEmpty(replacer)) return fileName;
  265. String regex = "[*:<>?\\\\/|]"; // TODO This regex is for Windows, add for Linux/Mac OS
  266. replacer = replacer.replaceAll(regex, "_"); // In case replacer contains unsupported chars
  267. return fileName.replaceAll(regex, replacer);
  268. }
  269. /**
  270. * Removes exceeding separators in a given path. Normalizes the given path to contain OS-specific file separators.
  271. * @param path the given path
  272. * @return the fixed given path
  273. */
  274. public static String fixPath(String path) {
  275. return fixPath(path, true);
  276. }
  277. /**
  278. * Removes exceeding separators in a given path. Optionally normalizes the given path to contain OS-specific file separators.
  279. * @param path the given path
  280. * @param forceOsSeparators true to ensure the path contains only OS-specific separators
  281. * @return the fixed given path
  282. */
  283. public static String fixPath(String path, boolean forceOsSeparators) {
  284. final String FP_PREFIX = "file://";
  285. String res = path;
  286. boolean hasFileProtocol = res.startsWith(FP_PREFIX);
  287. if (hasFileProtocol) {
  288. res = res.substring(FP_PREFIX.length()); // Remove "file://" part not to replace "///" with "//"
  289. }
  290. res = res.replaceAll("[\\\\/]+", "/");
  291. if (!hasFileProtocol && forceOsSeparators) {
  292. res = res.replace("/", File.separator);
  293. }
  294. // if (res.startsWith("\\")) {
  295. // // Don't let a Windows path start with a back slash
  296. // res = res.substring(1);
  297. // }
  298. return hasFileProtocol ? FP_PREFIX + res : res;
  299. }
  300. /**
  301. * Replaces unsupported characters in a given short file name (no directory path) with underscore.
  302. * @param fileName the given short file name
  303. * @return the given file name with fixed unsupported characters
  304. */
  305. public static String fixFilename(String fileName) {
  306. return fixFilename(fileName, "_");
  307. }
  308. /**
  309. * Builds a path from given parts. Path separators are normalized.
  310. * @param parts parts of the path
  311. * @return path containing the given parts separated with a file name separator
  312. * for the current platform.
  313. */
  314. public static String buildPath(String... parts) {
  315. String res = null;
  316. for (final String part : parts) {
  317. String normalizedPart = part.replace("\\", "/"); // Normalize to slash
  318. if (res == null) {
  319. res = normalizedPart;
  320. }
  321. else {
  322. if (!res.endsWith("/")) {
  323. res += "/";
  324. }
  325. res += normalizedPart;
  326. }
  327. }
  328. return fixPath(res);
  329. }
  330. /**
  331. * Return a list of files based on suffix (i.e, .xml, .html etc.)
  332. * @param directory - directory where files are located
  333. * @param suffix - the sufix used to filter files
  334. * @return - list of files matching the suffix
  335. * @throws URISyntaxException if the syntax is not correct.
  336. */
  337. public static String[] getFilteredFiles(final String directory, final String suffix)
  338. throws URISyntaxException {
  339. File dir = new File(directory);
  340. FilenameFilter filter = new FilenameFilter() {
  341. public boolean accept(File dir, String name) {
  342. return name.endsWith(suffix);
  343. }
  344. };
  345. return dir.list(filter);
  346. }
  347. /**
  348. * Creates the directory tree for the give full path (dir+filename)
  349. *
  350. * @param path
  351. * directory to create and filename. If you want to pass only a directory
  352. * name make sure it has a trailing separator (e.g. "c:\project\tmp\").
  353. * The path can be a URL path (e.g. "/C:/test/file.ext").
  354. * @return true if one of more directories were created or if there was no directory to create,
  355. * false otherwise (an error occurred)
  356. */
  357. static public boolean createDirectories (String path) {
  358. String tmp = path.replace('\\', '/'); // Normalize separators (some path are mixed)
  359. int n = tmp.lastIndexOf('/');
  360. if ( n == -1 ) return true; // Nothing to do
  361. // Else, use the directory part and create the tree
  362. String dirPath = path.substring(0, n);
  363. File dir = new File(dirPath);
  364. if ( !dir.exists() ) return dir.mkdirs();
  365. else return true;
  366. }
  367. /**
  368. * Escape newlines and whitespace so they survive roundtrip as an xml attribute
  369. * \n=\u0098
  370. * \r=\u0097
  371. * \u0020=\u0096
  372. * @param text - original text
  373. * @return escaped string
  374. */
  375. static public String escapeWhitespaceForXML (String text) {
  376. if ( text == null || text.isEmpty()) return "";
  377. StringBuffer sbTmp = new StringBuffer(text.length());
  378. char ch;
  379. for ( int i = 0; i < text.length(); i++ ) {
  380. ch = text.charAt(i);
  381. switch (ch) {
  382. case '\n':
  383. sbTmp.append('\u0098');
  384. continue;
  385. case '\r':
  386. sbTmp.append('\u0097');
  387. continue;
  388. case ' ':
  389. sbTmp.append('\u0096');
  390. continue;
  391. default:
  392. if ( text.charAt(i) > 127 ) { // Extended chars
  393. if ( Character.isHighSurrogate(ch) ) {
  394. int cp = text.codePointAt(i++);
  395. String tmp = new String(Character.toChars(cp));
  396. sbTmp.append(tmp);
  397. } else {
  398. sbTmp.append(text.charAt(i));
  399. }
  400. } else { // ASCII chars
  401. sbTmp.append(text.charAt(i));
  402. }
  403. continue;
  404. }
  405. }
  406. return sbTmp.toString();
  407. }
  408. static public String unescapeWhitespaceForXML (String text) {
  409. if ( text == null || text.isEmpty()) return "";
  410. StringBuffer sbTmp = new StringBuffer(text.length());
  411. char ch;
  412. for ( int i = 0; i < text.length(); i++ ) {
  413. ch = text.charAt(i);
  414. switch (ch) {
  415. case '\u0098':
  416. sbTmp.append('\n');
  417. continue;
  418. case '\u0097':
  419. sbTmp.append('\r');
  420. continue;
  421. case '\u0096':
  422. sbTmp.append(' ');
  423. continue;
  424. default:
  425. if ( text.charAt(i) > 127 ) { // Extended chars
  426. if ( Character.isHighSurrogate(ch) ) {
  427. int cp = text.codePointAt(i++);
  428. String tmp = new String(Character.toChars(cp));
  429. sbTmp.append(tmp);
  430. } else {
  431. sbTmp.append(text.charAt(i));
  432. }
  433. } else { // ASCII chars
  434. sbTmp.append(text.charAt(i));
  435. }
  436. continue;
  437. }
  438. }
  439. return sbTmp.toString();
  440. }
  441. /**
  442. * Escapes a string for XML.
  443. *
  444. * @param text
  445. * string to escape.
  446. * @param quoteMode
  447. * 0=no quote escaped, 1=apos and quot, 2=#39 and quot, and
  448. * 3=quot only.
  449. * @param escapeGT
  450. * true to always escape '&gt;' to gt
  451. * @param encoder
  452. * the character set encoder to use to detect un-supported
  453. * character, or null to never escape normal characters.
  454. * @return the escaped string.
  455. */
  456. static public String escapeToXML (String text,
  457. int quoteMode,
  458. boolean escapeGT,
  459. CharsetEncoder encoder)
  460. {
  461. if ( text == null ) return "";
  462. StringBuffer sbTmp = new StringBuffer(text.length());
  463. char ch;
  464. for ( int i = 0; i < text.length(); i++ ) {
  465. ch = text.charAt(i);
  466. switch (ch) {
  467. case '<':
  468. sbTmp.append("&lt;");
  469. continue;
  470. case '>':
  471. if (escapeGT)
  472. sbTmp.append("&gt;");
  473. else {
  474. if (( i > 0 ) && ( text.charAt(i - 1) == ']' )) {
  475. sbTmp.append("&gt;");
  476. }
  477. else {
  478. sbTmp.append('>');
  479. }
  480. }
  481. continue;
  482. case '&':
  483. sbTmp.append("&amp;");
  484. continue;
  485. case '"':
  486. if ( quoteMode > 0 ) {
  487. sbTmp.append("&quot;");
  488. }
  489. else {
  490. sbTmp.append('"');
  491. }
  492. continue;
  493. case '\'':
  494. switch ( quoteMode ) {
  495. case 1:
  496. sbTmp.append("&apos;");
  497. break;
  498. case 2:
  499. sbTmp.append("&#39;");
  500. break;
  501. default:
  502. sbTmp.append(text.charAt(i));
  503. break;
  504. }
  505. continue;
  506. default:
  507. if ( text.charAt(i) > 127 ) { // Extended chars
  508. if ( Character.isHighSurrogate(ch) ) {
  509. int cp = text.codePointAt(i++);
  510. String tmp = new String(Character.toChars(cp));
  511. if (( encoder != null ) && !encoder.canEncode(tmp) ) {
  512. sbTmp.append(String.format("&#x%x;", cp));
  513. } else {
  514. sbTmp.append(tmp);
  515. }
  516. }
  517. else {
  518. if (( encoder != null ) && !encoder.canEncode(text.charAt(i)) ) {
  519. sbTmp.append(String.format("&#x%04x;", text.codePointAt(i)));
  520. }
  521. else { // No encoder or char is supported
  522. sbTmp.append(text.charAt(i));
  523. }
  524. }
  525. }
  526. else { // ASCII chars
  527. sbTmp.append(text.charAt(i));
  528. }
  529. continue;
  530. }
  531. }
  532. return sbTmp.toString();
  533. }
  534. /**
  535. * Escapes a given string into RTF format.
  536. *
  537. * @param text
  538. * the string to convert.
  539. * @param convertLineBreaks
  540. * Indicates if the line-breaks should be converted.
  541. * @param lineBreakStyle
  542. * Type of line-break conversion. 0=do nothing special, 1=close
  543. * then re-open as external, 2=close then re-open as internal.
  544. * @param encoder
  545. * Encoder to use for the extended characters.
  546. * @return The input string escaped to RTF.
  547. */
  548. static public String escapeToRTF (String text,
  549. boolean convertLineBreaks,
  550. int lineBreakStyle,
  551. CharsetEncoder encoder)
  552. {
  553. try {
  554. if ( text == null ) return "";
  555. StringBuffer tmp = new StringBuffer(text.length());
  556. CharBuffer tmpBuf = CharBuffer.allocate(1);
  557. ByteBuffer encBuf;
  558. for ( int i=0; i<text.length(); i++ ) {
  559. switch ( text.charAt(i) ) {
  560. case '{':
  561. case '}':
  562. case '\\':
  563. tmp.append("\\").append(text.charAt(i));
  564. break;
  565. case '\r': // Skip
  566. break;
  567. case '\n':
  568. if ( convertLineBreaks ) {
  569. switch ( lineBreakStyle ) {
  570. case 1: // Outside external
  571. tmp.append(RTF_ENDCODE);
  572. tmp.append("\r\n\\par ");
  573. tmp.append(RTF_STARTCODE);
  574. continue;
  575. case 2:
  576. tmp.append(RTF_ENDINLINE);
  577. tmp.append("\r\n\\par ");
  578. tmp.append(RTF_STARTINLINE);
  579. continue;
  580. case 0: // Just convert
  581. default:
  582. tmp.append("\r\n\\par ");
  583. continue;
  584. }
  585. }
  586. else {
  587. tmp.append("\n");
  588. }
  589. break;
  590. case '\u00a0': // Non-breaking space
  591. tmp.append("\\~"); // No extra space (it's a control word)
  592. break;
  593. case '\t':
  594. tmp.append("\\tab ");
  595. break;
  596. case '\u2022':
  597. tmp.append("\\bullet ");
  598. break;
  599. case '\u2018':
  600. tmp.append("\\lquote ");
  601. break;
  602. case '\u2019':
  603. tmp.append("\\rquote ");
  604. break;
  605. case '\u201c':
  606. tmp.append("\\ldblquote ");
  607. break;
  608. case '\u201d':
  609. tmp.append("\\rdblquote ");
  610. break;
  611. case '\u2013':
  612. tmp.append("\\endash ");
  613. break;
  614. case '\u2014':
  615. tmp.append("\\emdash ");
  616. break;
  617. case '\u200d':
  618. tmp.append("\\zwj ");
  619. break;
  620. case '\u200c':
  621. tmp.append("\\zwnj ");
  622. break;
  623. case '\u200e':
  624. tmp.append("\\ltrmark ");
  625. break;
  626. case '\u200f':
  627. tmp.append("\\rtlmark ");
  628. break;
  629. default:
  630. if ( text.charAt(i) > 127 ) {
  631. if ( encoder.canEncode(text.charAt(i)) ) {
  632. tmpBuf.put(0, text.charAt(i));
  633. tmpBuf.position(0);
  634. encBuf = encoder.encode(tmpBuf);
  635. if ( encBuf.limit() > 1 ) {
  636. tmp.append(String.format("{\\uc%d", encBuf.limit()));
  637. tmp.append(String.format("\\u%d", (int) text.charAt(i)));
  638. for (int j = 0; j < encBuf.limit(); j++) {
  639. tmp.append(String.format("\\'%x", (encBuf.get(j) < 0 ? (0xFF ^ ~encBuf.get(j))
  640. : encBuf.get(j))));
  641. }
  642. tmp.append("}");
  643. }
  644. else {
  645. tmp.append(String.format("\\u%d", (int) text.charAt(i)));
  646. tmp.append(String.format("\\'%x", (encBuf.get(0) < 0 ? (0xFF ^ ~encBuf.get(0))
  647. : encBuf.get(0))));
  648. }
  649. }
  650. else { // Cannot encode in the RTF encoding, so use
  651. // Just Unicode
  652. tmp.append(String.format("\\u%d ?", (int) text.charAt(i)));
  653. }
  654. }
  655. else {
  656. tmp.append(text.charAt(i));
  657. }
  658. break;
  659. }
  660. }
  661. return tmp.toString();
  662. }
  663. catch ( CharacterCodingException e ) {
  664. throw new OkapiException(e);
  665. }
  666. }
  667. /**
  668. * Recursive function to delete the content of a given directory (including
  669. * all its sub-directories. This does not delete the original parent
  670. * directory.
  671. * @param directory the directory of the content to delete.
  672. * @return true if the content was deleted, false otherwise.
  673. */
  674. public static boolean deleteDirectory (File directory) {
  675. boolean res = true;
  676. File[] list = directory.listFiles();
  677. if ( list == null ) return true;
  678. for ( File f : list ) {
  679. if ( f.isDirectory() ) {
  680. deleteDirectory(f);
  681. }
  682. // Set the result, but keep deleting
  683. if ( !f.delete() ) res = false;
  684. }
  685. return res;
  686. }
  687. /**
  688. * Deletes the content of a given directory, and if requested, the directory
  689. * itself. Sub-directories and their content are part of the deleted
  690. * content.
  691. *
  692. * @param directory
  693. * the path of the directory to delete
  694. * @param contentOnly
  695. * indicates if the directory itself should be removed. If this
  696. * flag is true, only the content is deleted.
  697. */
  698. public static void deleteDirectory (String directory,
  699. boolean contentOnly)
  700. {
  701. File f = new File(directory);
  702. // Make sure this is a directory
  703. if ( !f.isDirectory() ) {
  704. return;
  705. }
  706. deleteDirectory(f);
  707. if ( !contentOnly ) {
  708. f.delete();
  709. }
  710. }
  711. /**
  712. * Gets the filename of a path.
  713. *
  714. * @param path
  715. * the path from where to get the filename. The path can be a URL
  716. * path (e.g. "/C:/test/file.ext").
  717. * @param keepExtension
  718. * true to keep the existing extension, false to remove it.
  719. * @return the filename with or without extension.
  720. */
  721. static public String getFilename (String path,
  722. boolean keepExtension)
  723. {
  724. // Get the filename (allow path with mixed separators)
  725. int n = path.lastIndexOf('/');
  726. int n2 = path.lastIndexOf('\\');
  727. if ( n2 > n ) n = n2;
  728. if ( n == -1 ) { // Then try Windows
  729. n = path.lastIndexOf('\\');
  730. }
  731. if ( n > -1 ) {
  732. path = path.substring(n + 1);
  733. }
  734. // Stop here if we keep the extension
  735. if ( keepExtension ) {
  736. return path;
  737. }
  738. // Else: remove the extension if there is one
  739. n = path.lastIndexOf('.');
  740. if ( n > -1 ) {
  741. return path.substring(0, n);
  742. }
  743. // Else:
  744. return path;
  745. }
  746. /**
  747. * Gets the extension of a given path or filename.
  748. *
  749. * @param path
  750. * the original path or filename.
  751. * @return the last extension of the filename (including the period), or
  752. * empty if there is no period in the filename. If the filename ends
  753. * with a period, the return is a period.
  754. * Never returns null.
  755. */
  756. static public String getExtension (String path) {
  757. // Get the extension
  758. int n = path.lastIndexOf('.');
  759. if (n == -1) return ""; // Empty
  760. return path.substring(n);
  761. }
  762. /**
  763. * Makes a URI string from a path. If the path itself can be recognized as a
  764. * string URI already, it is passed unchanged. For example "C:\test" and
  765. * "file:///C:/test" will both return "file:///C:/test" encoded as URI.
  766. *
  767. * @param pathOrUri
  768. * the path to change to URI string.
  769. * @return the URI string.
  770. * @throws OkapiUnsupportedEncodingException if UTF-8 is not supported (can't happen).
  771. */
  772. static public String makeURIFromPath (String pathOrUri) {
  773. if (isEmpty(pathOrUri)) {
  774. throw new IllegalArgumentException();
  775. }
  776. // This should catch most of the URI forms
  777. pathOrUri = pathOrUri.replace('\\', '/');
  778. if (pathOrUri.indexOf("://") != -1)
  779. return pathOrUri;
  780. // If not that, then assume it's a file
  781. if (pathOrUri.startsWith("file:/"))
  782. pathOrUri = pathOrUri.substring(6);
  783. if (pathOrUri.startsWith("/"))
  784. pathOrUri = pathOrUri.substring(1);
  785. String tmp = URLEncodeUTF8(pathOrUri);
  786. // Use '%20' instead of '+': '+ not working with File(uri) it seems
  787. return "file:///" + tmp.replace("+", "%20");
  788. }
  789. /**
  790. * Creates a new URI object from a path or a URI string.
  791. *
  792. * @param pathOrUri
  793. * the path or URI string to use.
  794. * @return the new URI object for the given path or URI string.
  795. */
  796. static public URI toURI (String pathOrUri) {
  797. try {
  798. // Satisfy unit test for empty path.
  799. if ( pathOrUri == null || pathOrUri.equals("") ) {
  800. return new URI("");
  801. }
  802. URI uri = new URI(pathOrUri);
  803. if ( uri != null && uri.isAbsolute() ) {
  804. return uri;
  805. }
  806. }
  807. catch ( URISyntaxException e ) {
  808. // If that didn't work, try going through File.
  809. }
  810. return new File(pathOrUri).toURI();
  811. }
  812. /**
  813. * Creates a new URL object from a path or a URI string. This is a
  814. * convenience wrapper to catch the various conversion exceptions.
  815. *
  816. * @param pathOrUri
  817. * the path or URI string to use.
  818. * @return the new URI object for the given path or URI string.
  819. */
  820. static public URL toURL(String pathOrUri) {
  821. return URItoURL(toURI(pathOrUri));
  822. }
  823. /**
  824. * Convert a URL to a URI. Convenience method to avoid catching
  825. * {@link URISyntaxException} all over the place.
  826. * @param url The URL to convert
  827. * @return The new URI object for the given URL
  828. */
  829. static public URI URLtoURI (URL url) {
  830. try {
  831. return url.toURI();
  832. } catch (URISyntaxException e) {
  833. throw new OkapiException(e);
  834. }
  835. }
  836. /**
  837. * Convert a URI to a URL. Convenience method to avoid catching
  838. * {@link MalformedURLException}.
  839. * @param uri to convert
  840. * @return the resulting url
  841. */
  842. static public URL URItoURL(URI uri) {
  843. try {
  844. return uri.toURL();
  845. } catch (MalformedURLException e) {
  846. throw new OkapiException(e);
  847. }
  848. }
  849. /**
  850. * Gets the longest common path between an existing current directory and a
  851. * new one.
  852. *
  853. * @param currentDir
  854. * the current longest common path.
  855. * @param newDir
  856. * the new directory to compare with.
  857. * @param ignoreCase
  858. * true if the method should ignore cases differences.
  859. * @return the longest sub-directory that is common to both directories.
  860. * This can be a null if the current directory is null,
  861. * or empty if there is no common path.
  862. */
  863. static public String longestCommonDir (String currentDir,
  864. String newDir,
  865. boolean ignoreCase)
  866. {
  867. if ( currentDir == null ) {
  868. return newDir;
  869. }
  870. if ( currentDir.length() == 0 ) {
  871. return currentDir;
  872. }
  873. // Get temporary copies
  874. String currentLow = currentDir;
  875. String newLow = newDir;
  876. if ( ignoreCase ) {
  877. currentLow = currentDir.toLowerCase();
  878. newLow = newDir.toLowerCase();
  879. }
  880. // The new path equals, or include the existing root: no change
  881. if ( newLow.indexOf(currentLow) == 0 ) {
  882. return currentDir;
  883. }
  884. // Search the common path
  885. String tmp = currentLow;
  886. int i = 0;
  887. while ( newLow.indexOf(tmp) != 0 ) {
  888. tmp = Util.getDirectoryName(tmp);
  889. i++;
  890. if ( tmp.length() == 0 ) {
  891. return ""; // No common path at all
  892. }
  893. }
  894. // Do not return currentDir.substring(0, tmp.length());
  895. // because the lower-case string maybe of a different length than cased one
  896. // (e.g. German Sz). Instead re-do the splitting as many time as needed.
  897. tmp = currentDir;
  898. for ( int j = 0; j < i; j++ ) {
  899. tmp = Util.getDirectoryName(tmp);
  900. }
  901. return tmp;
  902. }
  903. /**
  904. * Gets the longest common path between directories on a given list.
  905. *
  906. * @param ignoreCase
  907. * if the method should ignore cases differences.
  908. * @param directories
  909. * the given list of directories.
  910. * @return the longest sub-directory that is common to all directories.
  911. * This can be a null or empty string.
  912. */
  913. static public String longestCommonDir (boolean ignoreCase, String... directories) {
  914. if (directories == null) return "";
  915. if (directories.length == 1) return directories[0]; // Can be null
  916. String res = directories[0];
  917. for (int i = 1; i < directories.length; i++) {
  918. res = longestCommonDir(res, directories[i], ignoreCase);
  919. }
  920. return res;
  921. }
  922. /**
  923. * Indicates if the current OS is case-sensitive.
  924. *
  925. * @return true if the current OS is case-sensitive, false if otherwise.
  926. */
  927. static public boolean isOSCaseSensitive () {
  928. // May not work on all platforms,
  929. // But should on basic Windows, Mac and Linux
  930. // (Use Windows file separator-type to guess the OS)
  931. return !File.separator.equals("\\");
  932. }
  933. /**
  934. * Writes a Byte-Order-Mark if the encoding indicates it is needed. This
  935. * methods must be the first call after opening the writer.
  936. *
  937. * @param writer
  938. * writer where to output the BOM.
  939. * @param bomOnUTF8
  940. * indicates if we should use a BOM on UTF-8 files.
  941. * @param encoding
  942. * encoding of the output.
  943. * @throws OkapiIOException if anything went wrong with the writing.
  944. */
  945. static public void writeBOMIfNeeded (Writer writer,
  946. boolean bomOnUTF8,
  947. String encoding)
  948. {
  949. try {
  950. String tmp = encoding.toLowerCase();
  951. // Check UTF-8 first (most cases)
  952. if ((bomOnUTF8) && (tmp.equalsIgnoreCase("utf-8"))) {
  953. writer.write("\ufeff");
  954. return;
  955. }
  956. /*
  957. * It seems writers do the following: For "UTF-16" they output
  958. * UTF-16BE with a BOM. For "UTF-16LE" they output UTF-16LE without
  959. * BOM. For "UTF-16BE" they output UTF-16BE without BOM. So we force a
  960. * BOM for UTF-16LE and UTF-16BE
  961. */
  962. if (tmp.equals("utf-16be") || tmp.equals("utf-16le")) {
  963. writer.write("\ufeff");
  964. return;
  965. }
  966. // TODO: Is this an issue? Does *reading* UTF-16LE/BE does not check
  967. // for BOM?
  968. } catch (IOException e) {
  969. throw new OkapiIOException(e);
  970. }
  971. }
  972. /**
  973. * Gets the default system temporary directory to use for the current user.
  974. * The directory path returned has never a trailing separator.
  975. *
  976. * @return The directory path of the temporary directory to use, without
  977. * trailing separator.
  978. */
  979. public static String getTempDirectory () {
  980. String tmp = System.getProperty("java.io.tmpdir");
  981. // Normalize for all platforms: no trailing separator
  982. if (tmp.endsWith(File.separator)) // This separator is always
  983. // platform-specific
  984. tmp = tmp.substring(0, tmp.length() - 1);
  985. return tmp;
  986. }
  987. /**
  988. * Gets the text content of the first TEXT child of an element node. This is
  989. * to use instead of node.getTextContent() which does not work with some
  990. * Macintosh Java VMs. Note this work-around get <b>only the first TEXT
  991. * node</b>.
  992. *
  993. * @param node
  994. * the container element.
  995. * @return the text of the first TEXT child node.
  996. */
  997. public static String getTextContent (Node node) {
  998. Node tmp = node.getFirstChild();
  999. while (true) {
  1000. if (tmp == null)
  1001. return "";
  1002. if (tmp.getNodeType() == Node.TEXT_NODE) {
  1003. return tmp.getNodeValue();
  1004. }
  1005. tmp = tmp.getNextSibling();
  1006. }
  1007. }
  1008. /**
  1009. * Calculates safely a percentage. If the total is 0, the methods return 1.
  1010. *
  1011. * @param part
  1012. * the part of the total.
  1013. * @param total
  1014. * the total.
  1015. * @return the percentage of part in total.
  1016. */
  1017. public static int getPercentage (int part,
  1018. int total)
  1019. {
  1020. return (total == 0 ? 1 : Math.round((float) part / (float) total * 100));
  1021. }
  1022. /**
  1023. * Creates a string Identifier based on the hash code of the given text.
  1024. *
  1025. * @param text
  1026. * the text to make an ID for.
  1027. * @return The string identifier for the given text.
  1028. */
  1029. public static String makeId (String text) {
  1030. int n = text.hashCode();
  1031. return String.format("%s%X", ((n > 0) ? 'P' : 'N'), n);
  1032. }
  1033. /**
  1034. * Indicates if two language codes are 'the same'. The comparison ignores
  1035. * case differences, and if the parameter ignoreRegion is true, any part
  1036. * after the first '-' is also ignored. Note that the character '_' is
  1037. * treated like a character '-'.
  1038. *
  1039. * @param lang1
  1040. * first language code to compare.
  1041. * @param lang2
  1042. * second language code to compare.
  1043. * @param ignoreRegion
  1044. * True to ignore any part after the first separator, false to
  1045. * take it into account.
  1046. * @return true if, according the given options, the two language codes are
  1047. * the same. False otherwise.
  1048. */
  1049. static public boolean isSameLanguage (String lang1,
  1050. String lang2,
  1051. boolean ignoreRegion)
  1052. {
  1053. lang1 = lang1.replace('_', '-');
  1054. lang2 = lang2.replace('_', '-');
  1055. if (ignoreRegion) { // Do not take the region part into account
  1056. int n = lang1.indexOf('-');
  1057. if (n > -1) {
  1058. lang1 = lang1.substring(0, n);
  1059. }
  1060. n = lang2.indexOf('-');
  1061. if (n > -1) {
  1062. lang2 = lang2.substring(0, n);
  1063. }
  1064. }
  1065. return lang1.equalsIgnoreCase(lang2);
  1066. }
  1067. /**
  1068. * Indicates if a given string is null or empty.
  1069. *
  1070. * @param string
  1071. * the string to check.
  1072. * @return true if the given string is null or empty.
  1073. */
  1074. static public boolean isEmpty (String string) {
  1075. return (( string == null ) || ( string.length() == 0 ));
  1076. }
  1077. /**
  1078. * Indicates if a locale id is null or empty.
  1079. * @param locale the locale id to examine.
  1080. * @return true if the given locale id is null or empty, false otherwise.
  1081. */
  1082. static public boolean isNullOrEmpty (LocaleId locale) {
  1083. return (( locale == null ) || ( locale.equals(LocaleId.EMPTY) ));
  1084. }
  1085. /**
  1086. * Indicates if a string is null or empty, optionally ignoring the white spaces.
  1087. * @param string the string to examine.
  1088. * @param ignoreWS true to ignore white spaces.
  1089. * @return true if the given string is null, or empty. The argument ignoreWS is true a string
  1090. * with only white spaces is concidered empty.
  1091. */
  1092. static public boolean isEmpty (String string,
  1093. boolean ignoreWS)
  1094. {
  1095. if ( ignoreWS && ( string != null )) {
  1096. string = string.trim();
  1097. }
  1098. return isEmpty(string);
  1099. }
  1100. /**
  1101. * Indicates if a StringBuilder object is null or empty.
  1102. * @param sb the object to examine.
  1103. * @return true if the given object is null or empty.
  1104. */
  1105. static public boolean isEmpty (StringBuilder sb) {
  1106. return (( sb == null ) || ( sb.length() == 0 ));
  1107. }
  1108. /**
  1109. * Indicates if a given list is null or empty.
  1110. * @param <E> the type of the elements in the list.
  1111. * @param e the list to examine.
  1112. * @return true if the list is null or empty.
  1113. */
  1114. static public <E> boolean isEmpty (List <E> e) {
  1115. return (( e == null ) || e.isEmpty() );
  1116. }
  1117. /**
  1118. * Indicates if a given map is null or empty.
  1119. * @param <K> the type of the map's keys.
  1120. * @param <V> the type of the map's values.
  1121. * @param map the map to examine.
  1122. * @return true if the map is null or empty,
  1123. */
  1124. public static <K, V> boolean isEmpty (Map<K, V> map) {
  1125. return (( map == null ) || map.isEmpty() );
  1126. }
  1127. /**
  1128. * Indicates if an array is null or empty.
  1129. * @param e the array to examine.
  1130. * @return true if the given array is null or empty.
  1131. */
  1132. static public boolean isEmpty (Object[] e) {
  1133. return (e == null ||(e != null && e.length == 0));
  1134. }
  1135. /**
  1136. * Gets the length of a string, even a null one.
  1137. * @param string the string to examine.
  1138. * @return the length of the given string, 0 if the string is null.
  1139. */
  1140. static public int getLength (String string) {
  1141. return (isEmpty(string)) ? 0 : string.length();
  1142. }
  1143. /**
  1144. * Gets a character at a given position in a string.
  1145. * The string can be null and the position can be beyond the last character.
  1146. * @param string the string to examine.
  1147. * @param pos the position of the character to retrieve.
  1148. * @return the character at the given position,
  1149. * or '\0' if the string is null or if the position is beyond the length of the string.
  1150. */
  1151. static public char getCharAt (String string,
  1152. int pos)
  1153. {
  1154. if ( isEmpty(string) ) {
  1155. return '\0';
  1156. }
  1157. return (string.length() > pos) ? string.charAt(pos) : '\0';
  1158. }
  1159. /**
  1160. * Gets the last character of a given string.
  1161. * The string can be null or empty.
  1162. * @param string the string to examine.
  1163. * @return the last character of the given string,
  1164. * or '\0' if the string is null or empty.
  1165. */
  1166. static public char getLastChar (String string) {
  1167. if ( isEmpty(string) ) {
  1168. return '\0';
  1169. }
  1170. return string.charAt(string.length() - 1);
  1171. }
  1172. /**
  1173. * Deletes the last character of a given string.
  1174. * The string can be null or empty.
  1175. * @param string the string where to remove the character.
  1176. * @return a new string where the last character has been removed,
  1177. * or an empty string if the given string was null or empty.
  1178. */
  1179. static public String deleteLastChar (String string) {
  1180. if ( isEmpty(string) ) {
  1181. return "";
  1182. }
  1183. return string.substring(0, string.length() - 1);
  1184. }
  1185. /**
  1186. * Gets the last character of a given StringBuilder object.
  1187. * The object can be null or empty.
  1188. * @param sb the StringBuilder object to examine.
  1189. * @return the last character of the given StringBuilder object,
  1190. * or '\0' if the object is null or empty.
  1191. */
  1192. static public char getLastChar (StringBuilder sb) {
  1193. if ( isEmpty(sb) ) {
  1194. return '\0';
  1195. }
  1196. return sb.charAt(sb.length() - 1);
  1197. }
  1198. /**
  1199. * Deletes the last character of a given StringBuilder object.
  1200. * If the object is null or empty no character are removed.
  1201. * @param sb the StringBuilder object where to remove the character.
  1202. */
  1203. static public void deleteLastChar (StringBuilder sb) {
  1204. if ( isEmpty(sb) ) {
  1205. return;
  1206. }
  1207. sb.deleteCharAt(sb.length() - 1);
  1208. }
  1209. /**
  1210. * Indicates if a given index is within the list bounds.
  1211. * @param <E> the type of the list's elements.
  1212. * @param index the given index.
  1213. * @param list the given list.
  1214. * @return true if a given index is within the list bounds.
  1215. */
  1216. public static <E> boolean checkIndex (int index,
  1217. List<E> list)
  1218. {
  1219. return (list != null) && (index >= 0) && (index < list.size());
  1220. }
  1221. /**
  1222. * Converts an integer value to a string.
  1223. * This method simply calls <code>String.valueOf(intValue);</code>.
  1224. * @param value the value to convert.
  1225. * @return the string representation of the given value.
  1226. */
  1227. public static String intToStr (int value) {
  1228. return String.valueOf(value);
  1229. }
  1230. /**
  1231. * Converts a string to an integer. If the conversion fails the method
  1232. * returns the given default value.
  1233. * @param value the string to convert.
  1234. * @param intDefault the default value to use if the conversion fails.
  1235. * @return the integer value of the string, or the provided default
  1236. * value if the conversion failed.
  1237. */
  1238. public static int strToInt (String value,
  1239. int intDefault)
  1240. {
  1241. if ( Util.isEmpty(value) ) {
  1242. return intDefault;
  1243. }
  1244. try {
  1245. return Integer.valueOf(value);
  1246. }
  1247. catch (NumberFormatException e) {
  1248. return intDefault;
  1249. }
  1250. }
  1251. /**
  1252. * Convert String to int .
  1253. * Almost 3x faster than Integer.valueOf()
  1254. * @param s - string to be converted to int
  1255. * @return int represented by string
  1256. * @throws NumberFormatException if the {@link String} does not represent a number.
  1257. */
  1258. public static int fastParseInt(final String s)
  1259. {
  1260. if (s == null)
  1261. throw new NumberFormatException("Null string");
  1262. // Check for a sign.
  1263. int num = 0;
  1264. int sign = -1;
  1265. final int len = s.length();
  1266. final char ch = s.charAt(0);
  1267. if (ch == '-')
  1268. {
  1269. if (len == 1)
  1270. throw new NumberFormatException("Missing digits: " + s);
  1271. sign = 1;
  1272. }
  1273. else
  1274. {
  1275. final int d = ch - '0';
  1276. if (d < 0 || d > 9)
  1277. throw new NumberFormatException("Malformed: " + s);
  1278. num = -d;
  1279. }
  1280. // Build the number.
  1281. final int max = (sign == -1) ?
  1282. -Integer.MAX_VALUE : Integer.MIN_VALUE;
  1283. final int multmax = max / 10;
  1284. int i = 1;
  1285. while (i < len)
  1286. {
  1287. int d = s.charAt(i++) - '0';
  1288. if (d < 0 || d > 9)
  1289. throw new NumberFormatException("Malformed: " + s);
  1290. if (num < multmax)
  1291. throw new NumberFormatException("Over/underflow: " + s);
  1292. num *= 10;
  1293. if (num < (max + d))
  1294. throw new NumberFormatException("Over/underflow: " + s);
  1295. num -= d;
  1296. }
  1297. return sign * num;
  1298. }
  1299. /**
  1300. * Converts a string to a long. If the conversion fails the method
  1301. * returns the given default value.
  1302. * @param value the string to convert.
  1303. * @param longDefault the default value to use if the conversion fails.
  1304. * @return the long value of the string, or the provided default
  1305. * value if the conversion failed.
  1306. */
  1307. public static long strToLong (String value,
  1308. long longDefault)
  1309. {
  1310. if ( Util.isEmpty(value) ) {
  1311. return longDefault;
  1312. }
  1313. try {
  1314. return Long.valueOf(value);
  1315. }
  1316. catch (NumberFormatException e) {
  1317. return longDefault;
  1318. }
  1319. }
  1320. /**
  1321. * Converts a string to a double. If the conversion fails the method
  1322. * returns the given default value.
  1323. * @param value the string to convert.
  1324. * @param doubleDefault the default value to use if the conversion fails.
  1325. * @return the double value of the string, or the provided default
  1326. * value if the conversion failed.
  1327. */
  1328. public static double strToDouble (String value,
  1329. double doubleDefault)
  1330. {
  1331. if ( Util.isEmpty(value) ) {
  1332. return doubleDefault;
  1333. }
  1334. try {
  1335. return Double.valueOf(value);
  1336. }
  1337. catch (NumberFormatException e) {
  1338. return doubleDefault;
  1339. }
  1340. }
  1341. /**
  1342. * Gets the element of an array for a given index.
  1343. * the method returns null if the index is out of bounds.
  1344. * @param <T> the type of the array's elements.
  1345. * @param array the array where to lookup the element.
  1346. * @param index the index.
  1347. * @return the element of the array for the given index, or null if the
  1348. * index is out of bounds, or if the element is null.
  1349. */
  1350. public static <T>T get(T[] array,
  1351. int index)
  1352. {
  1353. if (( index >= 0 ) && ( index < array.length )) {
  1354. return array[index];
  1355. }
  1356. else {
  1357. return null;
  1358. }
  1359. }
  1360. /**
  1361. * Returns true if a given index is within the array bounds.
  1362. * @param <T> the type of the array's elements.
  1363. * @param index the given index.
  1364. * @param array the given list.
  1365. * @return true if a given index is within the array bounds.
  1366. */
  1367. public static <T> boolean checkIndex (int index,
  1368. T[] array)
  1369. {
  1370. return (array != null) && (index >= 0) && (index < array.length);
  1371. }
  1372. /**
  1373. * Indicates whether a byte-flag is set or not in a given value.
  1374. * @param value the value to check.
  1375. * @param flag the flag to look for.
  1376. * @return true if the flag is set, false if it is not.
  1377. */
  1378. public static boolean checkFlag (int value,
  1379. int flag)
  1380. {
  1381. return (value & flag) == flag;
  1382. }
  1383. /**
  1384. * Get the operating system
  1385. * @return one of WINDOWS, MAC or LINUX
  1386. */
  1387. public static SUPPORTED_OS getOS () {
  1388. String osName = System.getProperty("os.name");
  1389. if (osName.startsWith("Windows")) { // Windows case
  1390. return SUPPORTED_OS.WINDOWS;
  1391. }
  1392. else if (osName.contains("OS X")) { // Macintosh case
  1393. return SUPPORTED_OS.MAC;
  1394. }
  1395. return SUPPORTED_OS.LINUX;
  1396. }
  1397. /**
  1398. * Opens the specified page in a web browser (Java 1.5 compatible).
  1399. * <p>This is based on the public domain class BareBonesBrowserLaunch from Dem Pilafian at
  1400. * (<a href="http://www.centerkey.com/java/browser/">www.centerkey.com/java/browser</a>)
  1401. * @param url the URL of the page to open.
  1402. */
  1403. public static void openURL (String url) {
  1404. String osName = System.getProperty("os.name");
  1405. try {
  1406. if (osName.contains("OS X")) { // Macintosh case
  1407. /* One possible way. But this causes warning when run with -XstartOnFirstThread option
  1408. Class<?> fileMgr = Class.forName("com.apple.eio.FileManager");
  1409. Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[]{String.class});
  1410. openURL.invoke(null, new Object[] {url}); */
  1411. // So, use open, and seems to work on the Macintosh (not Linux)
  1412. Runtime.getRuntime().exec("open " + url);
  1413. }
  1414. else if (osName.startsWith("Windows")) { // Windows case
  1415. Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
  1416. }
  1417. else { // Assumes Unix or Linux
  1418. boolean found = false;
  1419. for ( String browser : browsers ) {
  1420. if ( !found ) { // Search for the first browser available
  1421. found = Runtime.getRuntime().exec(new String[] {"which", browser}).waitFor() == 0;
  1422. if ( found ) { // Start it if we find one
  1423. Runtime.getRuntime().exec(new String[] {browser, url});
  1424. }
  1425. }
  1426. }
  1427. if ( !found ) {
  1428. throw new Exception("No browser found.");
  1429. }
  1430. }
  1431. }
  1432. catch ( Throwable e ) {
  1433. throw new OkapiException("Error attempting to launch web browser.", e);
  1434. }
  1435. }
  1436. /**
  1437. * Opens a given topic of the OkapiWiki.
  1438. * @param topic the title of the topic/page.
  1439. */
  1440. public static void openWikiTopic (String topic) {
  1441. try {
  1442. // Resolve spaces
  1443. topic = topic.replace(' ', '_');
  1444. //TODO: get the base URL from a properties file
  1445. Util.openURL(new URL(String.format("http://okapiframework.org/wiki/index.php?title=%s", topic)).toString());
  1446. }
  1447. catch ( MalformedURLException e ) {
  1448. e.printStackTrace();
  1449. }
  1450. }
  1451. /**
  1452. * Gets the directory location of a given class. The value returned can be the directory
  1453. * where the .class file is located, or, if the class in a JAR file, the directory
  1454. * where the .jar file is located.
  1455. * @param theClass the class to query.
  1456. * @return the directory location of the given class, or null if an error occurs.
  1457. */
  1458. public static String getClassLocation (Class<?> theClass) {
  1459. String res = null;
  1460. File file = new File(theClass.getProtectionDomain().getCodeSource().getLocation().getFile());
  1461. res = URLDecodeUTF8(file.getAbsolutePath());
  1462. // Remove the JAR file if necessary
  1463. if ( res.endsWith(".jar") ) {
  1464. res = getDirectoryName(res);
  1465. }
  1466. return res;
  1467. }
  1468. // Unused
  1469. // /**
  1470. // * Generate a random string consisting only of numbers
  1471. // * @param length - specifies length of the string
  1472. // * @return a random String
  1473. // */
  1474. // public static String generateRandomId(int length) {
  1475. // Random rnd = new Random();
  1476. //
  1477. // StringBuilder sb = new StringBuilder( length );
  1478. // for( int i = 0; i < length; i++ ) {
  1479. // sb.append(rnd.nextInt(9));
  1480. // }
  1481. // return sb.toString();
  1482. //
  1483. // }
  1484. /**
  1485. * Replaces in a given original string, a potential variable ROOT_DIRECTORY_VAR by a given root directory.
  1486. * @param original the original string where to perform the replacement.
  1487. * @param rootDir the root directory. If null it will be automatically set to the
  1488. * user home directory.
  1489. * @return the original string with ROOT_DIRECTORY_VAR replaced if it was there.
  1490. */
  1491. public static String fillRootDirectoryVariable (String original,
  1492. String rootDir)
  1493. {
  1494. if ( rootDir == null ) {
  1495. if ( !original.contains(ROOT_DIRECTORY_VAR) ) {
  1496. // We early-out here if no work needs to be done so as to avoid
  1497. // unnecessarily throwing when resolving user.dir.
  1498. return original;
  1499. } else {
  1500. // The user.dir system property is restricted in some scenarios.
  1501. // See "Forbidden System Properties":
  1502. // https://docs.oracle.com/javase/tutorial/deployment/doingMoreWithRIA/properties.html
  1503. // However we know that we need to replace ROOT_DIRECTORY_VAR
  1504. // now, and the path will be useless without replacement, so we
  1505. // just throw and let the caller handle it.
  1506. rootDir = System.getProperty("user.dir");
  1507. }
  1508. }
  1509. return original.replace(ROOT_DIRECTORY_VAR, rootDir);
  1510. }
  1511. /**
  1512. * Replaces in a given original string, a potential variable INPUT_ROOT_DIRECTORY_VAR by a given root directory.
  1513. * @param original the original string where to perform the replacement.
  1514. * @param inputRootDir the input root directory. If null it will be automatically set to
  1515. * an empty string.
  1516. * @return the original string with INPUT_ROOT_DIRECTORY_VAR replaced if it was there.
  1517. */
  1518. public static String fillInputRootDirectoryVariable (String original,
  1519. String inputRootDir)
  1520. {
  1521. if ( inputRootDir == null ) {
  1522. inputRootDir = "";
  1523. }
  1524. return original.replace(INPUT_ROOT_DIRECTORY_VAR, inputRootDir);
  1525. }
  1526. /**
  1527. * Expands environment variables by replacing strings of the type
  1528. * ${varname} with their values as reported by the system.
  1529. * @param original The original string in which to perform the replacement
  1530. * @return The original string with all environment variables expanded
  1531. */
  1532. public static String fillSystemEnvars(String original)
  1533. {
  1534. for (Entry<String, String> e : System.getenv().entrySet()) {
  1535. original = original.replace(String.format("${%s}", e.getKey()), e.getValue());
  1536. }
  1537. return original;
  1538. }
  1539. /**
  1540. * Check a piece of text to make sure that all contained variables (${foo})
  1541. * are resolvable by the fill...() methods in this class.
  1542. * @param text The text to check
  1543. * @param allowEnvar Whether or not to allow system environment variables
  1544. * @param allowRootDir Whether or not to allow ${rootDir}
  1545. * @param allowInputRootDir Whether or not to allow ${inputRootDir}
  1546. * @return Whether or not the input text's variables are valid
  1547. */
  1548. public static boolean validateVariables(String text,
  1549. boolean allowEnvar, boolean allowRootDir, boolean allowInputRootDir) {
  1550. Matcher m = VARIABLE_REGEX_PATTERN.matcher(text);
  1551. while (m.find()) {
  1552. String var = m.group();
  1553. String varName = m.group(1);
  1554. if (allowEnvar && System.getenv().containsKey(varName)) continue;
  1555. if (allowRootDir && var.equals(ROOT_DIRECTORY_VAR)) continue;
  1556. if (allowInputRootDir && var.equals(INPUT_ROOT_DIRECTORY_VAR)) continue;
  1557. return false;
  1558. }
  1559. return true;
  1560. }
  1561. /**
  1562. * Expand all supported variables and canonicalize (resolve ".." and ".")
  1563. * a path. If the input path has no variables, it is returned unchanged.
  1564. * rootDir and inputRootDir can be null.
  1565. * @param path The path to expand
  1566. * @param rootDir The directory to expand ${rootDir} into
  1567. * @param inputRootDir The directory to expand ${inputRootDir} into
  1568. * @return The expanded path
  1569. * @throws IOException If canonicalizing fails
  1570. */
  1571. public static String expandPath (String path, String rootDir, String inputRootDir)
  1572. throws IOException {
  1573. if (!path.contains("${")) return path;
  1574. path = Util.fillSystemEnvars(path);
  1575. path = Util.fillRootDirectoryVariable(path, rootDir);
  1576. path = Util.fillInputRootDirectoryVariable(path, inputRootDir);
  1577. path = new File(path).getCanonicalPath();
  1578. return path;
  1579. }
  1580. /**
  1581. * Returns the smallest value in a given array of values.
  1582. * @param values the given array
  1583. * @return the smallest value in the array
  1584. */
  1585. public static int min (int... values) {
  1586. int res = Integer.MAX_VALUE;
  1587. for (int value : values) {
  1588. res = Math.min(res, value);
  1589. }
  1590. return (values.length > 0) ?

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