View Javadoc

1   package zipdiff;
2   
3   import java.util.*;
4   import java.util.zip.ZipInputStream;
5   import java.util.zip.ZipEntry;
6   import java.util.zip.ZipFile;
7   import java.util.logging.Level;
8   import java.util.logging.Logger;
9   import java.util.regex.*;
10  import java.io.*;
11  
12  /***
13   * Checks and compiles differences between two zip files.
14   * It also has the ability to exclude entries from the comparison
15   * based on a regular expression.
16   * 
17   * @author Sean C. Sullivan
18   */
19  public class DifferenceCalculator {
20  
21      private Logger logger = Logger.getLogger(getClass().getName());
22  
23      private ZipFile file1;
24      private ZipFile file2;
25      private boolean ignoreTimestamps = false;
26      private boolean ignoreCVSFiles = false;
27      private boolean compareCRCValues = true;
28      private Pattern filesToIgnorePattern;
29      private boolean bVerbose = false;
30  
31      protected void debug(Object msg) {
32          if (isVerboseEnabled()) {
33              System.out.println("[" + DifferenceCalculator.class.getName() + "] " + String.valueOf(msg));
34          }
35      }
36  
37      /***
38       * Set the verboseness of the debug output.
39       * @param b true to make verbose
40       */
41      public void setVerbose(boolean b) {
42          bVerbose = b;
43      }
44  
45      protected boolean isVerboseEnabled() {
46          return bVerbose;
47      }
48  
49  	/***
50  	 * Constructor taking 2 filenames to compare
51  	 * @throws java.io.IOException
52  	 */
53      public DifferenceCalculator(String filename1, String filename2) throws java.io.IOException {
54          this(new File(filename1), new File(filename2));
55      }
56  
57  	/***
58  	 * Constructor taking 2 Files to compare
59  	 * @throws java.io.IOException
60  	 */
61      public DifferenceCalculator(File f1, File f2) throws java.io.IOException {
62          this(new ZipFile(f1), new ZipFile(f2));
63      }
64  
65  	/***
66  	 * Constructor taking 2 ZipFiles to compare
67  	 */
68  	public DifferenceCalculator(ZipFile zf1, ZipFile zf2) {
69  		file1 = zf1;
70  		file2 = zf2;
71  	}
72  
73      /***
74       * 
75       * @param Set A set of regular expressions that when matched against a ZipEntry
76       * then that ZipEntry will be ignored from the comparison.
77       * @see java.util.regex
78       */
79      public void setFilenameRegexToIgnore(Set patterns) {
80          if (patterns == null) {
81              filesToIgnorePattern = null;
82          } else if (patterns.isEmpty()) {
83              filesToIgnorePattern = null;
84          } else {
85              String regex = "";
86  
87              Iterator iter = patterns.iterator();
88              while (iter.hasNext()) {
89                  String pattern = (String) iter.next();
90                  if (regex.length() > 0) {
91                      regex += "|";
92                  }
93                  regex += "(" + pattern + ")";
94              }
95              filesToIgnorePattern = Pattern.compile(regex);
96              logger.log(Level.FINE, "Regular expression is : " + regex);
97          }
98      }
99  
100     /***
101      * returns true if fileToIgnorePattern matches the filename given.
102      * @param filepath 
103      * @param filename The name of the file to check to see if it should be ignored.
104      * @return true if the file should be ignored.
105      */
106     protected boolean ignoreThisFile(String filepath, String entryName) {
107         if (entryName == null) {
108             return false;
109         }
110         else if (isCVSFile(filepath, entryName) && (ignoreCVSFiles() ) ) {
111         	return true;
112         } else if (filesToIgnorePattern == null) {
113             return false;
114         } else {
115             Matcher m = filesToIgnorePattern.matcher(entryName);
116             boolean match = m.matches();
117             if (match) {
118                 logger.log(Level.FINEST, "Found a match against : " + entryName + " so excluding");
119             }
120             return match;
121         }
122     }
123 
124     protected boolean isCVSFile(String filepath, String entryName)
125     {
126     	if (entryName == null)
127     	{
128     		return false;
129     	}
130     	else if ( (filepath.indexOf("CVS/") != -1) || (entryName.indexOf("CVS/") != -1))
131 		{
132 			return true;
133 		}
134 		else
135 		{
136 			return false;
137 		}
138     }
139 	/***
140 	 * Ensure that the comparison checks against the CRCs of the entries.
141 	 * @param b true ensures that CRCs will be checked
142 	 */
143     public void setCompareCRCValues(boolean b) {
144         compareCRCValues = b;
145     }
146 
147 	/***
148 	 * @return true if this instance will check the CRCs of each ZipEntry
149 	 */
150     public boolean getCompareCRCValues() {
151         return compareCRCValues;
152     }
153 
154     /***
155      * Opens the ZipFile and builds up a map of all the entries. The key is the name of
156      * the entry and the value is the ZipEntry itself.
157      * @param zf The ZipFile for which to build up the map of ZipEntries
158      * @return The map containing all the ZipEntries. The key being the name of the ZipEntry.
159      * @throws java.io.IOException
160      */
161     protected Map buildZipEntryMap(ZipFile zf) throws java.io.IOException {
162         Map zipEntryMap = new HashMap();
163         try {
164 	        Enumeration entries = zf.entries();
165 	        while (entries.hasMoreElements()) {
166 	            ZipEntry entry = (ZipEntry) entries.nextElement();
167 	            InputStream is = null;
168 	            try {
169 	                is = zf.getInputStream(entry);
170 	                processZipEntry("", entry, is, zipEntryMap);
171 	            } finally {
172 	                if (is != null) {
173 	                    is.close();
174 	                }
175 	            }
176 	        }
177         } finally {
178             zf.close();
179         }
180 
181         return zipEntryMap;
182     }
183 
184     /***
185      * Will place ZipEntries for a given ZipEntry into the given Map. More ZipEntries will result
186      * if zipEntry is itself a ZipFile. All embedded ZipFiles will be processed with their names
187      * prefixed onto the names of their ZipEntries.
188      * @param prefix The prefix of the ZipEntry that should be added to the key. Typically used 
189      * when processing embedded ZipFiles. The name of the embedded ZipFile would be the prefix of
190      * all the embedded ZipEntries.
191      * @param zipEntry The ZipEntry to place into the Map. If it is a ZipFile then all its ZipEntries
192      * will also be placed in the Map.
193      * @param is The InputStream of the corresponding ZipEntry.
194      * @param zipEntryMap The Map in which to place all the ZipEntries into. The key will
195      * be the name of the ZipEntry.
196      * @throws IOException
197      */
198     protected void processZipEntry(String prefix, ZipEntry zipEntry, InputStream is, Map zipEntryMap) throws IOException {
199         if (ignoreThisFile(prefix, zipEntry.getName())) {
200 			logger.log(Level.FINE, "ignoring file: " + zipEntry.getName());
201         } else {
202             String name = prefix + zipEntry.getName();
203 
204             logger.log(Level.FINEST, "processing ZipEntry: " + name);
205 
206             if (zipEntry.isDirectory()) {
207                 zipEntryMap.put(name, zipEntry);
208             } else if (isZipFile(name)) {
209                 processEmbeddedZipFile(zipEntry.getName() + "/", is, zipEntryMap);
210                 zipEntryMap.put(name, zipEntry);
211             } else {
212                 zipEntryMap.put(name, zipEntry);
213             }
214         }
215     }
216 
217     protected void processEmbeddedZipFile(String prefix, InputStream is, Map m) throws java.io.IOException {
218         ZipInputStream zis = new ZipInputStream(is);
219 
220         ZipEntry entry = zis.getNextEntry();
221 
222         while (entry != null) {
223             processZipEntry(prefix, entry, zis, m);
224             zis.closeEntry();
225             entry = zis.getNextEntry();
226         }
227 
228     }
229 
230     /***
231      * Returns true if the filename has a valid zip extension.
232      * i.e. jar, war, ear, zip etc.
233      * @param filename The name of the file to check.
234      * @return true if it has a valid extension.
235      */
236     public static boolean isZipFile(String filename) {
237         boolean result;
238 
239         if (filename == null) {
240             result = false;
241         } else {
242             String lowercaseName = filename.toLowerCase();
243             if (lowercaseName.endsWith(".zip")) {
244                 result = true;
245             } else if (lowercaseName.endsWith(".ear")) {
246                 result = true;
247             } else if (lowercaseName.endsWith(".war")) {
248                 result = true;
249             } else if (lowercaseName.endsWith(".rar")) {
250                 result = true;
251             } else if (lowercaseName.endsWith(".jar")) {
252                 result = true;
253             } else {
254                 result = false;
255             }
256         }
257 
258         return result;
259     }
260 
261     /***
262      * Calculates all the differences between two zip files.
263      * It builds up the 2 maps of ZipEntries for the two files 
264      * and then compares them.
265      * @param zf1 The first ZipFile to compare
266      * @param zf2 The second ZipFile to compare
267      * @return All the differences between the two files.
268      * @throws java.io.IOException
269      */
270     protected Differences calculateDifferences(ZipFile zf1, ZipFile zf2) throws java.io.IOException {
271         Map map1 = buildZipEntryMap(zf1);
272         Map map2 = buildZipEntryMap(zf2);
273 
274         return calculateDifferences(map1, map2);
275     }
276 
277     /***
278      * Given two Maps of ZipEntries it will generate a Differences of all the
279      * differences found between the two maps. 
280      * @return All the differences found between the two maps
281      */
282     protected Differences calculateDifferences(Map m1, Map m2) {
283         Differences d = new Differences();
284 
285         Set names1 = m1.keySet();
286         Set names2 = m2.keySet();
287 
288         Set allNames = new HashSet();
289         allNames.addAll(names1);
290         allNames.addAll(names2);
291 
292         Iterator iterAllNames = allNames.iterator();
293         while (iterAllNames.hasNext()) {
294             String name = (String) iterAllNames.next();
295             if (ignoreThisFile("", name)) {
296             	// do nothing
297             }
298             else if (names1.contains(name) && (!names2.contains(name))) {
299                 d.fileRemoved(name, (ZipEntry) m1.get(name));
300             } else if (names2.contains(name) && (!names1.contains(name))) {
301                 d.fileAdded(name, (ZipEntry) m2.get(name));
302             } else if (names1.contains(name) && (names2.contains(name))) {
303                 ZipEntry entry1 = (ZipEntry) m1.get(name);
304                 ZipEntry entry2 = (ZipEntry) m2.get(name);
305                 if (!entriesMatch(entry1, entry2)) {
306                     d.fileChanged(name, entry1, entry2);
307                 }
308             } else {
309                 throw new IllegalStateException("unexpected state");
310             }
311         }
312 
313         return d;
314     }
315 
316     /***
317      * returns true if the two entries are equivalent in type, name, size, compressed size
318      * and time or CRC.
319      * @param entry1 The first ZipEntry to compare
320      * @param entry2 The second ZipEntry to compare
321      * @return true if the entries are equivalent.
322      */
323     protected boolean entriesMatch(ZipEntry entry1, ZipEntry entry2) {
324         boolean result;
325 
326         result =
327             (entry1.isDirectory() == entry2.isDirectory())
328                 && (entry1.getSize() == entry2.getSize())
329                 && (entry1.getCompressedSize() == entry2.getCompressedSize())
330                 && (entry1.getName().equals(entry2.getName()));
331 
332         if (!isIgnoringTimestamps()) {
333             result = result && (entry1.getTime() == entry2.getTime());
334         }
335 
336         if (getCompareCRCValues()) {
337             result = result && (entry1.getCrc() == entry2.getCrc());
338         }
339         return result;
340     }
341 
342     public void setIgnoreTimestamps(boolean b) {
343         ignoreTimestamps = b;
344     }
345 
346     public boolean isIgnoringTimestamps() {
347         return ignoreTimestamps;
348     }
349 
350     public boolean ignoreCVSFiles()
351     {
352     	return ignoreCVSFiles;
353     }
354 
355     public void setIgnoreCVSFiles(boolean b)
356     {
357     	ignoreCVSFiles = b;
358     }
359     
360     /***
361      * 
362      * @return all the differences found between the two zip files.
363      * @throws java.io.IOException
364      */
365     public Differences getDifferences() throws java.io.IOException {
366         Differences d = calculateDifferences(file1, file2);
367         d.setFilename1(file1.getName());
368         d.setFilename2(file2.getName());
369 
370         return d;
371     }
372 }