KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > jnlp > sample > jardiff > JarDiff


1 /*
2  * @(#)JarDiff.java 1.7 05/11/17
3  *
4  * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * -Redistribution of source code must retain the above copyright notice, this
10  * list of conditions and the following disclaimer.
11  *
12  * -Redistribution in binary form must reproduce the above copyright notice,
13  * this list of conditions and the following disclaimer in the documentation
14  * and/or other materials provided with the distribution.
15  *
16  * Neither the name of Sun Microsystems, Inc. or the names of contributors may
17  * be used to endorse or promote products derived from this software without
18  * specific prior written permission.
19  *
20  * This software is provided "AS IS," without a warranty of any kind. ALL
21  * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
22  * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
23  * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN")
24  * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
25  * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
26  * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
27  * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
28  * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
29  * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
30  * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
31  *
32  * You acknowledge that this software is not designed, licensed or intended
33  * for use in the design, construction, operation or maintenance of any
34  * nuclear facility.
35  */

36
37 package jnlp.sample.jardiff;
38
39 import java.io.*;
40 import java.util.*;
41 import java.util.jar.*;
42 import java.util.zip.*;
43
44
45 /**
46  * JarDiff is able to create a jar file containing the delta between two
47  * jar files (old and new). The delta jar file can then be applied to the
48  * old jar file to reconstruct the new jar file.
49  * <p>
50  * Refer to the JNLP spec for details on how this is done.
51  *
52  * @version 1.13, 06/26/03
53  */

