KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tools > ant > taskdefs > Checksum


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */

18 package org.apache.tools.ant.taskdefs;
19
20 import java.security.DigestInputStream JavaDoc;
21 import java.security.MessageDigest JavaDoc;
22 import java.security.NoSuchAlgorithmException JavaDoc;
23 import java.security.NoSuchProviderException JavaDoc;
24 import java.io.File JavaDoc;
25 import java.io.FileOutputStream JavaDoc;
26 import java.io.FileInputStream JavaDoc;
27 import java.io.FileReader JavaDoc;
28 import java.io.BufferedReader JavaDoc;
29 import java.io.IOException JavaDoc;
30 import java.util.HashMap JavaDoc;
31 import java.util.Map JavaDoc;
32 import java.util.Iterator JavaDoc;
33 import java.util.Hashtable JavaDoc;
34 import java.util.Enumeration JavaDoc;
35 import java.util.Set JavaDoc;
36 import java.util.Arrays JavaDoc;
37 import java.text.MessageFormat JavaDoc;
38 import java.text.ParseException JavaDoc;
39
40 import org.apache.tools.ant.BuildException;
41 import org.apache.tools.ant.Project;
42 import org.apache.tools.ant.taskdefs.condition.Condition;
43 import org.apache.tools.ant.types.EnumeratedAttribute;
44 import org.apache.tools.ant.types.FileSet;
45 import org.apache.tools.ant.types.ResourceCollection;
46 import org.apache.tools.ant.types.resources.Union;
47 import org.apache.tools.ant.types.resources.Restrict;
48 import org.apache.tools.ant.types.resources.FileResource;
49 import org.apache.tools.ant.types.resources.selectors.Type;
50 import org.apache.tools.ant.util.FileUtils;
51 import org.apache.tools.ant.util.StringUtils;
52
53 /**
54  * Used to create or verify file checksums.
55  *
56  * @since Ant 1.5
57  *
58  * @ant.task category="control"
59  */

