source: release-kits/lirk3/resources/gs3-release-maker/apache-ant-1.6.5/src/main/org/apache/tools/ant/taskdefs/Checksum.java@ 14982

Last change on this file since 14982 was 14982, checked in by oranfry, 16 years ago

initial import of LiRK3

File size: 19.7 KB
Line 
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 */
17package org.apache.tools.ant.taskdefs;
18
19import java.security.DigestInputStream;
20import java.security.MessageDigest;
21import java.security.NoSuchAlgorithmException;
22import java.security.NoSuchProviderException;
23import java.io.File;
24import java.io.FileOutputStream;
25import java.io.FileInputStream;
26import java.io.FileReader;
27import java.io.BufferedReader;
28import java.io.IOException;
29import java.io.InputStreamReader;
30import java.util.HashMap;
31import java.util.Map;
32import java.util.Vector;
33import java.util.Hashtable;
34import java.util.Enumeration;
35import java.util.Set;
36import java.util.Arrays;
37
38import org.apache.tools.ant.BuildException;
39import org.apache.tools.ant.DirectoryScanner;
40import org.apache.tools.ant.Project;
41import org.apache.tools.ant.taskdefs.condition.Condition;
42import org.apache.tools.ant.types.FileSet;
43
44/**
45 * Used to create or verify file checksums.
46 *
47 *
48 * @since Ant 1.5
49 *
50 * @ant.task category="control"
51 */
52public class Checksum extends MatchingTask implements Condition {
53
54 /**
55 * File for which checksum is to be calculated.
56 */
57 private File file = null;
58
59 /**
60 * Root directory in which the checksum files will be written.
61 * If not specified, the checksum files will be written
62 * in the same directory as each file.
63 */
64 private File todir;
65
66 /**
67 * MessageDigest algorithm to be used.
68 */
69 private String algorithm = "MD5";
70 /**
71 * MessageDigest Algorithm provider
72 */
73 private String provider = null;
74 /**
75 * File Extension that is be to used to create or identify
76 * destination file
77 */
78 private String fileext;
79 /**
80 * Holds generated checksum and gets set as a Project Property.
81 */
82 private String property;
83 /**
84 * Holds checksums for all files (both calculated and cached on disk).
85 * Key: java.util.File (source file)
86 * Value: java.lang.String (digest)
87 */
88 private Map allDigests = new HashMap();
89 /**
90 * Holds relative file names for all files (always with a forward slash).
91 * This is used to calculate the total hash.
92 * Key: java.util.File (source file)
93 * Value: java.lang.String (relative file name)
94 */
95 private Map relativeFilePaths = new HashMap();
96 /**
97 * Property where totalChecksum gets set.
98 */
99 private String totalproperty;
100 /**
101 * Whether or not to create a new file.
102 * Defaults to <code>false</code>.
103 */
104 private boolean forceOverwrite;
105 /**
106 * Contains the result of a checksum verification. ("true" or "false")
107 */
108 private String verifyProperty;
109 /**
110 * Vector to hold source file sets.
111 */
112 private Vector filesets = new Vector();
113 /**
114 * Stores SourceFile, DestFile pairs and SourceFile, Property String pairs.
115 */
116 private Hashtable includeFileMap = new Hashtable();
117 /**
118 * Message Digest instance
119 */
120 private MessageDigest messageDigest;
121 /**
122 * is this task being used as a nested condition element?
123 */
124 private boolean isCondition;
125 /**
126 * Size of the read buffer to use.
127 */
128 private int readBufferSize = 8 * 1024;
129
130 /**
131 * Sets the file for which the checksum is to be calculated.
132 */
133 public void setFile(File file) {
134 this.file = file;
135 }
136
137 /**
138 * Sets the root directory where checksum files will be
139 * written/read
140 *
141 * @since Ant 1.6
142 */
143 public void setTodir(File todir) {
144 this.todir = todir;
145 }
146
147 /**
148 * Specifies the algorithm to be used to compute the checksum.
149 * Defaults to "MD5". Other popular algorithms like "SHA" may be used as well.
150 */
151 public void setAlgorithm(String algorithm) {
152 this.algorithm = algorithm;
153 }
154
155 /**
156 * Sets the MessageDigest algorithm provider to be used
157 * to calculate the checksum.
158 */
159 public void setProvider(String provider) {
160 this.provider = provider;
161 }
162
163 /**
164 * Sets the file extension that is be to used to
165 * create or identify destination file.
166 */
167 public void setFileext(String fileext) {
168 this.fileext = fileext;
169 }
170
171 /**
172 * Sets the property to hold the generated checksum.
173 */
174 public void setProperty(String property) {
175 this.property = property;
176 }
177
178 /**
179 * Sets the property to hold the generated total checksum
180 * for all files.
181 *
182 * @since Ant 1.6
183 */
184 public void setTotalproperty(String totalproperty) {
185 this.totalproperty = totalproperty;
186 }
187
188 /**
189 * Sets the verify property. This project property holds
190 * the result of a checksum verification - "true" or "false"
191 */
192 public void setVerifyproperty(String verifyProperty) {
193 this.verifyProperty = verifyProperty;
194 }
195
196 /**
197 * Whether or not to overwrite existing file irrespective of
198 * whether it is newer than
199 * the source file. Defaults to false.
200 */
201 public void setForceOverwrite(boolean forceOverwrite) {
202 this.forceOverwrite = forceOverwrite;
203 }
204
205 /**
206 * The size of the read buffer to use.
207 */
208 public void setReadBufferSize(int size) {
209 this.readBufferSize = size;
210 }
211
212 /**
213 * Files to generate checksums for.
214 */
215 public void addFileset(FileSet set) {
216 filesets.addElement(set);
217 }
218
219 /**
220 * Calculate the checksum(s).
221 */
222 public void execute() throws BuildException {
223 isCondition = false;
224 boolean value = validateAndExecute();
225 if (verifyProperty != null) {
226 getProject().setNewProperty(verifyProperty,
227 new Boolean(value).toString());
228 }
229 }
230
231 /**
232 * Calculate the checksum(s)
233 *
234 * @return Returns true if the checksum verification test passed,
235 * false otherwise.
236 */
237 public boolean eval() throws BuildException {
238 isCondition = true;
239 return validateAndExecute();
240 }
241
242 /**
243 * Validate attributes and get down to business.
244 */
245 private boolean validateAndExecute() throws BuildException {
246 String savedFileExt = fileext;
247
248 if (file == null && filesets.size() == 0) {
249 throw new BuildException(
250 "Specify at least one source - a file or a fileset.");
251 }
252
253 if (file != null && file.exists() && file.isDirectory()) {
254 throw new BuildException(
255 "Checksum cannot be generated for directories");
256 }
257
258 if (file != null && totalproperty != null) {
259 throw new BuildException(
260 "File and Totalproperty cannot co-exist.");
261 }
262
263 if (property != null && fileext != null) {
264 throw new BuildException(
265 "Property and FileExt cannot co-exist.");
266 }
267
268 if (property != null) {
269 if (forceOverwrite) {
270 throw new BuildException(
271 "ForceOverwrite cannot be used when Property is specified");
272 }
273
274 if (file != null) {
275 if (filesets.size() > 0) {
276 throw new BuildException("Multiple files cannot be used "
277 + "when Property is specified");
278 }
279 } else {
280 if (filesets.size() > 1) {
281 throw new BuildException("Multiple files cannot be used "
282 + "when Property is specified");
283 }
284 }
285 }
286
287 if (verifyProperty != null) {
288 isCondition = true;
289 }
290
291 if (verifyProperty != null && forceOverwrite) {
292 throw new BuildException(
293 "VerifyProperty and ForceOverwrite cannot co-exist.");
294 }
295
296 if (isCondition && forceOverwrite) {
297 throw new BuildException("ForceOverwrite cannot be used when "
298 + "conditions are being used.");
299 }
300
301 messageDigest = null;
302 if (provider != null) {
303 try {
304 messageDigest = MessageDigest.getInstance(algorithm, provider);
305 } catch (NoSuchAlgorithmException noalgo) {
306 throw new BuildException(noalgo, getLocation());
307 } catch (NoSuchProviderException noprovider) {
308 throw new BuildException(noprovider, getLocation());
309 }
310 } else {
311 try {
312 messageDigest = MessageDigest.getInstance(algorithm);
313 } catch (NoSuchAlgorithmException noalgo) {
314 throw new BuildException(noalgo, getLocation());
315 }
316 }
317
318 if (messageDigest == null) {
319 throw new BuildException("Unable to create Message Digest",
320 getLocation());
321 }
322
323 if (fileext == null) {
324 fileext = "." + algorithm;
325 } else if (fileext.trim().length() == 0) {
326 throw new BuildException(
327 "File extension when specified must not be an empty string");
328 }
329
330 try {
331 int sizeofFileSet = filesets.size();
332 for (int i = 0; i < sizeofFileSet; i++) {
333 FileSet fs = (FileSet) filesets.elementAt(i);
334 DirectoryScanner ds = fs.getDirectoryScanner(getProject());
335 String[] srcFiles = ds.getIncludedFiles();
336 for (int j = 0; j < srcFiles.length; j++) {
337 File src = new File(fs.getDir(getProject()), srcFiles[j]);
338 if (totalproperty != null || todir != null) {
339 // Use '/' to calculate digest based on file name.
340 // This is required in order to get the same result
341 // on different platforms.
342 String relativePath = srcFiles[j].replace(File.separatorChar, '/');
343 relativeFilePaths.put(src, relativePath);
344 }
345 addToIncludeFileMap(src);
346 }
347 }
348
349 addToIncludeFileMap(file);
350
351 return generateChecksums();
352 } finally {
353 fileext = savedFileExt;
354 includeFileMap.clear();
355 }
356 }
357
358 /**
359 * Add key-value pair to the hashtable upon which
360 * to later operate upon.
361 */
362 private void addToIncludeFileMap(File file) throws BuildException {
363 if (file != null) {
364 if (file.exists()) {
365 if (property == null) {
366 File checksumFile = getChecksumFile(file);
367 if (forceOverwrite || isCondition
368 || (file.lastModified() > checksumFile.lastModified())) {
369 includeFileMap.put(file, checksumFile);
370 } else {
371 log(file + " omitted as " + checksumFile + " is up to date.",
372 Project.MSG_VERBOSE);
373 if (totalproperty != null) {
374 // Read the checksum from disk.
375 String checksum = null;
376 try {
377 BufferedReader diskChecksumReader
378 = new BufferedReader(new FileReader(checksumFile));
379 checksum = diskChecksumReader.readLine();
380 } catch (IOException e) {
381 throw new BuildException("Couldn't read checksum file "
382 + checksumFile, e);
383 }
384 byte[] digest = decodeHex(checksum.toCharArray());
385 allDigests.put(file, digest);
386 }
387 }
388 } else {
389 includeFileMap.put(file, property);
390 }
391 } else {
392 String message = "Could not find file "
393 + file.getAbsolutePath()
394 + " to generate checksum for.";
395 log(message);
396 throw new BuildException(message, getLocation());
397 }
398 }
399 }
400
401 private File getChecksumFile(File file) {
402 File directory;
403 if (todir != null) {
404 // A separate directory was explicitly declared
405 String path = (String) relativeFilePaths.get(file);
406 directory = new File(todir, path).getParentFile();
407 // Create the directory, as it might not exist.
408 directory.mkdirs();
409 } else {
410 // Just use the same directory as the file itself.
411 // This directory will exist
412 directory = file.getParentFile();
413 }
414 File checksumFile = new File(directory, file.getName() + fileext);
415 return checksumFile;
416 }
417
418 /**
419 * Generate checksum(s) using the message digest created earlier.
420 */
421 private boolean generateChecksums() throws BuildException {
422 boolean checksumMatches = true;
423 FileInputStream fis = null;
424 FileOutputStream fos = null;
425 byte[] buf = new byte[readBufferSize];
426 try {
427 for (Enumeration e = includeFileMap.keys(); e.hasMoreElements();) {
428 messageDigest.reset();
429 File src = (File) e.nextElement();
430 if (!isCondition) {
431 log("Calculating " + algorithm + " checksum for " + src, Project.MSG_VERBOSE);
432 }
433 fis = new FileInputStream(src);
434 DigestInputStream dis = new DigestInputStream(fis,
435 messageDigest);
436 while (dis.read(buf, 0, readBufferSize) != -1) {
437 ;
438 }
439 dis.close();
440 fis.close();
441 fis = null;
442 byte[] fileDigest = messageDigest.digest ();
443 if (totalproperty != null) {
444 allDigests.put(src, fileDigest);
445 }
446 String checksum = createDigestString(fileDigest);
447 //can either be a property name string or a file
448 Object destination = includeFileMap.get(src);
449 if (destination instanceof java.lang.String) {
450 String prop = (String) destination;
451 if (isCondition) {
452 checksumMatches
453 = checksumMatches && checksum.equals(property);
454 } else {
455 getProject().setNewProperty(prop, checksum);
456 }
457 } else if (destination instanceof java.io.File) {
458 if (isCondition) {
459 File existingFile = (File) destination;
460 if (existingFile.exists()) {
461 fis = new FileInputStream(existingFile);
462 InputStreamReader isr = new InputStreamReader(fis);
463 BufferedReader br = new BufferedReader(isr);
464 String suppliedChecksum = br.readLine();
465 fis.close();
466 fis = null;
467 br.close();
468 isr.close();
469 checksumMatches = checksumMatches
470 && checksum.equals(suppliedChecksum);
471 } else {
472 checksumMatches = false;
473 }
474 } else {
475 File dest = (File) destination;
476 fos = new FileOutputStream(dest);
477 fos.write(checksum.getBytes());
478 fos.close();
479 fos = null;
480 }
481 }
482 }
483 if (totalproperty != null) {
484 // Calculate the total checksum
485 // Convert the keys (source files) into a sorted array.
486 Set keys = allDigests.keySet();
487 Object[] keyArray = keys.toArray();
488 // File is Comparable, so sorting is trivial
489 Arrays.sort(keyArray);
490 // Loop over the checksums and generate a total hash.
491 messageDigest.reset();
492 for (int i = 0; i < keyArray.length; i++) {
493 File src = (File) keyArray[i];
494
495 // Add the digest for the file content
496 byte[] digest = (byte[]) allDigests.get(src);
497 messageDigest.update(digest);
498
499 // Add the file path
500 String fileName = (String) relativeFilePaths.get(src);
501 messageDigest.update(fileName.getBytes());
502 }
503 String totalChecksum = createDigestString(messageDigest.digest());
504 getProject().setNewProperty(totalproperty, totalChecksum);
505 }
506 } catch (Exception e) {
507 throw new BuildException(e, getLocation());
508 } finally {
509 if (fis != null) {
510 try {
511 fis.close();
512 } catch (IOException e) {
513 // ignore
514 }
515 }
516 if (fos != null) {
517 try {
518 fos.close();
519 } catch (IOException e) {
520 // ignore
521 }
522 }
523 }
524 return checksumMatches;
525 }
526
527 private String createDigestString(byte[] fileDigest) {
528 StringBuffer checksumSb = new StringBuffer();
529 for (int i = 0; i < fileDigest.length; i++) {
530 String hexStr = Integer.toHexString(0x00ff & fileDigest[i]);
531 if (hexStr.length() < 2) {
532 checksumSb.append("0");
533 }
534 checksumSb.append(hexStr);
535 }
536 return checksumSb.toString();
537 }
538
539 /**
540 * Converts an array of characters representing hexadecimal values into an
541 * array of bytes of those same values. The returned array will be half the
542 * length of the passed array, as it takes two characters to represent any
543 * given byte. An exception is thrown if the passed char array has an odd
544 * number of elements.
545 *
546 * NOTE: This code is copied from jakarta-commons codec.
547 */
548 public static byte[] decodeHex(char[] data) throws BuildException {
549 int l = data.length;
550
551 if ((l & 0x01) != 0) {
552 throw new BuildException("odd number of characters.");
553 }
554
555 byte[] out = new byte[l >> 1];
556
557 // two characters form the hex value.
558 for (int i = 0, j = 0; j < l; i++) {
559 int f = Character.digit(data[j++], 16) << 4;
560 f = f | Character.digit(data[j++], 16);
561 out[i] = (byte) (f & 0xFF);
562 }
563
564 return out;
565 }
566}
Note: See TracBrowser for help on using the repository browser.