54 public class JarDiff implements JarDiffConstants {
55     private static final int DEFAULT_READ_SIZE = 2048;
56     private static byte[] newBytes = new byte[DEFAULT_READ_SIZE];
57     private static byte[] oldBytes = new byte[DEFAULT_READ_SIZE];
58     private static ResourceBundle _resources = null;
59
60     // The JARDiff.java is the stand-along jardiff.jar tool. Thus, we do not
61
// depend on Globals.java and other stuff here. Instead, we use an explicit
62
// _debug flag.
63
private static boolean _debug;
64
65     public static ResourceBundle getResources() {
66     if (_resources == null) {
67         _resources = ResourceBundle.getBundle("jnlp/sample/jardiff/resources/strings");
68     }
69     return _resources;
70     }
71
72     /**
73      * Creates a patch from the two passed in files, writing the result
74      * to <code>os</code>.
75      */

76     public static void createPatch(String JavaDoc oldPath, String JavaDoc newPath,
77                             OutputStream os, boolean minimal) throws IOException{
78         JarFile2 oldJar = new JarFile2(oldPath);
79         JarFile2 newJar = new JarFile2(newPath);
80       
81         try {
82           Iterator entries;
83           HashMap moved = new HashMap();
84           HashSet visited = new HashSet();
85           HashSet implicit = new HashSet();
86           HashSet moveSrc = new HashSet();
87           HashSet newEntries = new HashSet();
88
89
90           // FIRST PASS
91
// Go through the entries in new jar and
92
// determine which files are candidates for implicit moves
93
// ( files that has the same filename and same content in old.jar
94
// and new.jar )
95
// and for files that cannot be implicitly moved, we will either
96
// find out whether it is moved or new (modified)
97
entries = newJar.getJarEntries();
98           if (entries != null) {
99             while (entries.hasNext()) {
100                 JarEntry newEntry = (JarEntry)entries.next();
101                 String JavaDoc newname = newEntry.getName();
102     
103                 // Return best match of contents, will return a name match if possible
104
String JavaDoc oldname = oldJar.getBestMatch(newJar, newEntry);
105                 if (oldname == null) {
106                     // New or modified entry
107
if (_debug) {
108                         System.out.println("NEW: "+ newname);
109                     }
110                     newEntries.add(newname);
111                 } else {
112                     // Content already exist - need to do a move
113

114                     // Should do implicit move? Yes, if names are the same, and
115
// no move command already exist from oldJar
116
if (oldname.equals(newname) && !moveSrc.contains(oldname)) {
117                         if (_debug) {
118                             System.out.println(newname + " added to implicit set!");
119                         }
120                         implicit.add(newname);
121                     } else {
122                     // The 1.0.1/1.0 JarDiffPatcher cannot handle
123
// multiple MOVE command with same src.
124
// The work around here is if we are going to generate
125
// a MOVE command with duplicate src, we will
126
// instead add the target as a new file. This way
127
// the jardiff can be applied by 1.0.1/1.0
128
// JarDiffPatcher also.
129
if (!minimal && (implicit.contains(oldname) ||
130                             moveSrc.contains(oldname) )) {
131                 
132                             // generate non-minimal jardiff
133
// for backward compatibility
134

135                             if (_debug) {
136                     
137                                 System.out.println("NEW: "+ newname);
138                             }
139                             newEntries.add(newname);
140                         } else {
141                             // Use newname as key, since they are unique
142
if (_debug) {
143                                 System.err.println("moved.put " + newname + " " + oldname);
144                             }
145                             moved.put(newname, oldname);
146                             moveSrc.add(oldname);
147                         }
148                         // Check if this disables an implicit 'move <oldname> <oldname>'
149
if (implicit.contains(oldname) && minimal) {
150               
151                            if (_debug) {
152                               System.err.println("implicit.remove " + oldname);
153                 
154                               System.err.println("moved.put " + oldname + " " + oldname);
155                 
156                             }
157                             implicit.remove(oldname);
158                             moved.put(oldname, oldname);
159                             moveSrc.add(oldname);
160                         }
161
162         
163                     }
164                 }
165             }
166           } //if (entries != null)
167

168           // SECOND PASS: <deleted files> = <oldjarnames> - <implicitmoves> -
169
// <source of move commands> - <new or modified entries>
170
ArrayList deleted = new ArrayList();
171           entries = oldJar.getJarEntries();
172           if (entries != null) {
173               while (entries.hasNext()) {
174                   JarEntry oldEntry = (JarEntry)entries.next();
175                   String JavaDoc oldName = oldEntry.getName();
176                   if (!implicit.contains(oldName) && !moveSrc.contains(oldName)
177                     && !newEntries.contains(oldName)) {
178                       if (_debug) {
179                           System.err.println("deleted.add " + oldName);
180                       }
181                       deleted.add(oldName);
182                   }
183               }
184           }
185     
186           //DEBUG
187
if (_debug) {
188               //DEBUG: print out moved map
189
entries = moved.keySet().iterator();
190               if (entries != null) {
191                   System.out.println("MOVED MAP!!!");
192                   while (entries.hasNext()) {
193                       String JavaDoc newName = (String JavaDoc)entries.next();
194                       String JavaDoc oldName = (String JavaDoc)moved.get(newName);
195                       System.out.println("key is " + newName + " value is " + oldName);
196                       }
197               }
198         
199               //DEBUG: print out IMOVE map
200
entries = implicit.iterator();
201               if (entries != null) {
202                   System.out.println("IMOVE MAP!!!");
203                   while (entries.hasNext()) {
204                       String JavaDoc newName = (String JavaDoc)entries.next();
205                       System.out.println("key is " + newName);
206                   }
207               }
208           }
209
210           JarOutputStream jos = new JarOutputStream(os);
211
212           // Write out all the MOVEs and REMOVEs
213
createIndex(jos, deleted, moved);
214
215           // Put in New and Modified entries
216
entries = newEntries.iterator();
217           if (entries != null) {
218        
219               while (entries.hasNext()) {
220                   String JavaDoc newName = (String JavaDoc)entries.next();
221                   if (_debug) {
222                       System.out.println("New File: " + newName);
223                   }
224                   writeEntry(jos, newJar.getEntryByName(newName), newJar);
225               }
226           }
227      
228     
229           jos.finish();
230           jos.close();
231
232         } catch (IOException ioE){
233           throw ioE;
234         } finally {
235           try {
236               oldJar.getJarFile().close();
237           } catch (IOException e1) {
238               //ignore
239
}
240           try {
241               newJar.getJarFile().close();
242           } catch (IOException e1) {
243             //ignore
244
}
245         } // finally
246
}
247
248     /**
249      * Writes the index file out to <code>jos</code>.
250      * <code>oldEntries</code> gives the names of the files that were removed,
251      * <code>movedMap</code> maps from the new name to the old name.
252      */

253     private static void createIndex(JarOutputStream jos, List oldEntries,
254                              Map movedMap) throws
255                        IOException {
256         StringWriter writer = new StringWriter();
257
258         writer.write(VERSION_HEADER);
259         writer.write("\r\n");
260
261         // Write out entries that have been removed
262
for (int counter = 0; counter < oldEntries.size(); counter++) {
263             String JavaDoc name = (String JavaDoc)oldEntries.get(counter);
264
265             writer.write(REMOVE_COMMAND);
266             writer.write(" ");
267             writeEscapedString(writer, name);
268             writer.write("\r\n");
269         }
270
271         // And those that have moved
272
Iterator names = movedMap.keySet().iterator();
273
274         if (names != null) {
275             while (names.hasNext()) {
276                 String JavaDoc newName = (String JavaDoc)names.next();
277                 String JavaDoc oldName = (String JavaDoc)movedMap.get(newName);
278
279         writer.write(MOVE_COMMAND);
280         writer.write(" ");
281         writeEscapedString(writer, oldName);
282         writer.write(" ");
283         writeEscapedString(writer, newName);
284         writer.write("\r\n");
285            
286             }
287         }
288
289         JarEntry je = new JarEntry(INDEX_NAME);
290         byte[] bytes = writer.toString().getBytes("UTF-8");
291
292         writer.close();
293         jos.putNextEntry(je);
294         jos.write(bytes, 0, bytes.length);
295     }
296
297     private static void writeEscapedString(Writer writer, String JavaDoc string)
298                       throws IOException {
299         int index = 0;
300         int last = 0;
301         char[] chars = null;
302
303         while ((index = string.indexOf(' ', index)) != -1) {
304             if (last != index) {
305                 if (chars == null) {
306                     chars = string.toCharArray();
307                 }
308                 writer.write(chars, last, index - last);
309             }
310             last = index;
311             index++;
312             writer.write('\\');
313         }
314         if (last != 0) {
315             writer.write(chars, last, chars.length - last);
316         }
317         else {
318             // no spaces
319
writer.write(string);
320         }
321     }
322
323     private static void writeEntry(JarOutputStream jos, JarEntry entry,
324                             JarFile2 file) throws IOException {
325         writeEntry(jos, entry, file.getJarFile().getInputStream(entry));
326     }
327
328     private static void writeEntry(JarOutputStream jos, JarEntry entry,
329                             InputStream data) throws IOException {
330         jos.putNextEntry(entry);
331
332         try {
333             // Read the entry
334
int size = data.read(newBytes);
335
336             while (size != -1) {
337                 jos.write(newBytes, 0, size);
338                 size = data.read(newBytes);
339             }
340         } catch(IOException ioE) {
341             throw ioE;
342         } finally {
343             try {
344                 data.close();
345             } catch(IOException e){
346                 //Ignore
347
}
348
349         }
350     }
351
352    
353   
354
355     /**
356      * JarFile2 wraps a JarFile providing some convenience methods.
357      */

358     private static class JarFile2 {
359         private JarFile _jar;
360         private List _entries;
361         private HashMap _nameToEntryMap;
362     private HashMap _crcToEntryMap;
363             
364         public JarFile2(String JavaDoc path) throws IOException {
365             _jar = new JarFile(new File(path));
366             index();
367         }
368
369         public JarFile getJarFile() {
370             return _jar;
371         }
372
373         public Iterator getJarEntries() {
374             return _entries.iterator();
375         }
376
377         public JarEntry getEntryByName(String JavaDoc name) {
378             return (JarEntry)_nameToEntryMap.get(name);
379         }
380
381     /**
382      * Returns true if the two InputStreams differ.
383      */

384     private static boolean differs(InputStream oldIS, InputStream newIS)
385         throws IOException {
386         int newSize = 0;
387         int oldSize;
388         int total = 0;
389         boolean retVal = false;
390         
391         try{
392             while (newSize != -1) {
393                 newSize = newIS.read(newBytes);
394                 oldSize = oldIS.read(oldBytes);
395         
396                 if (newSize != oldSize) {
397                     if (_debug) {
398                         System.out.println("\tread sizes differ: " + newSize +
399                             " " + oldSize + " total " + total);
400                     }
401                     retVal = true;
402                     break;
403                 }
404                 if (newSize > 0) {
405                     while (--newSize >= 0) {
406                         total++;
407                         if (newBytes[newSize] != oldBytes[newSize]) {
408                             if (_debug) {
409                                 System.out.println("\tbytes differ at " +
410                                                     total);
411                             }
412                             retVal = true;
413                             break;
414                         }
415                         if ( retVal ) {
416                             //Jump out
417
break;
418                         }
419                         newSize = 0;
420                     }
421                 }
422             }
423         } catch(IOException ioE){
424             throw ioE;
425         } finally {
426             try {
427                 oldIS.close();
428             } catch(IOException e){
429                 //Ignore
430
}
431             try {
432                 newIS.close();
433             } catch(IOException e){
434                 //Ignore
435
}
436         }
437         return retVal;
438     }
439     
440     public String JavaDoc getBestMatch(JarFile2 file, JarEntry entry) throws IOException {
441         // check for same name and same content, return name if found
442
if (contains(file, entry)) {
443         return (entry.getName());
444         }
445
446         // return name of same content file or null
447
return (hasSameContent(file,entry));
448     }
449     
450     public boolean contains(JarFile2 f, JarEntry e) throws IOException {
451
452         JarEntry thisEntry = getEntryByName(e.getName());
453          
454         // Look up name in 'this' Jar2File - if not exist return false
455
if (thisEntry == null)
456         return false;
457
458         // Check CRC - if no match - return false
459
if (thisEntry.getCrc() != e.getCrc())
460         return false;
461
462         // Check contents - if no match - return false
463
InputStream oldIS = getJarFile().getInputStream(thisEntry);
464         InputStream newIS = f.getJarFile().getInputStream(e);
465         boolean retValue = differs(oldIS, newIS);
466
467         return !retValue;
468     }
469
470     public String JavaDoc hasSameContent(JarFile2 file, JarEntry entry) throws
471     IOException {
472
473         String JavaDoc thisName = null;
474         
475         Long JavaDoc crcL = new Long JavaDoc(entry.getCrc());
476
477         // check if this jar contains files with the passed in entry's crc
478
if (_crcToEntryMap.containsKey(crcL)) {
479             // get the Linked List with files with the crc
480
LinkedList ll = (LinkedList)_crcToEntryMap.get(crcL);
481             // go through the list and check for content match
482
ListIterator li = ll.listIterator(0);
483             if (li != null) {
484                 while (li.hasNext()) {
485                     JarEntry thisEntry = (JarEntry)li.next();
486
487                     // check for content match
488
InputStream oldIS = getJarFile().getInputStream(thisEntry);
489                     InputStream newIS = file.getJarFile().getInputStream(entry);
490                 
491                     if (!differs(oldIS, newIS)) {
492                         thisName = thisEntry.getName();
493                         return thisName;
494                     }
495                 }
496             }
497         }
498      
499         return thisName;
500        
501     }
502
503
504       
505
506        
507         private void index() throws IOException {
508             Enumeration entries = _jar.entries();
509
510             _nameToEntryMap = new HashMap();
511         _crcToEntryMap = new HashMap();
512          
513             _entries = new ArrayList();
514             if (_debug) {
515                 System.out.println("indexing: " + _jar.getName());
516             }
517             if (entries != null) {
518                 while (entries.hasMoreElements()) {
519                     JarEntry entry = (JarEntry)entries.nextElement();
520                    
521             long crc = entry.getCrc();
522
523             Long JavaDoc crcL = new Long JavaDoc(crc);
524
525                     if (_debug) {
526                         System.out.println("\t" + entry.getName() + " CRC " +
527                                       crc);
528                     }
529          
530                     _nameToEntryMap.put(entry.getName(), entry);
531                     _entries.add(entry);
532
533             // generate the CRC to entries map
534
if (_crcToEntryMap.containsKey(crcL)) {
535             // key exist, add the entry to the correcponding
536
// linked list
537

538             // get the linked list
539
LinkedList ll = (LinkedList)_crcToEntryMap.get(crcL);
540
541             // put in the new entry
542
ll.add(entry);
543
544             // put it back in the hash map
545
_crcToEntryMap.put(crcL, ll);
546             } else {
547             // create a new entry in the hashmap for the new key
548

549             // first create the linked list and put in the new
550
// entry
551
LinkedList ll = new LinkedList();
552             ll.add(entry);
553
554             // create the new entry in the hashmap
555
_crcToEntryMap.put(crcL, ll);
556             }
557                   
558                 }
559             }
560         }
561              
562     }
563
564
565     private static void showHelp() {
566         System.out.println("JarDiff: [-nonminimal (for backward compatibility with 1.0.1/1.0] [-creatediff | -applydiff] [-output file] old.jar new.jar");
567     }
568
569     // -creatediff -applydiff -debug -output file
570
public static void main(String JavaDoc[] args) throws IOException {
571         boolean diff = true;
572     boolean minimal = true;
573         String JavaDoc outputFile = "out.jardiff";
574
575         for (int counter = 0; counter < args.length; counter++) {
576         // for backward compatibilty with 1.0.1/1.0
577
if (args[counter].equals("-nonminimal") ||
578                 args[counter].equals("-n")) {
579         minimal = false;
580         }
581         else if (args[counter].equals("-creatediff") ||
582                 args[counter].equals("-c")) {
583                 diff = true;
584             }
585             else if (args[counter].equals("-applydiff") ||
586                 args[counter].equals("-a")) {
587                 diff = false;
588             }
589             else if (args[counter].equals("-debug") ||
590                 args[counter].equals("-d")) {
591                 _debug = true;
592             }
593             else if (args[counter].equals("-output") ||
594                      args[counter].equals("-o")) {
595                 if (++counter < args.length) {
596                     outputFile = args[counter];
597                 }
598             }
599             else if (args[counter].equals("-applydiff") ||
600                 args[counter].equals("-a")) {
601                 diff = false;
602             }
603             else {
604                 if ((counter + 2) != args.length) {
605                     showHelp();
606                     System.exit(0);
607                 }
608                 if (diff) {
609                     try {
610                         OutputStream os = new FileOutputStream(outputFile);
611
612                         JarDiff.createPatch(args[counter],
613                           args[counter + 1], os, minimal);
614                         os.close();
615                     } catch (IOException ioe) {
616             try {
617                 System.out.println(getResources().getString("jardiff.error.create") + " " + ioe);
618             } catch (MissingResourceException mre) {
619             }
620             }
621                 }
622                 else {
623                     try {
624                         OutputStream os = new FileOutputStream(outputFile);
625
626                         new JarDiffPatcher().applyPatch(
627                             null,
628                             args[counter],
629                             args[counter + 1],
630                             os);
631                         os.close();
632                     } catch (IOException ioe) {
633             try {
634                 System.out.println(getResources().getString("jardiff.error.apply") + " " + ioe);
635             } catch (MissingResourceException mre) {
636             }
637                     }
638                 }
639                 System.exit(0);
640             }
641         }
642         showHelp();
643     }
644 }
645
Popular Tags