60 public class Checksum extends MatchingTask implements Condition {
61     private static class FileUnion extends Restrict {
62         private Union u;
63         FileUnion() {
64             u = new Union();
65             super.add(u);
66             super.add(Type.FILE);
67         }
68         public void add(ResourceCollection rc) {
69             u.add(rc);
70         }
71     }
72
73     /**
74      * File for which checksum is to be calculated.
75      */

76     private File JavaDoc file = null;
77
78     /**
79      * Root directory in which the checksum files will be written.
80      * If not specified, the checksum files will be written
81      * in the same directory as each file.
82      */

83     private File JavaDoc todir;
84
85     /**
86      * MessageDigest algorithm to be used.
87      */

88     private String JavaDoc algorithm = "MD5";
89     /**
90      * MessageDigest Algorithm provider
91      */

92     private String JavaDoc provider = null;
93     /**
94      * File Extension that is be to used to create or identify
95      * destination file
96      */

97     private String JavaDoc fileext;
98     /**
99      * Holds generated checksum and gets set as a Project Property.
100      */

101     private String JavaDoc property;
102     /**
103      * Holds checksums for all files (both calculated and cached on disk).
104      * Key: java.util.File (source file)
105      * Value: java.lang.String (digest)
106      */

107     private Map JavaDoc allDigests = new HashMap JavaDoc();
108     /**
109      * Holds relative file names for all files (always with a forward slash).
110      * This is used to calculate the total hash.
111      * Key: java.util.File (source file)
112      * Value: java.lang.String (relative file name)
113      */

114     private Map JavaDoc relativeFilePaths = new HashMap JavaDoc();
115     /**
116      * Property where totalChecksum gets set.
117      */

118     private String JavaDoc totalproperty;
119     /**
120      * Whether or not to create a new file.
121      * Defaults to <code>false</code>.
122      */

123     private boolean forceOverwrite;
124     /**
125      * Contains the result of a checksum verification. ("true" or "false")
126      */

127     private String JavaDoc verifyProperty;
128     /**
129      * Resource Collection.
130      */

131     private FileUnion resources = null;
132     /**
133      * Stores SourceFile, DestFile pairs and SourceFile, Property String pairs.
134      */

135     private Hashtable JavaDoc includeFileMap = new Hashtable JavaDoc();
136     /**
137      * Message Digest instance
138      */

139     private MessageDigest JavaDoc messageDigest;
140     /**
141      * is this task being used as a nested condition element?
142      */

143     private boolean isCondition;
144     /**
145      * Size of the read buffer to use.
146      */

147     private int readBufferSize = 8 * 1024;
148
149     /**
150      * Formater for the checksum file.
151      */

152     private MessageFormat JavaDoc format = FormatElement.getDefault().getFormat();
153
154     /**
155      * Sets the file for which the checksum is to be calculated.
156      * @param file a <code>File</code> value
157      */

158     public void setFile(File JavaDoc file) {
159         this.file = file;
160     }
161
162     /**
163      * Sets the root directory where checksum files will be
164      * written/read
165      * @param todir the directory to write to
166      * @since Ant 1.6
167      */

168     public void setTodir(File JavaDoc todir) {
169         this.todir = todir;
170     }
171
172     /**
173      * Specifies the algorithm to be used to compute the checksum.
174      * Defaults to "MD5". Other popular algorithms like "SHA" may be used as well.
175      * @param algorithm a <code>String</code> value
176      */

177     public void setAlgorithm(String JavaDoc algorithm) {
178         this.algorithm = algorithm;
179     }
180
181     /**
182      * Sets the MessageDigest algorithm provider to be used
183      * to calculate the checksum.
184      * @param provider a <code>String</code> value
185      */

186     public void setProvider(String JavaDoc provider) {
187         this.provider = provider;
188     }
189
190     /**
191      * Sets the file extension that is be to used to
192      * create or identify destination file.
193      * @param fileext a <code>String</code> value
194      */

195     public void setFileext(String JavaDoc fileext) {
196         this.fileext = fileext;
197     }
198
199     /**
200      * Sets the property to hold the generated checksum.
201      * @param property a <code>String</code> value
202      */

203     public void setProperty(String JavaDoc property) {
204         this.property = property;
205     }
206
207     /**
208      * Sets the property to hold the generated total checksum
209      * for all files.
210      * @param totalproperty a <code>String</code> value
211      *
212      * @since Ant 1.6
213      */

214     public void setTotalproperty(String JavaDoc totalproperty) {
215         this.totalproperty = totalproperty;
216     }
217
218     /**
219      * Sets the verify property. This project property holds
220      * the result of a checksum verification - "true" or "false"
221      * @param verifyProperty a <code>String</code> value
222      */

223     public void setVerifyproperty(String JavaDoc verifyProperty) {
224         this.verifyProperty = verifyProperty;
225     }
226
227     /**
228      * Whether or not to overwrite existing file irrespective of
229      * whether it is newer than
230      * the source file. Defaults to false.
231      * @param forceOverwrite a <code>boolean</code> value
232      */

233     public void setForceOverwrite(boolean forceOverwrite) {
234         this.forceOverwrite = forceOverwrite;
235     }
236
237     /**
238      * The size of the read buffer to use.
239      * @param size an <code>int</code> value
240      */

241     public void setReadBufferSize(int size) {
242         this.readBufferSize = size;
243     }
244
245     /**
246      * Select the in/output pattern via a well know format name.
247      * @param e an <code>enumerated</code> value
248      *
249      * @since 1.7.0
250      */

251     public void setFormat(FormatElement e) {
252         format = e.getFormat();
253     }
254
255     /**
256      * Specify the pattern to use as a MessageFormat pattern.
257      *
258      * <p>{0} gets replaced by the checksum, {1} by the filename.</p>
259      * @param p a <code>String</code> value
260      *
261      * @since 1.7.0
262      */

263     public void setPattern(String JavaDoc p) {
264         format = new MessageFormat JavaDoc(p);
265     }
266
267     /**
268      * Files to generate checksums for.
269      * @param set a fileset of files to generate checksums for.
270      */

271     public void addFileset(FileSet set) {
272         add(set);
273     }
274
275     /**
276      * Add a resource collection.
277      * @param rc the ResourceCollection to add.
278      */

279     public void add(ResourceCollection rc) {
280         if (rc == null) {
281             return;
282         }
283         resources = (resources == null) ? new FileUnion() : resources;
284         resources.add(rc);
285     }
286
287     /**
288      * Calculate the checksum(s).
289      * @throws BuildException on error
290      */

291     public void execute() throws BuildException {
292         isCondition = false;
293         boolean value = validateAndExecute();
294         if (verifyProperty != null) {
295             getProject().setNewProperty(
296                 verifyProperty,
297                 (value ? Boolean.TRUE.toString() : Boolean.FALSE.toString()));
298         }
299     }
300
301     /**
302      * Calculate the checksum(s)
303      *
304      * @return Returns true if the checksum verification test passed,
305      * false otherwise.
306      * @throws BuildException on error
307      */

308     public boolean eval() throws BuildException {
309         isCondition = true;
310         return validateAndExecute();
311     }
312
313     /**
314      * Validate attributes and get down to business.
315      */

316     private boolean validateAndExecute() throws BuildException {
317         String JavaDoc savedFileExt = fileext;
318
319         if (file == null && (resources == null || resources.size() == 0)) {
320             throw new BuildException(
321                 "Specify at least one source - a file or a resource collection.");
322         }
323         if (!(resources == null || resources.isFilesystemOnly())) {
324             throw new BuildException("Can only calculate checksums for file-based resources.");
325         }
326         if (file != null && file.exists() && file.isDirectory()) {
327             throw new BuildException("Checksum cannot be generated for directories");
328         }
329         if (file != null && totalproperty != null) {
330             throw new BuildException("File and Totalproperty cannot co-exist.");
331         }
332         if (property != null && fileext != null) {
333             throw new BuildException("Property and FileExt cannot co-exist.");
334         }
335         if (property != null) {
336             if (forceOverwrite) {
337                 throw new BuildException(
338                     "ForceOverwrite cannot be used when Property is specified");
339             }
340             int ct = 0;
341             if (resources != null) {
342                 ct += resources.size();
343             }
344             if (file != null) {
345                 ct++;
346             }
347             if (ct > 1) {
348                 throw new BuildException(
349                     "Multiple files cannot be used when Property is specified");
350             }
351         }
352         if (verifyProperty != null) {
353             isCondition = true;
354         }
355         if (verifyProperty != null && forceOverwrite) {
356             throw new BuildException("VerifyProperty and ForceOverwrite cannot co-exist.");
357         }
358         if (isCondition && forceOverwrite) {
359             throw new BuildException(
360                 "ForceOverwrite cannot be used when conditions are being used.");
361         }
362         messageDigest = null;
363         if (provider != null) {
364             try {
365                 messageDigest = MessageDigest.getInstance(algorithm, provider);
366             } catch (NoSuchAlgorithmException JavaDoc noalgo) {
367                 throw new BuildException(noalgo, getLocation());
368             } catch (NoSuchProviderException JavaDoc noprovider) {
369                 throw new BuildException(noprovider, getLocation());
370             }
371         } else {
372             try {
373                 messageDigest = MessageDigest.getInstance(algorithm);
374             } catch (NoSuchAlgorithmException JavaDoc noalgo) {
375                 throw new BuildException(noalgo, getLocation());
376             }
377         }
378         if (messageDigest == null) {
379             throw new BuildException("Unable to create Message Digest", getLocation());
380         }
381         if (fileext == null) {
382             fileext = "." + algorithm;
383         } else if (fileext.trim().length() == 0) {
384             throw new BuildException("File extension when specified must not be an empty string");
385         }
386         try {
387             if (resources != null) {
388                 for (Iterator JavaDoc i = resources.iterator(); i.hasNext();) {
389                     FileResource fr = (FileResource) i.next();
390                     File JavaDoc src = fr.getFile();
391                     if (totalproperty != null || todir != null) {
392                         // Use '/' to calculate digest based on file name.
393
// This is required in order to get the same result
394
// on different platforms.
395
relativeFilePaths.put(src, fr.getName().replace(File.separatorChar, '/'));
396                     }
397                     addToIncludeFileMap(src);
398                 }
399             }
400             if (file != null) {
401                 if (totalproperty != null || todir != null) {
402                     relativeFilePaths.put(
403                         file, file.getName().replace(File.separatorChar, '/'));
404                 }
405                 addToIncludeFileMap(file);
406             }
407             return generateChecksums();
408         } finally {
409             fileext = savedFileExt;
410             includeFileMap.clear();
411         }
412     }
413
414     /**
415      * Add key-value pair to the hashtable upon which
416      * to later operate upon.
417      */

418     private void addToIncludeFileMap(File JavaDoc file) throws BuildException {
419         if (file.exists()) {
420             if (property == null) {
421                 File JavaDoc checksumFile = getChecksumFile(file);
422                 if (forceOverwrite || isCondition
423                     || (file.lastModified() > checksumFile.lastModified())) {
424                     includeFileMap.put(file, checksumFile);
425                 } else {
426                     log(file + " omitted as " + checksumFile + " is up to date.",
427                         Project.MSG_VERBOSE);
428                     if (totalproperty != null) {
429                         // Read the checksum from disk.
430
String JavaDoc checksum = readChecksum(checksumFile);
431                         byte[] digest = decodeHex(checksum.toCharArray());
432                         allDigests.put(file, digest);
433                     }
434                 }
435             } else {
436                 includeFileMap.put(file, property);
437             }
438         } else {
439             String JavaDoc message = "Could not find file "
440                 + file.getAbsolutePath()
441                 + " to generate checksum for.";
442             log(message);
443             throw new BuildException(message, getLocation());
444         }
445     }
446
447     private File JavaDoc getChecksumFile(File JavaDoc file) {
448         File JavaDoc directory;
449         if (todir != null) {
450             // A separate directory was explicitly declared
451
String JavaDoc path = (String JavaDoc) relativeFilePaths.get(file);
452             if (path == null) {
453                 //bug 37386. this should not occur, but it has, once.
454
throw new BuildException(
455                     "Internal error: "
456                     + "relativeFilePaths could not match file"
457                     + file + "\n"
458                     + "please file a bug report on this");
459             }
460             directory = new File JavaDoc(todir, path).getParentFile();
461             // Create the directory, as it might not exist.
462
directory.mkdirs();
463         } else {
464             // Just use the same directory as the file itself.
465
// This directory will exist
466
directory = file.getParentFile();
467         }
468         File JavaDoc checksumFile = new File JavaDoc(directory, file.getName() + fileext);
469         return checksumFile;
470     }
471
472     /**
473      * Generate checksum(s) using the message digest created earlier.
474      */

475     private boolean generateChecksums() throws BuildException {
476         boolean checksumMatches = true;
477         FileInputStream JavaDoc fis = null;
478         FileOutputStream JavaDoc fos = null;
479         byte[] buf = new byte[readBufferSize];
480         try {
481             for (Enumeration JavaDoc e = includeFileMap.keys(); e.hasMoreElements();) {
482                 messageDigest.reset();
483                 File JavaDoc src = (File JavaDoc) e.nextElement();
484                 if (!isCondition) {
485                     log("Calculating " + algorithm + " checksum for " + src, Project.MSG_VERBOSE);
486                 }
487                 fis = new FileInputStream JavaDoc(src);
488                 DigestInputStream JavaDoc dis = new DigestInputStream JavaDoc(fis,
489                                                               messageDigest);
490                 while (dis.read(buf, 0, readBufferSize) != -1) {
491                     // Empty statement
492
}
493                 dis.close();
494                 fis.close();
495                 fis = null;
496                 byte[] fileDigest = messageDigest.digest ();
497                 if (totalproperty != null) {
498                     allDigests.put(src, fileDigest);
499                 }
500                 String JavaDoc checksum = createDigestString(fileDigest);
501                 //can either be a property name string or a file
502
Object JavaDoc destination = includeFileMap.get(src);
503                 if (destination instanceof java.lang.String JavaDoc) {
504                     String JavaDoc prop = (String JavaDoc) destination;
505                     if (isCondition) {
506                         checksumMatches
507                             = checksumMatches && checksum.equals(property);
508                     } else {
509                         getProject().setNewProperty(prop, checksum);
510                     }
511                 } else if (destination instanceof java.io.File JavaDoc) {
512                     if (isCondition) {
513                         File JavaDoc existingFile = (File JavaDoc) destination;
514                         if (existingFile.exists()) {
515                             try {
516                                 String JavaDoc suppliedChecksum =
517                                     readChecksum(existingFile);
518                                 checksumMatches = checksumMatches
519                                     && checksum.equals(suppliedChecksum);
520                             } catch (BuildException be) {
521                                 // file is on wrong format, swallow
522
checksumMatches = false;
523                             }
524                         } else {
525                             checksumMatches = false;
526                         }
527                     } else {
528                         File JavaDoc dest = (File JavaDoc) destination;
529                         fos = new FileOutputStream JavaDoc(dest);
530                         fos.write(format.format(new Object JavaDoc[] {
531                                                     checksum,
532                                                     src.getName(),
533                                                 }).getBytes());
534                         fos.write(StringUtils.LINE_SEP.getBytes());
535                         fos.close();
536                         fos = null;
537                     }
538                 }
539             }
540             if (totalproperty != null) {
541                 // Calculate the total checksum
542
// Convert the keys (source files) into a sorted array.
543
Set JavaDoc keys = allDigests.keySet();
544                 Object JavaDoc[] keyArray = keys.toArray();
545                 // File is Comparable, so sorting is trivial
546
Arrays.sort(keyArray);
547                 // Loop over the checksums and generate a total hash.
548
messageDigest.reset();
549                 for (int i = 0; i < keyArray.length; i++) {
550                     File JavaDoc src = (File JavaDoc) keyArray[i];
551
552                     // Add the digest for the file content
553
byte[] digest = (byte[]) allDigests.get(src);
554                     messageDigest.update(digest);
555
556                     // Add the file path
557
String JavaDoc fileName = (String JavaDoc) relativeFilePaths.get(src);
558                     messageDigest.update(fileName.getBytes());
559                 }
560                 String JavaDoc totalChecksum = createDigestString(messageDigest.digest());
561                 getProject().setNewProperty(totalproperty, totalChecksum);
562             }
563         } catch (Exception JavaDoc e) {
564             throw new BuildException(e, getLocation());
565         } finally {
566             FileUtils.close(fis);
567             FileUtils.close(fos);
568         }
569         return checksumMatches;
570     }
571
572     private String JavaDoc createDigestString(byte[] fileDigest) {
573         StringBuffer JavaDoc checksumSb = new StringBuffer JavaDoc();
574         for (int i = 0; i < fileDigest.length; i++) {
575             String JavaDoc hexStr = Integer.toHexString(0x00ff & fileDigest[i]);
576             if (hexStr.length() < 2) {
577                 checksumSb.append("0");
578             }
579             checksumSb.append(hexStr);
580         }
581         return checksumSb.toString();
582     }
583
584     /**
585      * Converts an array of characters representing hexadecimal values into an
586      * array of bytes of those same values. The returned array will be half the
587      * length of the passed array, as it takes two characters to represent any
588      * given byte. An exception is thrown if the passed char array has an odd
589      * number of elements.
590      *
591      * NOTE: This code is copied from jakarta-commons codec.
592      * @param data an array of characters representing hexadecimal values
593      * @return the converted array of bytes
594      * @throws BuildException on error
595      */

596     public static byte[] decodeHex(char[] data) throws BuildException {
597         int l = data.length;
598
599         if ((l & 0x01) != 0) {
600             throw new BuildException("odd number of characters.");
601         }
602
603         byte[] out = new byte[l >> 1];
604
605         // two characters form the hex value.
606
for (int i = 0, j = 0; j < l; i++) {
607             int f = Character.digit(data[j++], 16) << 4;
608             f = f | Character.digit(data[j++], 16);
609             out[i] = (byte) (f & 0xFF);
610         }
611
612         return out;
613     }
614
615     /**
616      * reads the checksum from a file using the specified format.
617      *
618      * @since 1.7
619      */

620     private String JavaDoc readChecksum(File JavaDoc f) {
621         BufferedReader JavaDoc diskChecksumReader = null;
622         try {
623             diskChecksumReader = new BufferedReader JavaDoc(new FileReader JavaDoc(f));
624             Object JavaDoc[] result = format.parse(diskChecksumReader.readLine());
625             if (result == null || result.length == 0 || result[0] == null) {
626                 throw new BuildException("failed to find a checksum");
627             }
628             return (String JavaDoc) result[0];
629         } catch (IOException JavaDoc e) {
630             throw new BuildException("Couldn't read checksum file " + f, e);
631         } catch (ParseException JavaDoc e) {
632             throw new BuildException("Couldn't read checksum file " + f, e);
633         } finally {
634             FileUtils.close(diskChecksumReader);
635         }
636     }
637
638     /**
639      * Helper class for the format attribute.
640      *
641      * @since 1.7
642      */

643     public static class FormatElement extends EnumeratedAttribute {
644         private static HashMap JavaDoc formatMap = new HashMap JavaDoc();
645         private static final String JavaDoc CHECKSUM = "CHECKSUM";
646         private static final String JavaDoc MD5SUM = "MD5SUM";
647         private static final String JavaDoc SVF = "SVF";
648
649         static {
650             formatMap.put(CHECKSUM, new MessageFormat JavaDoc("{0}"));
651             formatMap.put(MD5SUM, new MessageFormat JavaDoc("{0} *{1}"));
652             formatMap.put(SVF, new MessageFormat JavaDoc("MD5 ({1}) = {0}"));
653         }
654
655         /** Constructor for FormatElement */
656         public FormatElement() {
657             super();
658         }
659
660         /**
661          * Get the default value - CHECKSUM.
662          * @return the defaul value.
663          */

664         public static FormatElement getDefault() {
665             FormatElement e = new FormatElement();
666             e.setValue(CHECKSUM);
667             return e;
668         }
669
670         /**
671          * Convert this enumerated type to a <code>MessageFormat</code>.
672          * @return a <code>MessageFormat</code> object.
673          */

674         public MessageFormat JavaDoc getFormat() {
675             return (MessageFormat JavaDoc) formatMap.get(getValue());
676         }
677
678         /**
679          * Get the valid values.
680          * @return an array of values.
681          */

682         public String JavaDoc[] getValues() {
683             return new String JavaDoc[] {CHECKSUM, MD5SUM, SVF};
684         }
685     }
686 }
687
Popular Tags