001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.tools.ant.util;
019:
020: import java.io.File;
021: import java.io.Reader;
022: import java.io.InputStream;
023: import java.io.IOException;
024: import java.io.OutputStream;
025: import java.io.BufferedReader;
026: import java.io.BufferedWriter;
027: import java.io.InputStreamReader;
028: import java.io.OutputStreamWriter;
029: import java.io.BufferedInputStream;
030: import java.util.Arrays;
031: import java.util.Vector;
032: import java.util.Iterator;
033:
034: import org.apache.tools.ant.Project;
035: import org.apache.tools.ant.ProjectComponent;
036: import org.apache.tools.ant.filters.util.ChainReaderHelper;
037: import org.apache.tools.ant.types.Resource;
038: import org.apache.tools.ant.types.TimeComparison;
039: import org.apache.tools.ant.types.ResourceFactory;
040: import org.apache.tools.ant.types.ResourceCollection;
041: import org.apache.tools.ant.types.FilterSetCollection;
042: import org.apache.tools.ant.types.resources.Union;
043: import org.apache.tools.ant.types.resources.Restrict;
044: import org.apache.tools.ant.types.resources.Resources;
045: import org.apache.tools.ant.types.resources.Touchable;
046: import org.apache.tools.ant.types.resources.selectors.Or;
047: import org.apache.tools.ant.types.resources.selectors.And;
048: import org.apache.tools.ant.types.resources.selectors.Not;
049: import org.apache.tools.ant.types.resources.selectors.Date;
050: import org.apache.tools.ant.types.resources.selectors.Type;
051: import org.apache.tools.ant.types.resources.selectors.Exists;
052: import org.apache.tools.ant.types.resources.selectors.ResourceSelector;
053: import org.apache.tools.ant.types.selectors.SelectorUtils;
054:
055: // CheckStyle:HideUtilityClassConstructorCheck OFF - bc
056:
057: /**
058: * This class provides utility methods to process Resources.
059: *
060: * @since Ant 1.5.2
061: */
062: public class ResourceUtils {
063:
064: private static final class Outdated implements ResourceSelector {
065: private Resource control;
066: private long granularity;
067:
068: private Outdated(Resource control, long granularity) {
069: this .control = control;
070: this .granularity = granularity;
071: }
072:
073: public boolean isSelected(Resource r) {
074: return SelectorUtils.isOutOfDate(control, r, granularity);
075: }
076: }
077:
078: /** Utilities used for file operations */
079: private static final FileUtils FILE_UTILS = FileUtils
080: .getFileUtils();
081:
082: private static final ResourceSelector NOT_EXISTS = new Not(
083: new Exists());
084:
085: /**
086: * Tells which source files should be reprocessed based on the
087: * last modification date of target files.
088: * @param logTo where to send (more or less) interesting output.
089: * @param source array of resources bearing relative path and last
090: * modification date.
091: * @param mapper filename mapper indicating how to find the target
092: * files.
093: * @param targets object able to map as a resource a relative path
094: * at <b>destination</b>.
095: * @return array containing the source files which need to be
096: * copied or processed, because the targets are out of date or do
097: * not exist.
098: */
099: public static Resource[] selectOutOfDateSources(
100: ProjectComponent logTo, Resource[] source,
101: FileNameMapper mapper, ResourceFactory targets) {
102: return selectOutOfDateSources(logTo, source, mapper, targets,
103: FILE_UTILS.getFileTimestampGranularity());
104: }
105:
106: /**
107: * Tells which source files should be reprocessed based on the
108: * last modification date of target files.
109: * @param logTo where to send (more or less) interesting output.
110: * @param source array of resources bearing relative path and last
111: * modification date.
112: * @param mapper filename mapper indicating how to find the target
113: * files.
114: * @param targets object able to map as a resource a relative path
115: * at <b>destination</b>.
116: * @param granularity The number of milliseconds leeway to give
117: * before deciding a target is out of date.
118: * @return array containing the source files which need to be
119: * copied or processed, because the targets are out of date or do
120: * not exist.
121: * @since Ant 1.6.2
122: */
123: public static Resource[] selectOutOfDateSources(
124: ProjectComponent logTo, Resource[] source,
125: FileNameMapper mapper, ResourceFactory targets,
126: long granularity) {
127: Union u = new Union();
128: u.addAll(Arrays.asList(source));
129: ResourceCollection rc = selectOutOfDateSources(logTo, u,
130: mapper, targets, granularity);
131: return rc.size() == 0 ? new Resource[0] : ((Union) rc)
132: .listResources();
133: }
134:
135: /**
136: * Tells which sources should be reprocessed based on the
137: * last modification date of targets.
138: * @param logTo where to send (more or less) interesting output.
139: * @param source ResourceCollection.
140: * @param mapper filename mapper indicating how to find the target Resources.
141: * @param targets object able to map a relative path as a Resource.
142: * @param granularity The number of milliseconds leeway to give
143: * before deciding a target is out of date.
144: * @return ResourceCollection.
145: * @since Ant 1.7
146: */
147: public static ResourceCollection selectOutOfDateSources(
148: ProjectComponent logTo, ResourceCollection source,
149: FileNameMapper mapper, ResourceFactory targets,
150: long granularity) {
151: if (source.size() == 0) {
152: logTo.log("No sources found.", Project.MSG_VERBOSE);
153: return Resources.NONE;
154: }
155: source = Union.getInstance(source);
156: logFuture(logTo, source, granularity);
157:
158: Union result = new Union();
159: for (Iterator iter = source.iterator(); iter.hasNext();) {
160: Resource sr = (Resource) iter.next();
161: String srName = sr.getName();
162: srName = srName == null ? srName : srName.replace('/',
163: File.separatorChar);
164:
165: String[] targetnames = null;
166: try {
167: targetnames = mapper.mapFileName(srName);
168: } catch (Exception e) {
169: logTo.log("Caught " + e + " mapping resource " + sr,
170: Project.MSG_VERBOSE);
171: }
172: if (targetnames == null || targetnames.length == 0) {
173: logTo.log(sr
174: + " skipped - don\'t know how to handle it",
175: Project.MSG_VERBOSE);
176: continue;
177: }
178: Union targetColl = new Union();
179: for (int i = 0; i < targetnames.length; i++) {
180: targetColl.add(targets.getResource(targetnames[i]
181: .replace(File.separatorChar, '/')));
182: }
183: //find the out-of-date targets:
184: Restrict r = new Restrict();
185: r.add(new And(new ResourceSelector[] {
186: Type.FILE,
187: new Or(new ResourceSelector[] { NOT_EXISTS,
188: new Outdated(sr, granularity) }) }));
189: r.add(targetColl);
190: if (r.size() > 0) {
191: result.add(sr);
192: Resource t = (Resource) (r.iterator().next());
193: logTo.log(sr.getName()
194: + " added as "
195: + t.getName()
196: + (t.isExists() ? " is outdated."
197: : " doesn\'t exist."),
198: Project.MSG_VERBOSE);
199: continue;
200: }
201: //log uptodateness of all targets:
202: logTo.log(sr.getName() + " omitted as "
203: + targetColl.toString()
204: + (targetColl.size() == 1 ? " is" : " are ")
205: + " up to date.", Project.MSG_VERBOSE);
206: }
207: return result;
208: }
209:
210: /**
211: * Convenience method to copy content from one Resource to another.
212: * No filtering is performed.
213: *
214: * @param source the Resource to copy from.
215: * Must not be <code>null</code>.
216: * @param dest the Resource to copy to.
217: * Must not be <code>null</code>.
218: *
219: * @throws IOException if the copying fails.
220: *
221: * @since Ant 1.7
222: */
223: public static void copyResource(Resource source, Resource dest)
224: throws IOException {
225: copyResource(source, dest, null);
226: }
227:
228: /**
229: * Convenience method to copy content from one Resource to another.
230: * No filtering is performed.
231: *
232: * @param source the Resource to copy from.
233: * Must not be <code>null</code>.
234: * @param dest the Resource to copy to.
235: * Must not be <code>null</code>.
236: * @param project the project instance.
237: *
238: * @throws IOException if the copying fails.
239: *
240: * @since Ant 1.7
241: */
242: public static void copyResource(Resource source, Resource dest,
243: Project project) throws IOException {
244: copyResource(source, dest, null, null, false, false, null,
245: null, project);
246: }
247:
248: // CheckStyle:ParameterNumberCheck OFF - bc
249: /**
250: * Convenience method to copy content from one Resource to another
251: * specifying whether token filtering must be used, whether filter chains
252: * must be used, whether newer destination files may be overwritten and
253: * whether the last modified time of <code>dest</code> file should be made
254: * equal to the last modified time of <code>source</code>.
255: *
256: * @param source the Resource to copy from.
257: * Must not be <code>null</code>.
258: * @param dest the Resource to copy to.
259: * Must not be <code>null</code>.
260: * @param filters the collection of filters to apply to this copy.
261: * @param filterChains filterChains to apply during the copy.
262: * @param overwrite Whether or not the destination Resource should be
263: * overwritten if it already exists.
264: * @param preserveLastModified Whether or not the last modified time of
265: * the destination Resource should be set to that
266: * of the source.
267: * @param inputEncoding the encoding used to read the files.
268: * @param outputEncoding the encoding used to write the files.
269: * @param project the project instance.
270: *
271: * @throws IOException if the copying fails.
272: *
273: * @since Ant 1.7
274: */
275: public static void copyResource(Resource source, Resource dest,
276: FilterSetCollection filters, Vector filterChains,
277: boolean overwrite, boolean preserveLastModified,
278: String inputEncoding, String outputEncoding, Project project)
279: throws IOException {
280: if (!overwrite) {
281: long slm = source.getLastModified();
282: if (dest.isExists() && slm != 0
283: && dest.getLastModified() > slm) {
284: return;
285: }
286: }
287: final boolean filterSetsAvailable = (filters != null && filters
288: .hasFilters());
289: final boolean filterChainsAvailable = (filterChains != null && filterChains
290: .size() > 0);
291: if (filterSetsAvailable) {
292: BufferedReader in = null;
293: BufferedWriter out = null;
294: try {
295: InputStreamReader isr = null;
296: if (inputEncoding == null) {
297: isr = new InputStreamReader(source.getInputStream());
298: } else {
299: isr = new InputStreamReader(
300: source.getInputStream(), inputEncoding);
301: }
302: in = new BufferedReader(isr);
303: OutputStreamWriter osw = null;
304: if (outputEncoding == null) {
305: osw = new OutputStreamWriter(dest.getOutputStream());
306: } else {
307: osw = new OutputStreamWriter(
308: dest.getOutputStream(), outputEncoding);
309: }
310: out = new BufferedWriter(osw);
311: if (filterChainsAvailable) {
312: ChainReaderHelper crh = new ChainReaderHelper();
313: crh.setBufferSize(FileUtils.BUF_SIZE);
314: crh.setPrimaryReader(in);
315: crh.setFilterChains(filterChains);
316: crh.setProject(project);
317: Reader rdr = crh.getAssembledReader();
318: in = new BufferedReader(rdr);
319: }
320: LineTokenizer lineTokenizer = new LineTokenizer();
321: lineTokenizer.setIncludeDelims(true);
322: String newline = null;
323: String line = lineTokenizer.getToken(in);
324: while (line != null) {
325: if (line.length() == 0) {
326: // this should not happen, because the lines are
327: // returned with the end of line delimiter
328: out.newLine();
329: } else {
330: newline = filters.replaceTokens(line);
331: out.write(newline);
332: }
333: line = lineTokenizer.getToken(in);
334: }
335: } finally {
336: FileUtils.close(out);
337: FileUtils.close(in);
338: }
339: } else if (filterChainsAvailable
340: || (inputEncoding != null && !inputEncoding
341: .equals(outputEncoding))
342: || (inputEncoding == null && outputEncoding != null)) {
343: BufferedReader in = null;
344: BufferedWriter out = null;
345: try {
346: InputStreamReader isr = null;
347: if (inputEncoding == null) {
348: isr = new InputStreamReader(source.getInputStream());
349: } else {
350: isr = new InputStreamReader(
351: source.getInputStream(), inputEncoding);
352: }
353: in = new BufferedReader(isr);
354: OutputStreamWriter osw = null;
355: if (outputEncoding == null) {
356: osw = new OutputStreamWriter(dest.getOutputStream());
357: } else {
358: osw = new OutputStreamWriter(
359: dest.getOutputStream(), outputEncoding);
360: }
361: out = new BufferedWriter(osw);
362: if (filterChainsAvailable) {
363: ChainReaderHelper crh = new ChainReaderHelper();
364: crh.setBufferSize(FileUtils.BUF_SIZE);
365: crh.setPrimaryReader(in);
366: crh.setFilterChains(filterChains);
367: crh.setProject(project);
368: Reader rdr = crh.getAssembledReader();
369: in = new BufferedReader(rdr);
370: }
371: char[] buffer = new char[FileUtils.BUF_SIZE];
372: while (true) {
373: int nRead = in.read(buffer, 0, buffer.length);
374: if (nRead == -1) {
375: break;
376: }
377: out.write(buffer, 0, nRead);
378: }
379: } finally {
380: FileUtils.close(out);
381: FileUtils.close(in);
382: }
383: } else {
384: InputStream in = null;
385: OutputStream out = null;
386: try {
387: in = source.getInputStream();
388: out = dest.getOutputStream();
389:
390: byte[] buffer = new byte[FileUtils.BUF_SIZE];
391: int count = 0;
392: do {
393: out.write(buffer, 0, count);
394: count = in.read(buffer, 0, buffer.length);
395: } while (count != -1);
396: } finally {
397: FileUtils.close(out);
398: FileUtils.close(in);
399: }
400: }
401: if (preserveLastModified && dest instanceof Touchable) {
402: setLastModified((Touchable) dest, source.getLastModified());
403: }
404: }
405:
406: // CheckStyle:ParameterNumberCheck ON
407:
408: /**
409: * Set the last modified time of an object implementing
410: * org.apache.tools.ant.types.resources.Touchable .
411: *
412: * @param t the Touchable whose modified time is to be set.
413: * @param time the time to which the last modified time is to be set.
414: * if this is -1, the current time is used.
415: * @since Ant 1.7
416: */
417: public static void setLastModified(Touchable t, long time) {
418: t.touch((time < 0) ? System.currentTimeMillis() : time);
419: }
420:
421: /**
422: * Compares the contents of two Resources.
423: *
424: * @param r1 the Resource whose content is to be compared.
425: * @param r2 the other Resource whose content is to be compared.
426: * @param text true if the content is to be treated as text and
427: * differences in kind of line break are to be ignored.
428: *
429: * @return true if the content of the Resources is the same.
430: *
431: * @throws IOException if the Resources cannot be read.
432: * @since Ant 1.7
433: */
434: public static boolean contentEquals(Resource r1, Resource r2,
435: boolean text) throws IOException {
436: if (r1.isExists() != r2.isExists()) {
437: return false;
438: }
439: if (!r1.isExists()) {
440: // two not existing files are equal
441: return true;
442: }
443: // should the following two be switched? If r1 and r2 refer to the same file,
444: // isn't their content equal regardless of whether that file is a directory?
445: if (r1.isDirectory() || r2.isDirectory()) {
446: // don't want to compare directory contents for now
447: return false;
448: }
449: if (r1.equals(r2)) {
450: return true;
451: }
452: if (!text && r1.getSize() != r2.getSize()) {
453: return false;
454: }
455: return compareContent(r1, r2, text) == 0;
456: }
457:
458: /**
459: * Compare the content of two Resources. A nonexistent Resource's
460: * content is "less than" that of an existing Resource; a directory-type
461: * Resource's content is "less than" that of a file-type Resource.
462: * @param r1 the Resource whose content is to be compared.
463: * @param r2 the other Resource whose content is to be compared.
464: * @param text true if the content is to be treated as text and
465: * differences in kind of line break are to be ignored.
466: * @return a negative integer, zero, or a positive integer as the first
467: * argument is less than, equal to, or greater than the second.
468: * @throws IOException if the Resources cannot be read.
469: * @since Ant 1.7
470: */
471: public static int compareContent(Resource r1, Resource r2,
472: boolean text) throws IOException {
473: if (r1.equals(r2)) {
474: return 0;
475: }
476: boolean e1 = r1.isExists();
477: boolean e2 = r2.isExists();
478: if (!(e1 || e2)) {
479: return 0;
480: }
481: if (e1 != e2) {
482: return e1 ? 1 : -1;
483: }
484: boolean d1 = r1.isDirectory();
485: boolean d2 = r2.isDirectory();
486: if (d1 && d2) {
487: return 0;
488: }
489: if (d1 || d2) {
490: return d1 ? -1 : 1;
491: }
492: return text ? textCompare(r1, r2) : binaryCompare(r1, r2);
493: }
494:
495: /**
496: * Binary compares the contents of two Resources.
497: * <p>
498: * simple but sub-optimal comparision algorithm. written for working
499: * rather than fast. Better would be a block read into buffers followed
500: * by long comparisions apart from the final 1-7 bytes.
501: * </p>
502: *
503: * @param r1 the Resource whose content is to be compared.
504: * @param r2 the other Resource whose content is to be compared.
505: * @return a negative integer, zero, or a positive integer as the first
506: * argument is less than, equal to, or greater than the second.
507: * @throws IOException if the Resources cannot be read.
508: * @since Ant 1.7
509: */
510: private static int binaryCompare(Resource r1, Resource r2)
511: throws IOException {
512: InputStream in1 = null;
513: InputStream in2 = null;
514: try {
515: in1 = new BufferedInputStream(r1.getInputStream());
516: in2 = new BufferedInputStream(r2.getInputStream());
517:
518: for (int b1 = in1.read(); b1 != -1; b1 = in1.read()) {
519: int b2 = in2.read();
520: if (b1 != b2) {
521: return b1 > b2 ? 1 : -1;
522: }
523: }
524: return in2.read() == -1 ? 0 : -1;
525: } finally {
526: FileUtils.close(in1);
527: FileUtils.close(in2);
528: }
529: }
530:
531: /**
532: * Text compares the contents of two Resources.
533: * Ignores different kinds of line endings.
534: * @param r1 the Resource whose content is to be compared.
535: * @param r2 the other Resource whose content is to be compared.
536: * @return a negative integer, zero, or a positive integer as the first
537: * argument is less than, equal to, or greater than the second.
538: * @throws IOException if the Resources cannot be read.
539: * @since Ant 1.7
540: */
541: private static int textCompare(Resource r1, Resource r2)
542: throws IOException {
543: BufferedReader in1 = null;
544: BufferedReader in2 = null;
545: try {
546: in1 = new BufferedReader(new InputStreamReader(r1
547: .getInputStream()));
548: in2 = new BufferedReader(new InputStreamReader(r2
549: .getInputStream()));
550:
551: String expected = in1.readLine();
552: while (expected != null) {
553: String actual = in2.readLine();
554: if (!expected.equals(actual)) {
555: return expected.compareTo(actual);
556: }
557: expected = in1.readLine();
558: }
559: return in2.readLine() == null ? 0 : -1;
560: } finally {
561: FileUtils.close(in1);
562: FileUtils.close(in2);
563: }
564: }
565:
566: /**
567: * Log which Resources (if any) have been modified in the future.
568: * @param logTo the ProjectComponent to do the logging.
569: * @param rc the collection of Resources to check.
570: * @param granularity the timestamp granularity to use.
571: * @since Ant 1.7
572: */
573: private static void logFuture(ProjectComponent logTo,
574: ResourceCollection rc, long granularity) {
575: long now = System.currentTimeMillis() + granularity;
576: Date sel = new Date();
577: sel.setMillis(now);
578: sel.setWhen(TimeComparison.AFTER);
579: Restrict future = new Restrict();
580: future.add(sel);
581: future.add(rc);
582: for (Iterator iter = future.iterator(); iter.hasNext();) {
583: logTo.log("Warning: " + ((Resource) iter.next()).getName()
584: + " modified in the future.", Project.MSG_WARN);
585: }
586: }
587:
588: }
|