1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation
  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. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.util;
  18. import java.io.BufferedInputStream;
  19. import java.io.BufferedReader;
  20. import java.io.BufferedWriter;
  21. import java.io.File;
  22. import java.io.FileInputStream;
  23. import java.io.FileOutputStream;
  24. import java.io.FileReader;
  25. import java.io.FileWriter;
  26. import java.io.IOException;
  27. import java.io.InputStream;
  28. import java.io.InputStreamReader;
  29. import java.io.OutputStreamWriter;
  30. import java.io.Reader;
  31. import java.lang.reflect.Method;
  32. import java.net.MalformedURLException;
  33. import java.net.URL;
  34. import java.text.CharacterIterator;
  35. import java.text.DecimalFormat;
  36. import java.text.StringCharacterIterator;
  37. import java.util.Random;
  38. import java.util.Stack;
  39. import java.util.StringTokenizer;
  40. import java.util.Vector;
  41. import org.apache.tools.ant.BuildException;
  42. import org.apache.tools.ant.Project;
  43. import org.apache.tools.ant.filters.util.ChainReaderHelper;
  44. import org.apache.tools.ant.taskdefs.condition.Os;
  45. import org.apache.tools.ant.types.FilterSetCollection;
  46. import org.apache.tools.ant.launch.Locator;
  47. /**
  48. * This class also encapsulates methods which allow Files to be
  49. * refered to using abstract path names which are translated to native
  50. * system file paths at runtime as well as copying files or setting
  51. * there last modification time.
  52. *
  53. * @version $Revision: 1.56.2.7 $
  54. */
  55. public class FileUtils {
  56. private static Random rand = new Random(System.currentTimeMillis());
  57. private static Object lockReflection = new Object();
  58. private static java.lang.reflect.Method setLastModified = null;
  59. private boolean onNetWare = Os.isFamily("netware");
  60. // for toURI
  61. private static boolean[] isSpecial = new boolean[256];
  62. private static char[] escapedChar1 = new char[256];
  63. private static char[] escapedChar2 = new char[256];
  64. /**
  65. * the granularity of timestamps under FAT
  66. */
  67. public static final long FAT_FILE_TIMESTAMP_GRANULARITY = 2000;
  68. // stolen from FilePathToURI of the Xerces-J team
  69. static {
  70. for (int i = 0; i <= 0x20; i++) {
  71. isSpecial[i] = true;
  72. escapedChar1[i] = Character.forDigit(i >> 4, 16);
  73. escapedChar2[i] = Character.forDigit(i & 0xf, 16);
  74. }
  75. isSpecial[0x7f] = true;
  76. escapedChar1[0x7f] = '7';
  77. escapedChar2[0x7f] = 'F';
  78. char[] escChs = {'<', '>', '#', '%', '"', '{', '}',
  79. '|', '\\', '^', '~', '[', ']', '`'};
  80. int len = escChs.length;
  81. char ch;
  82. for (int i = 0; i < len; i++) {
  83. ch = escChs[i];
  84. isSpecial[ch] = true;
  85. escapedChar1[ch] = Character.forDigit(ch >> 4, 16);
  86. escapedChar2[ch] = Character.forDigit(ch & 0xf, 16);
  87. }
  88. }
  89. /**
  90. * Factory method.
  91. *
  92. * @return a new instance of FileUtils.
  93. */
  94. public static FileUtils newFileUtils() {
  95. return new FileUtils();
  96. }
  97. /**
  98. * Empty constructor.
  99. */
  100. protected FileUtils() {
  101. }
  102. /**
  103. * Get the URL for a file taking into account # characters
  104. *
  105. * @param file the file whose URL representation is required.
  106. * @return The FileURL value
  107. * @throws MalformedURLException if the URL representation cannot be
  108. * formed.
  109. */
  110. public URL getFileURL(File file) throws MalformedURLException {
  111. return new URL(toURI(file.getAbsolutePath()));
  112. }
  113. /**
  114. * Convienence method to copy a file from a source to a destination.
  115. * No filtering is performed.
  116. *
  117. * @param sourceFile Name of file to copy from.
  118. * Must not be <code>null</code>.
  119. * @param destFile Name of file to copy to.
  120. * Must not be <code>null</code>.
  121. *
  122. * @throws IOException if the copying fails
  123. */
  124. public void copyFile(String sourceFile, String destFile)
  125. throws IOException {
  126. copyFile(new File(sourceFile), new File(destFile), null, false, false);
  127. }
  128. /**
  129. * Convienence method to copy a file from a source to a destination
  130. * specifying if token filtering must be used.
  131. *
  132. * @param sourceFile Name of file to copy from.
  133. * Must not be <code>null</code>.
  134. * @param destFile Name of file to copy to.
  135. * Must not be <code>null</code>.
  136. * @param filters the collection of filters to apply to this copy
  137. *
  138. * @throws IOException if the copying fails
  139. */
  140. public void copyFile(String sourceFile, String destFile,
  141. FilterSetCollection filters)
  142. throws IOException {
  143. copyFile(new File(sourceFile), new File(destFile), filters,
  144. false, false);
  145. }
  146. /**
  147. * Convienence method to copy a file from a source to a
  148. * destination specifying if token filtering must be used and if
  149. * source files may overwrite newer destination files.
  150. *
  151. * @param sourceFile Name of file to copy from.
  152. * Must not be <code>null</code>.
  153. * @param destFile Name of file to copy to.
  154. * Must not be <code>null</code>.
  155. * @param filters the collection of filters to apply to this copy
  156. * @param overwrite Whether or not the destination file should be
  157. * overwritten if it already exists.
  158. *
  159. * @throws IOException if the copying fails
  160. */
  161. public void copyFile(String sourceFile, String destFile, FilterSetCollection filters,
  162. boolean overwrite) throws IOException {
  163. copyFile(new File(sourceFile), new File(destFile), filters,
  164. overwrite, false);
  165. }
  166. /**
  167. * Convienence method to copy a file from a source to a
  168. * destination specifying if token filtering must be used, if
  169. * source files may overwrite newer destination files and the
  170. * last modified time of <code>destFile</code> file should be made equal
  171. * to the last modified time of <code>sourceFile</code>.
  172. *
  173. * @param sourceFile Name of file to copy from.
  174. * Must not be <code>null</code>.
  175. * @param destFile Name of file to copy to.
  176. * Must not be <code>null</code>.
  177. * @param filters the collection of filters to apply to this copy
  178. * @param overwrite Whether or not the destination file should be
  179. * overwritten if it already exists.
  180. * @param preserveLastModified Whether or not the last modified time of
  181. * the resulting file should be set to that
  182. * of the source file.
  183. *
  184. * @throws IOException if the copying fails
  185. */
  186. public void copyFile(String sourceFile, String destFile, FilterSetCollection filters,
  187. boolean overwrite, boolean preserveLastModified)
  188. throws IOException {
  189. copyFile(new File(sourceFile), new File(destFile), filters,
  190. overwrite, preserveLastModified);
  191. }
  192. /**
  193. * Convienence method to copy a file from a source to a
  194. * destination specifying if token filtering must be used, if
  195. * source files may overwrite newer destination files and the
  196. * last modified time of <code>destFile</code> file should be made equal
  197. * to the last modified time of <code>sourceFile</code>.
  198. *
  199. * @param sourceFile Name of file to copy from.
  200. * Must not be <code>null</code>.
  201. * @param destFile Name of file to copy to.
  202. * Must not be <code>null</code>.
  203. * @param filters the collection of filters to apply to this copy
  204. * @param overwrite Whether or not the destination file should be
  205. * overwritten if it already exists.
  206. * @param preserveLastModified Whether or not the last modified time of
  207. * the resulting file should be set to that
  208. * of the source file.
  209. * @param encoding the encoding used to read and write the files.
  210. *
  211. * @throws IOException if the copying fails
  212. *
  213. * @since Ant 1.5
  214. */
  215. public void copyFile(String sourceFile, String destFile,
  216. FilterSetCollection filters, boolean overwrite,
  217. boolean preserveLastModified, String encoding)
  218. throws IOException {
  219. copyFile(new File(sourceFile), new File(destFile), filters,
  220. overwrite, preserveLastModified, encoding);
  221. }
  222. /**
  223. * Convienence method to copy a file from a source to a
  224. * destination specifying if token filtering must be used, if
  225. * filter chains must be used, if source files may overwrite
  226. * newer destination files and the last modified time of
  227. * <code>destFile</code> file should be made equal
  228. * to the last modified time of <code>sourceFile</code>.
  229. *
  230. * @param sourceFile Name of file to copy from.
  231. * Must not be <code>null</code>.
  232. * @param destFile Name of file to copy to.
  233. * Must not be <code>null</code>.
  234. * @param filters the collection of filters to apply to this copy
  235. * @param filterChains filterChains to apply during the copy.
  236. * @param overwrite Whether or not the destination file should be
  237. * overwritten if it already exists.
  238. * @param preserveLastModified Whether or not the last modified time of
  239. * the resulting file should be set to that
  240. * of the source file.
  241. * @param encoding the encoding used to read and write the files.
  242. * @param project the project instance
  243. *
  244. * @throws IOException if the copying fails
  245. *
  246. * @since Ant 1.5
  247. */
  248. public void copyFile(String sourceFile, String destFile,
  249. FilterSetCollection filters, Vector filterChains,
  250. boolean overwrite, boolean preserveLastModified,
  251. String encoding, Project project)
  252. throws IOException {
  253. copyFile(new File(sourceFile), new File(destFile), filters,
  254. filterChains, overwrite, preserveLastModified,
  255. encoding, project);
  256. }
  257. /**
  258. * Convienence method to copy a file from a source to a
  259. * destination specifying if token filtering must be used, if
  260. * filter chains must be used, if source files may overwrite
  261. * newer destination files and the last modified time of
  262. * <code>destFile</code> file should be made equal
  263. * to the last modified time of <code>sourceFile</code>.
  264. *
  265. * @param sourceFile Name of file to copy from.
  266. * Must not be <code>null</code>.
  267. * @param destFile Name of file to copy to.
  268. * Must not be <code>null</code>.
  269. * @param filters the collection of filters to apply to this copy
  270. * @param filterChains filterChains to apply during the copy.
  271. * @param overwrite Whether or not the destination file should be
  272. * overwritten if it already exists.
  273. * @param preserveLastModified Whether or not the last modified time of
  274. * the resulting file should be set to that
  275. * of the source file.
  276. * @param inputEncoding the encoding used to read the files.
  277. * @param outputEncoding the encoding used to write the files.
  278. * @param project the project instance
  279. *
  280. * @throws IOException if the copying fails
  281. *
  282. * @since Ant 1.6
  283. */
  284. public void copyFile(String sourceFile, String destFile,
  285. FilterSetCollection filters, Vector filterChains,
  286. boolean overwrite, boolean preserveLastModified,
  287. String inputEncoding, String outputEncoding,
  288. Project project)
  289. throws IOException {
  290. copyFile(new File(sourceFile), new File(destFile), filters,
  291. filterChains, overwrite, preserveLastModified,
  292. inputEncoding, outputEncoding, project);
  293. }
  294. /**
  295. * Convienence method to copy a file from a source to a destination.
  296. * No filtering is performed.
  297. *
  298. * @param sourceFile the file to copy from.
  299. * Must not be <code>null</code>.
  300. * @param destFile the file to copy to.
  301. * Must not be <code>null</code>.
  302. *
  303. * @throws IOException if the copying fails
  304. */
  305. public void copyFile(File sourceFile, File destFile) throws IOException {
  306. copyFile(sourceFile, destFile, null, false, false);
  307. }
  308. /**
  309. * Convienence method to copy a file from a source to a destination
  310. * specifying if token filtering must be used.
  311. *
  312. * @param sourceFile the file to copy from.
  313. * Must not be <code>null</code>.
  314. * @param destFile the file to copy to.
  315. * Must not be <code>null</code>.
  316. * @param filters the collection of filters to apply to this copy
  317. *
  318. * @throws IOException if the copying fails
  319. */
  320. public void copyFile(File sourceFile, File destFile, FilterSetCollection filters)
  321. throws IOException {
  322. copyFile(sourceFile, destFile, filters, false, false);
  323. }
  324. /**
  325. * Convienence method to copy a file from a source to a
  326. * destination specifying if token filtering must be used and if
  327. * source files may overwrite newer destination files.
  328. *
  329. * @param sourceFile the file to copy from.
  330. * Must not be <code>null</code>.
  331. * @param destFile the file to copy to.
  332. * Must not be <code>null</code>.
  333. * @param filters the collection of filters to apply to this copy
  334. * @param overwrite Whether or not the destination file should be
  335. * overwritten if it already exists.
  336. *
  337. * @throws IOException if the copying fails
  338. */
  339. public void copyFile(File sourceFile, File destFile, FilterSetCollection filters,
  340. boolean overwrite) throws IOException {
  341. copyFile(sourceFile, destFile, filters, overwrite, false);
  342. }
  343. /**
  344. * Convienence method to copy a file from a source to a
  345. * destination specifying if token filtering must be used, if
  346. * source files may overwrite newer destination files and the
  347. * last modified time of <code>destFile</code> file should be made equal
  348. * to the last modified time of <code>sourceFile</code>.
  349. *
  350. * @param sourceFile the file to copy from.
  351. * Must not be <code>null</code>.
  352. * @param destFile the file to copy to.
  353. * Must not be <code>null</code>.
  354. * @param filters the collection of filters to apply to this copy
  355. * @param overwrite Whether or not the destination file should be
  356. * overwritten if it already exists.
  357. * @param preserveLastModified Whether or not the last modified time of
  358. * the resulting file should be set to that
  359. * of the source file.
  360. *
  361. * @throws IOException if the copying fails
  362. */
  363. public void copyFile(File sourceFile, File destFile, FilterSetCollection filters,
  364. boolean overwrite, boolean preserveLastModified)
  365. throws IOException {
  366. copyFile(sourceFile, destFile, filters, overwrite,
  367. preserveLastModified, null);
  368. }
  369. /**
  370. * Convienence method to copy a file from a source to a
  371. * destination specifying if token filtering must be used, if
  372. * source files may overwrite newer destination files, the last
  373. * modified time of <code>destFile</code> file should be made
  374. * equal to the last modified time of <code>sourceFile</code> and
  375. * which character encoding to assume.
  376. *
  377. * @param sourceFile the file to copy from.
  378. * Must not be <code>null</code>.
  379. * @param destFile the file to copy to.
  380. * Must not be <code>null</code>.
  381. * @param filters the collection of filters to apply to this copy
  382. * @param overwrite Whether or not the destination file should be
  383. * overwritten if it already exists.
  384. * @param preserveLastModified Whether or not the last modified time of
  385. * the resulting file should be set to that
  386. * of the source file.
  387. * @param encoding the encoding used to read and write the files.
  388. *
  389. * @throws IOException if the copying fails
  390. *
  391. * @since Ant 1.5
  392. */
  393. public void copyFile(File sourceFile, File destFile,
  394. FilterSetCollection filters, boolean overwrite,
  395. boolean preserveLastModified, String encoding)
  396. throws IOException {
  397. copyFile(sourceFile, destFile, filters, null, overwrite,
  398. preserveLastModified, encoding, null);
  399. }
  400. /**
  401. * Convienence method to copy a file from a source to a
  402. * destination specifying if token filtering must be used, if
  403. * filter chains must be used, if source files may overwrite
  404. * newer destination files and the last modified time of
  405. * <code>destFile</code> file should be made equal
  406. * to the last modified time of <code>sourceFile</code>.
  407. *
  408. * @param sourceFile the file to copy from.
  409. * Must not be <code>null</code>.
  410. * @param destFile the file to copy to.
  411. * Must not be <code>null</code>.
  412. * @param filters the collection of filters to apply to this copy
  413. * @param filterChains filterChains to apply during the copy.
  414. * @param overwrite Whether or not the destination file should be
  415. * overwritten if it already exists.
  416. * @param preserveLastModified Whether or not the last modified time of
  417. * the resulting file should be set to that
  418. * of the source file.
  419. * @param encoding the encoding used to read and write the files.
  420. * @param project the project instance
  421. *
  422. * @throws IOException if the copying fails
  423. *
  424. * @since Ant 1.5
  425. */
  426. public void copyFile(File sourceFile, File destFile,
  427. FilterSetCollection filters, Vector filterChains,
  428. boolean overwrite, boolean preserveLastModified,
  429. String encoding, Project project)
  430. throws IOException {
  431. copyFile(sourceFile, destFile, filters, filterChains,
  432. overwrite, preserveLastModified, encoding, encoding, project);
  433. }
  434. /**
  435. * Convienence method to copy a file from a source to a
  436. * destination specifying if token filtering must be used, if
  437. * filter chains must be used, if source files may overwrite
  438. * newer destination files and the last modified time of
  439. * <code>destFile</code> file should be made equal
  440. * to the last modified time of <code>sourceFile</code>.
  441. *
  442. * @param sourceFile the file to copy from.
  443. * Must not be <code>null</code>.
  444. * @param destFile the file to copy to.
  445. * Must not be <code>null</code>.
  446. * @param filters the collection of filters to apply to this copy
  447. * @param filterChains filterChains to apply during the copy.
  448. * @param overwrite Whether or not the destination file should be
  449. * overwritten if it already exists.
  450. * @param preserveLastModified Whether or not the last modified time of
  451. * the resulting file should be set to that
  452. * of the source file.
  453. * @param inputEncoding the encoding used to read the files.
  454. * @param outputEncoding the encoding used to write the files.
  455. * @param project the project instance
  456. *
  457. *
  458. * @throws IOException if the copying fails
  459. *
  460. * @since Ant 1.6
  461. */
  462. public void copyFile(File sourceFile, File destFile,
  463. FilterSetCollection filters, Vector filterChains,
  464. boolean overwrite, boolean preserveLastModified,
  465. String inputEncoding, String outputEncoding,
  466. Project project)
  467. throws IOException {
  468. if (overwrite || !destFile.exists()
  469. || destFile.lastModified() < sourceFile.lastModified()) {
  470. if (destFile.exists() && destFile.isFile()) {
  471. destFile.delete();
  472. }
  473. // ensure that parent dir of dest file exists!
  474. // not using getParentFile method to stay 1.1 compat
  475. File parent = getParentFile(destFile);
  476. if (parent != null && !parent.exists()) {
  477. parent.mkdirs();
  478. }
  479. final boolean filterSetsAvailable = (filters != null
  480. && filters.hasFilters());
  481. final boolean filterChainsAvailable = (filterChains != null
  482. && filterChains.size() > 0);
  483. if (filterSetsAvailable) {
  484. BufferedReader in = null;
  485. BufferedWriter out = null;
  486. try {
  487. if (inputEncoding == null) {
  488. in = new BufferedReader(new FileReader(sourceFile));
  489. } else {
  490. InputStreamReader isr
  491. = new InputStreamReader(new FileInputStream(sourceFile),
  492. inputEncoding);
  493. in = new BufferedReader(isr);
  494. }
  495. if (outputEncoding == null) {
  496. out = new BufferedWriter(new FileWriter(destFile));
  497. } else {
  498. OutputStreamWriter osw
  499. = new OutputStreamWriter(new FileOutputStream(destFile),
  500. outputEncoding);
  501. out = new BufferedWriter(osw);
  502. }
  503. if (filterChainsAvailable) {
  504. ChainReaderHelper crh = new ChainReaderHelper();
  505. crh.setBufferSize(8192);
  506. crh.setPrimaryReader(in);
  507. crh.setFilterChains(filterChains);
  508. crh.setProject(project);
  509. Reader rdr = crh.getAssembledReader();
  510. in = new BufferedReader(rdr);
  511. }
  512. LineTokenizer lineTokenizer = new LineTokenizer();
  513. lineTokenizer.setIncludeDelims(true);
  514. String newline = null;
  515. String line = lineTokenizer.getToken(in);
  516. while (line != null) {
  517. if (line.length() == 0) {
  518. // this should not happen, because the lines are
  519. // returned with the end of line delimiter
  520. out.newLine();
  521. } else {
  522. newline = filters.replaceTokens(line);
  523. out.write(newline);
  524. }
  525. line = lineTokenizer.getToken(in);
  526. }
  527. } finally {
  528. if (out != null) {
  529. out.close();
  530. }
  531. if (in != null) {
  532. in.close();
  533. }
  534. }
  535. } else if (filterChainsAvailable
  536. || (inputEncoding != null
  537. && !inputEncoding.equals(outputEncoding))
  538. || (inputEncoding == null && outputEncoding != null)) {
  539. BufferedReader in = null;
  540. BufferedWriter out = null;
  541. try {
  542. if (inputEncoding == null) {
  543. in = new BufferedReader(new FileReader(sourceFile));
  544. } else {
  545. in =
  546. new BufferedReader(
  547. new InputStreamReader(
  548. new FileInputStream(sourceFile),
  549. inputEncoding));
  550. }
  551. if (outputEncoding == null) {
  552. out = new BufferedWriter(new FileWriter(destFile));
  553. } else {
  554. out =
  555. new BufferedWriter(
  556. new OutputStreamWriter(
  557. new FileOutputStream(destFile),
  558. outputEncoding));
  559. }
  560. if (filterChainsAvailable) {
  561. ChainReaderHelper crh = new ChainReaderHelper();
  562. crh.setBufferSize(8192);
  563. crh.setPrimaryReader(in);
  564. crh.setFilterChains(filterChains);
  565. crh.setProject(project);
  566. Reader rdr = crh.getAssembledReader();
  567. in = new BufferedReader(rdr);
  568. }
  569. char[] buffer = new char[1024 * 8];
  570. while (true) {
  571. int nRead = in.read(buffer, 0, buffer.length);
  572. if (nRead == -1) {
  573. break;
  574. }
  575. out.write(buffer, 0, nRead);
  576. }
  577. } finally {
  578. if (out != null) {
  579. out.close();
  580. }
  581. if (in != null) {
  582. in.close();
  583. }
  584. }
  585. } else {
  586. FileInputStream in = null;
  587. FileOutputStream out = null;
  588. try {
  589. in = new FileInputStream(sourceFile);
  590. out = new FileOutputStream(destFile);
  591. byte[] buffer = new byte[8 * 1024];
  592. int count = 0;
  593. do {
  594. out.write(buffer, 0, count);
  595. count = in.read(buffer, 0, buffer.length);
  596. } while (count != -1);
  597. } finally {
  598. if (out != null) {
  599. out.close();
  600. }
  601. if (in != null) {
  602. in.close();
  603. }
  604. }
  605. }
  606. if (preserveLastModified) {
  607. setFileLastModified(destFile, sourceFile.lastModified());
  608. }
  609. }
  610. }
  611. /**
  612. * see whether we have a setLastModified method in File and return it.
  613. *
  614. * @return a method to setLastModified.
  615. */
  616. protected final Method getSetLastModified() {
  617. if (JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_1)) {
  618. return null;
  619. }
  620. synchronized (lockReflection) {
  621. if (setLastModified == null) {
  622. try {
  623. setLastModified =
  624. java.io.File.class.getMethod("setLastModified",
  625. new Class[] {Long.TYPE});
  626. } catch (NoSuchMethodException nse) {
  627. throw new BuildException("File.setlastModified not in JDK > 1.1?",
  628. nse);
  629. }
  630. }
  631. }
  632. return setLastModified;
  633. }
  634. /**
  635. * Calls File.setLastModified(long time) in a Java 1.1 compatible way.
  636. *
  637. * @param file the file whose modified time is to be set
  638. * @param time the time to which the last modified time is to be set.
  639. *
  640. * @throws BuildException if the time cannot be set.
  641. */
  642. public void setFileLastModified(File file, long time)
  643. throws BuildException {
  644. if (JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_1)) {
  645. return;
  646. }
  647. Long[] times = new Long[1];
  648. if (time < 0) {
  649. times[0] = new Long(System.currentTimeMillis());
  650. } else {
  651. times[0] = new Long(time);
  652. }
  653. try {
  654. getSetLastModified().invoke(file, times);
  655. } catch (java.lang.reflect.InvocationTargetException ite) {
  656. Throwable nested = ite.getTargetException();
  657. throw new BuildException("Exception setting the modification time "
  658. + "of " + file, nested);
  659. } catch (Throwable other) {
  660. throw new BuildException("Exception setting the modification time "
  661. + "of " + file, other);
  662. }
  663. }
  664. /**
  665. * Interpret the filename as a file relative to the given file -
  666. * unless the filename already represents an absolute filename.
  667. *
  668. * @param file the "reference" file for relative paths. This
  669. * instance must be an absolute file and must not contain
  670. * "./" or "../" sequences (same for \ instead
  671. * of /). If it is null, this call is equivalent to
  672. * <code>new java.io.File(filename)</code>.
  673. *
  674. * @param filename a file name
  675. *
  676. * @return an absolute file that doesn't contain "./" or
  677. * "../" sequences and uses the correct separator for
  678. * the current platform.
  679. */
  680. public File resolveFile(File file, String filename) {
  681. filename = filename.replace('/', File.separatorChar)
  682. .replace('\\', File.separatorChar);
  683. // deal with absolute files
  684. if (!onNetWare) {
  685. if (filename.startsWith(File.separator)
  686. || (filename.length() >= 2
  687. && Character.isLetter(filename.charAt(0))
  688. && filename.charAt(1) == ':')) {
  689. return normalize(filename);
  690. }
  691. } else {
  692. // the assumption that the : will appear as the second character in
  693. // the path name breaks down when NetWare is a supported platform.
  694. // Netware volumes are of the pattern: "data:\"
  695. int colon = filename.indexOf(":");
  696. if (filename.startsWith(File.separator)
  697. || (colon > -1)) {
  698. return normalize(filename);
  699. }
  700. }
  701. if (file == null) {
  702. return new File(filename);
  703. }
  704. File helpFile = new File(file.getAbsolutePath());
  705. StringTokenizer tok = new StringTokenizer(filename, File.separator);
  706. while (tok.hasMoreTokens()) {
  707. String part = tok.nextToken();
  708. if (part.equals("..")) {
  709. helpFile = getParentFile(helpFile);
  710. if (helpFile == null) {
  711. String msg = "The file or path you specified ("
  712. + filename + ") is invalid relative to "
  713. + file.getPath();
  714. throw new BuildException(msg);
  715. }
  716. } else if (part.equals(".")) {
  717. // Do nothing here
  718. } else {
  719. helpFile = new File(helpFile, part);
  720. }
  721. }
  722. return new File(helpFile.getAbsolutePath());
  723. }
  724. /**
  725. * "normalize" the given absolute path.
  726. *
  727. * <p>This includes:
  728. * <ul>
  729. * <li>Uppercase the drive letter if there is one.</li>
  730. * <li>Remove redundant slashes after the drive spec.</li>
  731. * <li>resolve all ./, .\, ../ and ..\ sequences.</li>
  732. * <li>DOS style paths that start with a drive letter will have
  733. * \ as the separator.</li>
  734. * </ul>
  735. * Unlike <code>File#getCanonicalPath()</code> it specifically doesn't
  736. * resolve symbolic links.
  737. *
  738. * @param path the path to be normalized
  739. * @return the normalized version of the path.
  740. *
  741. * @throws java.lang.NullPointerException if the file path is
  742. * equal to null.
  743. */
  744. public File normalize(String path) {
  745. String orig = path;
  746. path = path.replace('/', File.separatorChar)
  747. .replace('\\', File.separatorChar);
  748. // make sure we are dealing with an absolute path
  749. int colon = path.indexOf(":");
  750. if (!onNetWare) {
  751. if (!path.startsWith(File.separator)
  752. && !(path.length() >= 2
  753. && Character.isLetter(path.charAt(0))
  754. && colon == 1)) {
  755. String msg = path + " is not an absolute path";
  756. throw new BuildException(msg);
  757. }
  758. } else {
  759. if (!path.startsWith(File.separator)
  760. && (colon == -1)) {
  761. String msg = path + " is not an absolute path";
  762. throw new BuildException(msg);
  763. }
  764. }
  765. boolean dosWithDrive = false;
  766. String root = null;
  767. // Eliminate consecutive slashes after the drive spec
  768. if ((!onNetWare && path.length() >= 2
  769. && Character.isLetter(path.charAt(0))
  770. && path.charAt(1) == ':')
  771. || (onNetWare && colon > -1)) {
  772. dosWithDrive = true;
  773. char[] ca = path.replace('/', '\\').toCharArray();
  774. StringBuffer sbRoot = new StringBuffer();
  775. for (int i = 0; i < colon; i++) {
  776. sbRoot.append(Character.toUpperCase(ca[i]));
  777. }
  778. sbRoot.append(':');
  779. if (colon + 1 < path.length()) {
  780. sbRoot.append(File.separatorChar);
  781. }
  782. root = sbRoot.toString();
  783. // Eliminate consecutive slashes after the drive spec
  784. StringBuffer sbPath = new StringBuffer();
  785. for (int i = colon + 1; i < ca.length; i++) {
  786. if ((ca[i] != '\\')
  787. || (ca[i] == '\\' && ca[i - 1] != '\\')) {
  788. sbPath.append(ca[i]);
  789. }
  790. }
  791. path = sbPath.toString().replace('\\', File.separatorChar);
  792. } else {
  793. if (path.length() == 1) {
  794. root = File.separator;
  795. path = "";
  796. } else if (path.charAt(1) == File.separatorChar) {
  797. // UNC drive
  798. root = File.separator + File.separator;
  799. path = path.substring(2);
  800. } else {
  801. root = File.separator;
  802. path = path.substring(1);
  803. }
  804. }
  805. Stack s = new Stack();
  806. s.push(root);
  807. StringTokenizer tok = new StringTokenizer(path, File.separator);
  808. while (tok.hasMoreTokens()) {
  809. String thisToken = tok.nextToken();
  810. if (".".equals(thisToken)) {
  811. continue;
  812. } else if ("..".equals(thisToken)) {
  813. if (s.size() < 2) {
  814. throw new BuildException("Cannot resolve path " + orig);
  815. } else {
  816. s.pop();
  817. }
  818. } else { // plain component
  819. s.push(thisToken);
  820. }
  821. }
  822. StringBuffer sb = new StringBuffer();
  823. for (int i = 0; i < s.size(); i++) {
  824. if (i > 1) {
  825. // not before the filesystem root and not after it, since root
  826. // already contains one
  827. sb.append(File.separatorChar);
  828. }
  829. sb.append(s.elementAt(i));
  830. }
  831. path = sb.toString();
  832. if (dosWithDrive) {
  833. path = path.replace('/', '\\');
  834. }
  835. return new File(path);
  836. }
  837. /**
  838. * Returns a VMS String representation of a <code>File</code> object.
  839. * This is useful since the JVM by default internally converts VMS paths
  840. * to Unix style.
  841. * The returned String is always an absolute path.
  842. *
  843. * @param f The <code>File</code> to get the VMS path for.
  844. * @return The absolute VMS path to <code>f</code>.
  845. */
  846. public String toVMSPath(File f) {
  847. // format: "DEVICE:[DIR.SUBDIR]FILE"
  848. String osPath;
  849. String path = normalize(f.getAbsolutePath()).getPath();
  850. String name = f.getName();
  851. boolean isAbsolute = path.charAt(0) == File.separatorChar;
  852. // treat directories specified using .DIR syntax as files
  853. boolean isDirectory = f.isDirectory() &&
  854. !name.regionMatches(true, name.length() - 4, ".DIR", 0, 4);
  855. String device = null;
  856. StringBuffer directory = null;
  857. String file = null;
  858. int index = 0;
  859. if (isAbsolute) {
  860. index = path.indexOf(File.separatorChar, 1);
  861. if (index == -1) {
  862. return path.substring(1) + ":[000000]";
  863. } else {
  864. device = path.substring(1, index++);
  865. }
  866. }
  867. if (isDirectory) {
  868. directory = new StringBuffer(path.substring(index).
  869. replace(File.separatorChar, '.'));
  870. } else {
  871. int dirEnd =
  872. path.lastIndexOf(File.separatorChar, path.length());
  873. if (dirEnd == -1 || dirEnd < index) {
  874. file = path.substring(index);
  875. } else {
  876. directory = new StringBuffer(path.substring(index, dirEnd).
  877. replace(File.separatorChar, '.'));
  878. index = dirEnd + 1;
  879. if (path.length() > index) {
  880. file = path.substring(index);
  881. }
  882. }
  883. }
  884. if (!isAbsolute && directory != null) {
  885. directory.insert(0, '.');
  886. }
  887. osPath = ((device != null) ? device + ":" : "") +
  888. ((directory != null) ? "[" + directory + "]" : "") +
  889. ((file != null) ? file : "");
  890. return osPath;
  891. }
  892. /**
  893. * Create a temporary file in a given directory.
  894. *
  895. * <p>The file denoted by the returned abstract pathname did not
  896. * exist before this method was invoked, any subsequent invocation
  897. * of this method will yield a different file name.</p>
  898. *
  899. * <p>This method is different to File.createTempFile of JDK 1.2
  900. * as it doesn't create the file itself.
  901. * It uses the location pointed to by java.io.tmpdir
  902. * when the parentDir attribute is
  903. * null.</p>
  904. *
  905. * @param parentDir Directory to create the temporary file in -
  906. * current working directory will be assumed if this parameter is
  907. * null.
  908. *
  909. * @return a File reference to the new temporary file.
  910. * @since ant 1.5
  911. */
  912. public File createTempFile(String prefix, String suffix, File parentDir) {
  913. File result = null;
  914. String parent = System.getProperty("java.io.tmpdir");
  915. if (parentDir != null) {
  916. parent = parentDir.getPath();
  917. }
  918. DecimalFormat fmt = new DecimalFormat("#####");
  919. synchronized (rand) {
  920. do {
  921. result = new File(parent,
  922. prefix + fmt.format(Math.abs(rand.nextInt()))
  923. + suffix);
  924. } while (result.exists());
  925. }
  926. return result;
  927. }
  928. /**
  929. * Compares the contents of two files.
  930. *
  931. * <p>simple but sub-optimal comparision algorithm. written for
  932. * working rather than fast. Better would be a block read into
  933. * buffers followed by long comparisions apart from the final 1-7
  934. * bytes.</p>
  935. *
  936. * @param f1 the file whose content is to be compared.
  937. * @param f2 the other file whose content is to be compared.
  938. *
  939. * @return true if the content of the files is the same.
  940. *
  941. * @throws IOException if the files cannot be read.
  942. *
  943. * @since 1.9
  944. */
  945. public boolean contentEquals(File f1, File f2) throws IOException {
  946. if (f1.exists() != f2.exists()) {
  947. return false;
  948. }
  949. if (!f1.exists()) {
  950. // two not existing files are equal
  951. return true;
  952. }
  953. if (f1.isDirectory() || f2.isDirectory()) {
  954. // don't want to compare directory contents for now
  955. return false;
  956. }
  957. if (fileNameEquals(f1, f2)) {
  958. // same filename => true
  959. return true;
  960. }
  961. if (f1.length() != f2.length()) {
  962. // different size =>false
  963. return false;
  964. }
  965. InputStream in1 = null;
  966. InputStream in2 = null;
  967. try {
  968. in1 = new BufferedInputStream(new FileInputStream(f1));
  969. in2 = new BufferedInputStream(new FileInputStream(f2));
  970. int expectedByte = in1.read();
  971. while (expectedByte != -1) {
  972. if (expectedByte != in2.read()) {
  973. return false;
  974. }
  975. expectedByte = in1.read();
  976. }
  977. if (in2.read() != -1) {
  978. return false;
  979. }
  980. return true;
  981. } finally {
  982. if (in1 != null) {
  983. try {
  984. in1.close();
  985. } catch (IOException e) {
  986. // ignore
  987. }
  988. }
  989. if (in2 != null) {
  990. try {
  991. in2.close();
  992. } catch (IOException e) {
  993. // ignore
  994. }
  995. }
  996. }
  997. }
  998. /**
  999. * Emulation of File.getParentFile for JDK 1.1
  1000. *
  1001. *
  1002. * @param f the file whose parent is required.
  1003. * @return the given file's parent, or null if the file does not have a
  1004. * parent.
  1005. * @since 1.10
  1006. */
  1007. public File getParentFile(File f) {
  1008. if (f != null) {
  1009. String p = f.getParent();
  1010. if (p != null) {
  1011. return new File(p);
  1012. }
  1013. }
  1014. return null;
  1015. }
  1016. /**
  1017. * Read from reader till EOF
  1018. * @param rdr the reader from which to read.
  1019. * @return the contents read out of the given reader
  1020. *
  1021. * @throws IOException if the contents could not be read out from the
  1022. * reader.
  1023. */
  1024. public static final String readFully(Reader rdr) throws IOException {
  1025. return readFully(rdr, 8192);
  1026. }
  1027. /**
  1028. * Read from reader till EOF
  1029. *
  1030. * @param rdr the reader from which to read.
  1031. * @param bufferSize the buffer size to use when reading
  1032. *
  1033. * @return the contents read out of the given reader
  1034. *
  1035. * @throws IOException if the contents could not be read out from the
  1036. * reader.
  1037. */
  1038. public static final String readFully(Reader rdr, int bufferSize)
  1039. throws IOException {
  1040. if (bufferSize <= 0) {
  1041. throw new IllegalArgumentException("Buffer size must be greater "
  1042. + "than 0");
  1043. }
  1044. final char[] buffer = new char[bufferSize];
  1045. int bufferLength = 0;
  1046. String text = null;
  1047. StringBuffer textBuffer = null;
  1048. while (bufferLength != -1) {
  1049. bufferLength = rdr.read(buffer);
  1050. if (bufferLength != -1) {
  1051. if (textBuffer == null) {
  1052. textBuffer = new StringBuffer(
  1053. new String(buffer, 0, bufferLength));
  1054. } else {
  1055. textBuffer.append(new String(buffer, 0, bufferLength));
  1056. }
  1057. }
  1058. }
  1059. if (textBuffer != null) {
  1060. text = textBuffer.toString();
  1061. }
  1062. return text;
  1063. }
  1064. /**
  1065. * Emulation of File.createNewFile for JDK 1.1.
  1066. *
  1067. * <p>This method does <strong>not</strong> guarantee that the
  1068. * operation is atomic.</p>
  1069. *
  1070. * @param f the file to be created
  1071. * @return true if the file did not exist already.
  1072. * @since Ant 1.5
  1073. */
  1074. public boolean createNewFile(File f) throws IOException {
  1075. if (f != null) {
  1076. if (f.exists()) {
  1077. return false;
  1078. }
  1079. FileOutputStream fos = null;
  1080. try {
  1081. fos = new FileOutputStream(f);
  1082. fos.write(new byte[0]);
  1083. } finally {
  1084. if (fos != null) {
  1085. fos.close();
  1086. }
  1087. }
  1088. return true;
  1089. }
  1090. return false;
  1091. }
  1092. /**
  1093. * Checks whether a given file is a symbolic link.
  1094. *
  1095. * <p>It doesn't really test for symbolic links but whether the
  1096. * canonical and absolute paths of the file are identical - this
  1097. * may lead to false positives on some platforms.</p>
  1098. *
  1099. * @param parent the parent directory of the file to test
  1100. * @param name the name of the file to test.
  1101. *
  1102. * @return true if the file is a symbolic link.
  1103. * @since Ant 1.5
  1104. */
  1105. public boolean isSymbolicLink(File parent, String name)
  1106. throws IOException {
  1107. File resolvedParent = new File(parent.getCanonicalPath());
  1108. File toTest = new File(resolvedParent, name);
  1109. return !toTest.getAbsolutePath().equals(toTest.getCanonicalPath());
  1110. }
  1111. /**
  1112. * Removes a leading path from a second path.
  1113. *
  1114. * @param leading The leading path, must not be null, must be absolute.
  1115. * @param path The path to remove from, must not be null, must be absolute.
  1116. *
  1117. * @return path's normalized absolute if it doesn't start with
  1118. * leading, path's path with leading's path removed otherwise.
  1119. *
  1120. * @since Ant 1.5
  1121. */
  1122. public String removeLeadingPath(File leading, File path) {
  1123. String l = normalize(leading.getAbsolutePath()).getAbsolutePath();
  1124. String p = normalize(path.getAbsolutePath()).getAbsolutePath();
  1125. if (l.equals(p)) {
  1126. return "";
  1127. }
  1128. // ensure that l ends with a /
  1129. // so we never think /foo was a parent directory of /foobar
  1130. if (!l.endsWith(File.separator)) {
  1131. l += File.separator;
  1132. }
  1133. if (p.startsWith(l)) {
  1134. return p.substring(l.length());
  1135. } else {
  1136. return p;
  1137. }
  1138. }
  1139. /**
  1140. * Constructs a <code>file:</code> URI that represents the
  1141. * external form of the given pathname.
  1142. *
  1143. * <p>Will be an absolute URI if the given path is absolute.</p>
  1144. *
  1145. * <p>This code doesn't handle non-ASCII characters properly.</p>
  1146. *
  1147. * @param path the path in the local file system
  1148. * @return the URI version of the local path.
  1149. * @since Ant 1.6
  1150. */
  1151. public String toURI(String path) {
  1152. boolean isDir = (new File(path)).isDirectory();
  1153. StringBuffer sb = new StringBuffer("file:");
  1154. // catch exception if normalize thinks this is not an absolute path
  1155. try {
  1156. path = normalize(path).getAbsolutePath();
  1157. sb.append("//");
  1158. // add an extra slash for filesystems with drive-specifiers
  1159. if (!path.startsWith(File.separator)) {
  1160. sb.append("/");
  1161. }
  1162. } catch (BuildException e) {
  1163. // relative path
  1164. }
  1165. path = path.replace('\\', '/');
  1166. CharacterIterator iter = new StringCharacterIterator(path);
  1167. for (char c = iter.first(); c != CharacterIterator.DONE;
  1168. c = iter.next()) {
  1169. if (c < 256 && isSpecial[c]) {
  1170. sb.append('%');
  1171. sb.append(escapedChar1[c]);
  1172. sb.append(escapedChar2[c]);
  1173. } else {
  1174. sb.append(c);
  1175. }
  1176. }
  1177. if (isDir && !path.endsWith("/")) {
  1178. sb.append('/');
  1179. }
  1180. return sb.toString();
  1181. }
  1182. /**
  1183. * Constructs a file path from a <code>file:</code> URI.
  1184. *
  1185. * <p>Will be an absolute path if the given URI is absolute.</p>
  1186. *
  1187. * <p>Swallows '%' that are not followed by two characters,
  1188. * doesn't deal with non-ASCII characters.</p>
  1189. *
  1190. * @param uri the URI designating a file in the local filesystem.
  1191. * @return the local file system path for the file.
  1192. * @since Ant 1.6
  1193. */
  1194. public String fromURI(String uri) {
  1195. String path = Locator.fromURI(uri);
  1196. // catch exception if normalize thinks this is not an absolute path
  1197. try {
  1198. path = normalize(path).getAbsolutePath();
  1199. } catch (BuildException e) {
  1200. // relative path
  1201. }
  1202. return path;
  1203. }
  1204. /**
  1205. * Compares two filenames.
  1206. *
  1207. * <p>Unlike java.io.File#equals this method will try to compare
  1208. * the absolute paths and "normalize" the filenames
  1209. * before comparing them.</p>
  1210. *
  1211. * @param f1 the file whose name is to be compared.
  1212. * @param f2 the other file whose name is to be compared.
  1213. *
  1214. * @return true if the file are for the same file.
  1215. *
  1216. * @since Ant 1.5.3
  1217. */
  1218. public boolean fileNameEquals(File f1, File f2) {
  1219. return normalize(f1.getAbsolutePath())
  1220. .equals(normalize(f2.getAbsolutePath()));
  1221. }
  1222. /**
  1223. * Renames a file, even if that involves crossing file system boundaries.
  1224. *
  1225. * <p>This will remove <code>to</code> (if it exists), ensure that
  1226. * <code>to</code>'s parent directory exists and move
  1227. * <code>from</code>, which involves deleting <code>from</code> as
  1228. * well.</p>
  1229. *
  1230. * @throws IOException if anything bad happens during this
  1231. * process. Note that <code>to</code> may have been deleted
  1232. * already when this happens.
  1233. *
  1234. * @param from the file to move
  1235. * @param to the new file name
  1236. *
  1237. * @since Ant 1.6
  1238. */
  1239. public void rename(File from, File to) throws IOException {
  1240. if (to.exists() && !to.delete()) {
  1241. throw new IOException("Failed to delete " + to
  1242. + " while trying to rename " + from);
  1243. }
  1244. File parent = getParentFile(to);
  1245. if (parent != null && !parent.exists() && !parent.mkdirs()) {
  1246. throw new IOException("Failed to create directory " + parent
  1247. + " while trying to rename " + from);
  1248. }
  1249. if (!from.renameTo(to)) {
  1250. copyFile(from, to);
  1251. if (!from.delete()) {
  1252. throw new IOException("Failed to delete " + from
  1253. + " while trying to rename it.");
  1254. }
  1255. }
  1256. }
  1257. public long getFileTimestampGranularity() {
  1258. if (Os.isFamily("dos")) {
  1259. return FAT_FILE_TIMESTAMP_GRANULARITY;
  1260. } else {
  1261. return 0;
  1262. }
  1263. }
  1264. }