You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

ResourceUtils.java 23 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. /*
  2. * Copyright 2003-2005 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.File;
  19. import java.io.Reader;
  20. import java.io.InputStream;
  21. import java.io.IOException;
  22. import java.io.OutputStream;
  23. import java.io.BufferedReader;
  24. import java.io.BufferedWriter;
  25. import java.io.InputStreamReader;
  26. import java.io.OutputStreamWriter;
  27. import java.io.BufferedInputStream;
  28. import java.util.Arrays;
  29. import java.util.Vector;
  30. import java.util.Iterator;
  31. import org.apache.tools.ant.Project;
  32. import org.apache.tools.ant.ProjectComponent;
  33. import org.apache.tools.ant.filters.util.ChainReaderHelper;
  34. import org.apache.tools.ant.types.Resource;
  35. import org.apache.tools.ant.types.TimeComparison;
  36. import org.apache.tools.ant.types.ResourceFactory;
  37. import org.apache.tools.ant.types.ResourceCollection;
  38. import org.apache.tools.ant.types.FilterSetCollection;
  39. import org.apache.tools.ant.types.resources.Union;
  40. import org.apache.tools.ant.types.resources.Restrict;
  41. import org.apache.tools.ant.types.resources.Resources;
  42. import org.apache.tools.ant.types.resources.Touchable;
  43. import org.apache.tools.ant.types.resources.selectors.Or;
  44. import org.apache.tools.ant.types.resources.selectors.And;
  45. import org.apache.tools.ant.types.resources.selectors.Not;
  46. import org.apache.tools.ant.types.resources.selectors.Date;
  47. import org.apache.tools.ant.types.resources.selectors.Type;
  48. import org.apache.tools.ant.types.resources.selectors.Exists;
  49. import org.apache.tools.ant.types.resources.selectors.ResourceSelector;
  50. import org.apache.tools.ant.types.selectors.SelectorUtils;
  51. /**
  52. * This class provides utility methods to process Resources.
  53. *
  54. * @since Ant 1.5.2
  55. */
  56. public class ResourceUtils {
  57. private static class Outdated implements ResourceSelector {
  58. private Resource control;
  59. private long granularity;
  60. private Outdated(Resource control, long granularity) {
  61. this.control = control;
  62. this.granularity = granularity;
  63. }
  64. public boolean isSelected(Resource r) {
  65. return SelectorUtils.isOutOfDate(control, r, granularity);
  66. }
  67. }
  68. /** Utilities used for file operations */
  69. private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
  70. private static final ResourceSelector NOT_EXISTS = new Not(new Exists());
  71. /**
  72. * Tells which source files should be reprocessed based on the
  73. * last modification date of target files.
  74. * @param logTo where to send (more or less) interesting output.
  75. * @param source array of resources bearing relative path and last
  76. * modification date.
  77. * @param mapper filename mapper indicating how to find the target
  78. * files.
  79. * @param targets object able to map as a resource a relative path
  80. * at <b>destination</b>.
  81. * @return array containing the source files which need to be
  82. * copied or processed, because the targets are out of date or do
  83. * not exist.
  84. */
  85. public static Resource[] selectOutOfDateSources(ProjectComponent logTo,
  86. Resource[] source,
  87. FileNameMapper mapper,
  88. ResourceFactory targets) {
  89. return selectOutOfDateSources(logTo, source, mapper, targets,
  90. FILE_UTILS.getFileTimestampGranularity());
  91. }
  92. /**
  93. * Tells which source files should be reprocessed based on the
  94. * last modification date of target files.
  95. * @param logTo where to send (more or less) interesting output.
  96. * @param source array of resources bearing relative path and last
  97. * modification date.
  98. * @param mapper filename mapper indicating how to find the target
  99. * files.
  100. * @param targets object able to map as a resource a relative path
  101. * at <b>destination</b>.
  102. * @param granularity The number of milliseconds leeway to give
  103. * before deciding a target is out of date.
  104. * @return array containing the source files which need to be
  105. * copied or processed, because the targets are out of date or do
  106. * not exist.
  107. * @since Ant 1.6.2
  108. */
  109. public static Resource[] selectOutOfDateSources(ProjectComponent logTo,
  110. Resource[] source,
  111. FileNameMapper mapper,
  112. ResourceFactory targets,
  113. long granularity) {
  114. Union u = new Union();
  115. u.addAll(Arrays.asList(source));
  116. ResourceCollection rc
  117. = selectOutOfDateSources(logTo, u, mapper, targets, granularity);
  118. return rc.size() == 0 ? new Resource[0] : ((Union) rc).listResources();
  119. }
  120. /**
  121. * Tells which sources should be reprocessed based on the
  122. * last modification date of targets.
  123. * @param logTo where to send (more or less) interesting output.
  124. * @param source ResourceCollection.
  125. * @param mapper filename mapper indicating how to find the target Resources.
  126. * @param targets object able to map a relative path as a Resource.
  127. * @param granularity The number of milliseconds leeway to give
  128. * before deciding a target is out of date.
  129. * @return ResourceCollection.
  130. * @since Ant 1.7
  131. */
  132. public static ResourceCollection selectOutOfDateSources(ProjectComponent logTo,
  133. ResourceCollection source,
  134. FileNameMapper mapper,
  135. ResourceFactory targets,
  136. long granularity) {
  137. if (source.size() == 0) {
  138. logTo.log("No sources found.", Project.MSG_VERBOSE);
  139. return Resources.NONE;
  140. }
  141. source = Union.getInstance(source);
  142. logFuture(logTo, source, granularity);
  143. Union result = new Union();
  144. for (Iterator iter = source.iterator(); iter.hasNext();) {
  145. Resource sr = (Resource) iter.next();
  146. String[] targetnames = mapper.mapFileName(
  147. sr.getName().replace('/', File.separatorChar));
  148. if (targetnames == null || targetnames.length == 0) {
  149. logTo.log(sr.getName()
  150. + " skipped - don\'t know how to handle it",
  151. Project.MSG_VERBOSE);
  152. continue;
  153. }
  154. Union targetColl = new Union();
  155. for (int i = 0; i < targetnames.length; i++) {
  156. targetColl.add(targets.getResource(
  157. targetnames[i].replace(File.separatorChar, '/')));
  158. }
  159. //find the out-of-date targets:
  160. Restrict r = new Restrict();
  161. r.add(new And(new ResourceSelector[] {Type.FILE, new Or(
  162. new ResourceSelector[] {NOT_EXISTS, new Outdated(sr, granularity)})}));
  163. r.add(targetColl);
  164. if (r.size() > 0) {
  165. result.add(sr);
  166. Resource t = (Resource) (r.iterator().next());
  167. logTo.log(sr.getName() + " added as " + t.getName()
  168. + (t.isExists() ? " is outdated." : " doesn\'t exist."),
  169. Project.MSG_VERBOSE);
  170. continue;
  171. }
  172. //log uptodateness of all targets:
  173. logTo.log(sr.getName()
  174. + " omitted as " + targetColl.toString()
  175. + (targetColl.size() == 1 ? " is" : " are ")
  176. + " up to date.", Project.MSG_VERBOSE);
  177. }
  178. return result;
  179. }
  180. /**
  181. * Convenience method to copy content from one Resource to another.
  182. * No filtering is performed.
  183. *
  184. * @param source the Resource to copy from.
  185. * Must not be <code>null</code>.
  186. * @param dest the Resource to copy to.
  187. * Must not be <code>null</code>.
  188. *
  189. * @throws IOException if the copying fails.
  190. *
  191. * @since Ant 1.7
  192. */
  193. public static void copyResource(Resource source, Resource dest) throws IOException {
  194. copyResource(source, dest, null);
  195. }
  196. /**
  197. * Convenience method to copy content from one Resource to another.
  198. * No filtering is performed.
  199. *
  200. * @param source the Resource to copy from.
  201. * Must not be <code>null</code>.
  202. * @param dest the Resource to copy to.
  203. * Must not be <code>null</code>.
  204. * @param project the project instance.
  205. *
  206. * @throws IOException if the copying fails.
  207. *
  208. * @since Ant 1.7
  209. */
  210. public static void copyResource(Resource source, Resource dest, Project project)
  211. throws IOException {
  212. copyResource(source, dest, null, null, false,
  213. false, null, null, project);
  214. }
  215. /**
  216. * Convenience method to copy content from one Resource to another
  217. * specifying whether token filtering must be used, whether filter chains
  218. * must be used, whether newer destination files may be overwritten and
  219. * whether the last modified time of <code>dest</code> file should be made
  220. * equal to the last modified time of <code>source</code>.
  221. *
  222. * @param source the Resource to copy from.
  223. * Must not be <code>null</code>.
  224. * @param dest the Resource to copy to.
  225. * Must not be <code>null</code>.
  226. * @param filters the collection of filters to apply to this copy.
  227. * @param filterChains filterChains to apply during the copy.
  228. * @param overwrite Whether or not the destination Resource should be
  229. * overwritten if it already exists.
  230. * @param preserveLastModified Whether or not the last modified time of
  231. * the destination Resource should be set to that
  232. * of the source.
  233. * @param inputEncoding the encoding used to read the files.
  234. * @param outputEncoding the encoding used to write the files.
  235. * @param project the project instance.
  236. *
  237. * @throws IOException if the copying fails.
  238. *
  239. * @since Ant 1.7
  240. */
  241. public static void copyResource(Resource source, Resource dest,
  242. FilterSetCollection filters, Vector filterChains,
  243. boolean overwrite, boolean preserveLastModified,
  244. String inputEncoding, String outputEncoding,
  245. Project project)
  246. throws IOException {
  247. if (!overwrite) {
  248. long slm = source.getLastModified();
  249. if (dest.isExists() && slm != 0
  250. && dest.getLastModified() > slm) {
  251. return;
  252. }
  253. }
  254. final boolean filterSetsAvailable = (filters != null
  255. && filters.hasFilters());
  256. final boolean filterChainsAvailable = (filterChains != null
  257. && filterChains.size() > 0);
  258. if (filterSetsAvailable) {
  259. BufferedReader in = null;
  260. BufferedWriter out = null;
  261. try {
  262. InputStreamReader isr = null;
  263. if (inputEncoding == null) {
  264. isr = new InputStreamReader(source.getInputStream());
  265. } else {
  266. isr = new InputStreamReader(source.getInputStream(),
  267. inputEncoding);
  268. }
  269. in = new BufferedReader(isr);
  270. OutputStreamWriter osw = null;
  271. if (outputEncoding == null) {
  272. osw = new OutputStreamWriter(dest.getOutputStream());
  273. } else {
  274. osw = new OutputStreamWriter(dest.getOutputStream(),
  275. outputEncoding);
  276. }
  277. out = new BufferedWriter(osw);
  278. if (filterChainsAvailable) {
  279. ChainReaderHelper crh = new ChainReaderHelper();
  280. crh.setBufferSize(FileUtils.BUF_SIZE);
  281. crh.setPrimaryReader(in);
  282. crh.setFilterChains(filterChains);
  283. crh.setProject(project);
  284. Reader rdr = crh.getAssembledReader();
  285. in = new BufferedReader(rdr);
  286. }
  287. LineTokenizer lineTokenizer = new LineTokenizer();
  288. lineTokenizer.setIncludeDelims(true);
  289. String newline = null;
  290. String line = lineTokenizer.getToken(in);
  291. while (line != null) {
  292. if (line.length() == 0) {
  293. // this should not happen, because the lines are
  294. // returned with the end of line delimiter
  295. out.newLine();
  296. } else {
  297. newline = filters.replaceTokens(line);
  298. out.write(newline);
  299. }
  300. line = lineTokenizer.getToken(in);
  301. }
  302. } finally {
  303. FileUtils.close(out);
  304. FileUtils.close(in);
  305. }
  306. } else if (filterChainsAvailable
  307. || (inputEncoding != null
  308. && !inputEncoding.equals(outputEncoding))
  309. || (inputEncoding == null && outputEncoding != null)) {
  310. BufferedReader in = null;
  311. BufferedWriter out = null;
  312. try {
  313. InputStreamReader isr = null;
  314. if (inputEncoding == null) {
  315. isr = new InputStreamReader(source.getInputStream());
  316. } else {
  317. isr = new InputStreamReader(source.getInputStream(),
  318. inputEncoding);
  319. }
  320. in = new BufferedReader(isr);
  321. OutputStreamWriter osw = null;
  322. if (outputEncoding == null) {
  323. osw = new OutputStreamWriter(dest.getOutputStream());
  324. } else {
  325. osw = new OutputStreamWriter(dest.getOutputStream(),
  326. outputEncoding);
  327. }
  328. out = new BufferedWriter(osw);
  329. if (filterChainsAvailable) {
  330. ChainReaderHelper crh = new ChainReaderHelper();
  331. crh.setBufferSize(FileUtils.BUF_SIZE);
  332. crh.setPrimaryReader(in);
  333. crh.setFilterChains(filterChains);
  334. crh.setProject(project);
  335. Reader rdr = crh.getAssembledReader();
  336. in = new BufferedReader(rdr);
  337. }
  338. char[] buffer = new char[FileUtils.BUF_SIZE];
  339. while (true) {
  340. int nRead = in.read(buffer, 0, buffer.length);
  341. if (nRead == -1) {
  342. break;
  343. }
  344. out.write(buffer, 0, nRead);
  345. }
  346. } finally {
  347. FileUtils.close(out);
  348. FileUtils.close(in);
  349. }
  350. } else {
  351. InputStream in = null;
  352. OutputStream out = null;
  353. try {
  354. in = source.getInputStream();
  355. out = dest.getOutputStream();
  356. byte[] buffer = new byte[FileUtils.BUF_SIZE];
  357. int count = 0;
  358. do {
  359. out.write(buffer, 0, count);
  360. count = in.read(buffer, 0, buffer.length);
  361. } while (count != -1);
  362. } finally {
  363. FileUtils.close(out);
  364. FileUtils.close(in);
  365. }
  366. }
  367. if (preserveLastModified && dest instanceof Touchable) {
  368. setLastModified((Touchable) dest, source.getLastModified());
  369. }
  370. }
  371. /**
  372. * Set the last modified time of an object implementing
  373. * org.apache.tools.ant.types.resources.Touchable .
  374. *
  375. * @param t the Touchable whose modified time is to be set.
  376. * @param time the time to which the last modified time is to be set.
  377. * if this is -1, the current time is used.
  378. * @since Ant 1.7
  379. */
  380. public static void setLastModified(Touchable t, long time) {
  381. t.touch((time < 0) ? System.currentTimeMillis() : time);
  382. }
  383. /**
  384. * Compares the contents of two Resources.
  385. *
  386. * @param r1 the Resource whose content is to be compared.
  387. * @param r2 the other Resource whose content is to be compared.
  388. * @param text true if the content is to be treated as text and
  389. * differences in kind of line break are to be ignored.
  390. *
  391. * @return true if the content of the Resources is the same.
  392. *
  393. * @throws IOException if the Resources cannot be read.
  394. * @since Ant 1.7
  395. */
  396. public static boolean contentEquals(Resource r1, Resource r2, boolean text) throws IOException {
  397. if (r1.isExists() != r2.isExists()) {
  398. return false;
  399. }
  400. if (!r1.isExists()) {
  401. // two not existing files are equal
  402. return true;
  403. }
  404. // should the following two be switched? If r1 and r2 refer to the same file,
  405. // isn't their content equal regardless of whether that file is a directory?
  406. if (r1.isDirectory() || r2.isDirectory()) {
  407. // don't want to compare directory contents for now
  408. return false;
  409. }
  410. if (r1.equals(r2)) {
  411. return true;
  412. }
  413. if (!text && r1.getSize() != r2.getSize()) {
  414. return false;
  415. }
  416. return compareContent(r1, r2, text) == 0;
  417. }
  418. /**
  419. * Compare the content of two Resources. A nonexistent Resource's
  420. * content is "less than" that of an existing Resource; a directory-type
  421. * Resource's content is "less than" that of a file-type Resource.
  422. * @param r1 the Resource whose content is to be compared.
  423. * @param r2 the other Resource whose content is to be compared.
  424. * @param text true if the content is to be treated as text and
  425. * differences in kind of line break are to be ignored.
  426. * @return a negative integer, zero, or a positive integer as the first
  427. * argument is less than, equal to, or greater than the second.
  428. * @throws IOException if the Resources cannot be read.
  429. * @since Ant 1.7
  430. */
  431. public static int compareContent(Resource r1, Resource r2, boolean text) throws IOException {
  432. if (r1.equals(r2)) {
  433. return 0;
  434. }
  435. boolean e1 = r1.isExists();
  436. boolean e2 = r2.isExists();
  437. if (!(e1 || e2)) {
  438. return 0;
  439. }
  440. if (e1 != e2) {
  441. return e1 ? 1 : -1;
  442. }
  443. boolean d1 = r1.isDirectory();
  444. boolean d2 = r2.isDirectory();
  445. if (d1 && d2) {
  446. return 0;
  447. }
  448. if (d1 || d2) {
  449. return d1 ? -1 : 1;
  450. }
  451. return text ? textCompare(r1, r2) : binaryCompare(r1, r2);
  452. }
  453. /**
  454. * Binary compares the contents of two Resources.
  455. * <p>
  456. * simple but sub-optimal comparision algorithm. written for working
  457. * rather than fast. Better would be a block read into buffers followed
  458. * by long comparisions apart from the final 1-7 bytes.
  459. * </p>
  460. *
  461. * @param r1 the Resource whose content is to be compared.
  462. * @param r2 the other Resource whose content is to be compared.
  463. * @return a negative integer, zero, or a positive integer as the first
  464. * argument is less than, equal to, or greater than the second.
  465. * @throws IOException if the Resources cannot be read.
  466. * @since Ant 1.7
  467. */
  468. private static int binaryCompare(Resource r1, Resource r2) throws IOException {
  469. InputStream in1 = null;
  470. InputStream in2 = null;
  471. try {
  472. in1 = new BufferedInputStream(r1.getInputStream());
  473. in2 = new BufferedInputStream(r2.getInputStream());
  474. for (int b1 = in1.read(); b1 != -1; b1 = in1.read()) {
  475. int b2 = in2.read();
  476. if (b1 != b2) {
  477. return b1 > b2 ? 1 : -1;
  478. }
  479. }
  480. return in2.read() == -1 ? 0 : -1;
  481. } finally {
  482. FileUtils.close(in1);
  483. FileUtils.close(in2);
  484. }
  485. }
  486. /**
  487. * Text compares the contents of two Resources.
  488. * Ignores different kinds of line endings.
  489. * @param r1 the Resource whose content is to be compared.
  490. * @param r2 the other Resource whose content is to be compared.
  491. * @return a negative integer, zero, or a positive integer as the first
  492. * argument is less than, equal to, or greater than the second.
  493. * @throws IOException if the Resources cannot be read.
  494. * @since Ant 1.7
  495. */
  496. private static int textCompare(Resource r1, Resource r2) throws IOException {
  497. BufferedReader in1 = null;
  498. BufferedReader in2 = null;
  499. try {
  500. in1 = new BufferedReader(new InputStreamReader(r1.getInputStream()));
  501. in2 = new BufferedReader(new InputStreamReader(r2.getInputStream()));
  502. String expected = in1.readLine();
  503. while (expected != null) {
  504. String actual = in2.readLine();
  505. if (!expected.equals(actual)) {
  506. return expected.compareTo(actual);
  507. }
  508. expected = in1.readLine();
  509. }
  510. return in2.readLine() == null ? 0 : -1;
  511. } finally {
  512. FileUtils.close(in1);
  513. FileUtils.close(in2);
  514. }
  515. }
  516. /**
  517. * Log which Resources (if any) have been modified in the future.
  518. * @param logTo the ProjectComponent to do the logging.
  519. * @param rc the collection of Resources to check.
  520. * @param long the timestamp granularity to use.
  521. * @since Ant 1.7
  522. */
  523. private static void logFuture(ProjectComponent logTo,
  524. ResourceCollection rc, long granularity) {
  525. long now = System.currentTimeMillis() + granularity;
  526. Date sel = new Date();
  527. sel.setMillis(now);
  528. sel.setWhen(TimeComparison.AFTER);
  529. Restrict future = new Restrict();
  530. future.add(sel);
  531. future.add(rc);
  532. for (Iterator iter = future.iterator(); iter.hasNext();) {
  533. logTo.log("Warning: " + ((Resource) iter.next()).getName()
  534. + " modified in the future.", Project.MSG_WARN);
  535. }
  536. }
  537. }