1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 package jnlp.sample.jardiff;
38
39 import java.io.File;
40 import java.io.FileOutputStream;
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.io.OutputStream;
44 import java.io.StringWriter;
45 import java.io.Writer;
46 import java.util.ArrayList;
47 import java.util.Enumeration;
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.Iterator;
51 import java.util.LinkedList;
52 import java.util.List;
53 import java.util.ListIterator;
54 import java.util.Map;
55 import java.util.MissingResourceException;
56 import java.util.ResourceBundle;
57 import java.util.jar.JarEntry;
58 import java.util.jar.JarFile;
59 import java.util.jar.JarOutputStream;
60
61
62
63
64
65
66
67
68
69
70
71 public class JarDiff
72 implements JarDiffConstants
73 {
74 private static final int DEFAULT_READ_SIZE = 2048;
75
76 private static byte[] newBytes = new byte[DEFAULT_READ_SIZE];
77
78 private static byte[] oldBytes = new byte[DEFAULT_READ_SIZE];
79
80 private static ResourceBundle _resources = null;
81
82
83
84
85 private static boolean _debug;
86
87 public static ResourceBundle getResources()
88 {
89 if ( _resources == null )
90 {
91 _resources = ResourceBundle.getBundle( "jnlp/sample/jardiff/resources/strings" );
92 }
93 return _resources;
94 }
95
96
97
98
99
100 public static void createPatch( String oldPath, String newPath, OutputStream os, boolean minimal )
101 throws IOException
102 {
103 JarFile2 oldJar = new JarFile2( oldPath );
104 JarFile2 newJar = new JarFile2( newPath );
105
106 try
107 {
108 Iterator entries;
109 HashMap moved = new HashMap();
110 HashSet visited = new HashSet();
111 HashSet implicit = new HashSet();
112 HashSet moveSrc = new HashSet();
113 HashSet newEntries = new HashSet();
114
115
116
117
118
119
120
121
122 entries = newJar.getJarEntries();
123 if ( entries != null )
124 {
125 while ( entries.hasNext() )
126 {
127 JarEntry newEntry = (JarEntry) entries.next();
128 String newname = newEntry.getName();
129
130
131 String oldname = oldJar.getBestMatch( newJar, newEntry );
132 if ( oldname == null )
133 {
134
135 if ( _debug )
136 {
137 System.out.println( "NEW: " + newname );
138 }
139 newEntries.add( newname );
140 }
141 else
142 {
143
144
145
146
147 if ( oldname.equals( newname ) && !moveSrc.contains( oldname ) )
148 {
149 if ( _debug )
150 {
151 System.out.println( newname + " added to implicit set!" );
152 }
153 implicit.add( newname );
154 }
155 else
156 {
157
158
159
160
161
162
163
164 if ( !minimal && ( implicit.contains( oldname ) || moveSrc.contains( oldname ) ) )
165 {
166
167
168
169
170 if ( _debug )
171 {
172 System.out.println( "NEW: " + newname );
173 }
174 newEntries.add( newname );
175 }
176 else
177 {
178
179 if ( _debug )
180 {
181 System.err.println( "moved.put " + newname + " " + oldname );
182 }
183 moved.put( newname, oldname );
184 moveSrc.add( oldname );
185 }
186
187 if ( implicit.contains( oldname ) && minimal )
188 {
189
190 if ( _debug )
191 {
192 System.err.println( "implicit.remove " + oldname );
193
194 System.err.println( "moved.put " + oldname + " " + oldname );
195 }
196 implicit.remove( oldname );
197 moved.put( oldname, oldname );
198 moveSrc.add( oldname );
199 }
200
201
202 }
203 }
204 }
205 }
206
207
208
209 ArrayList deleted = new ArrayList();
210 entries = oldJar.getJarEntries();
211 if ( entries != null )
212 {
213 while ( entries.hasNext() )
214 {
215 JarEntry oldEntry = (JarEntry) entries.next();
216 String oldName = oldEntry.getName();
217 if ( !implicit.contains( oldName ) && !moveSrc.contains( oldName ) &&
218 !newEntries.contains( oldName ) )
219 {
220 if ( _debug )
221 {
222 System.err.println( "deleted.add " + oldName );
223 }
224 deleted.add( oldName );
225 }
226 }
227 }
228
229
230 if ( _debug )
231 {
232
233 entries = moved.keySet().iterator();
234 if ( entries != null )
235 {
236 System.out.println( "MOVED MAP!!!" );
237 while ( entries.hasNext() )
238 {
239 String newName = (String) entries.next();
240 String oldName = (String) moved.get( newName );
241 System.out.println( "key is " + newName + " value is " + oldName );
242 }
243 }
244
245
246 entries = implicit.iterator();
247 if ( entries != null )
248 {
249 System.out.println( "IMOVE MAP!!!" );
250 while ( entries.hasNext() )
251 {
252 String newName = (String) entries.next();
253 System.out.println( "key is " + newName );
254 }
255 }
256 }
257
258 JarOutputStream jos = new JarOutputStream( os );
259
260
261 createIndex( jos, deleted, moved );
262
263
264 entries = newEntries.iterator();
265 if ( entries != null )
266 {
267
268 while ( entries.hasNext() )
269 {
270 String newName = (String) entries.next();
271 if ( _debug )
272 {
273 System.out.println( "New File: " + newName );
274 }
275 writeEntry( jos, newJar.getEntryByName( newName ), newJar );
276 }
277 }
278
279 jos.finish();
280 jos.close();
281
282 }
283 catch ( IOException ioE )
284 {
285 throw ioE;
286 }
287 finally
288 {
289 try
290 {
291 oldJar.getJarFile().close();
292 }
293 catch ( IOException e1 )
294 {
295
296 }
297 try
298 {
299 newJar.getJarFile().close();
300 }
301 catch ( IOException e1 )
302 {
303
304 }
305 }
306 }
307
308
309
310
311
312
313 private static void createIndex( JarOutputStream jos, List oldEntries, Map movedMap )
314 throws IOException
315 {
316 StringWriter writer = new StringWriter();
317
318 writer.write( VERSION_HEADER );
319 writer.write( "\r\n" );
320
321
322 for ( Object oldEntry : oldEntries )
323 {
324 String name = (String) oldEntry;
325
326 writer.write( REMOVE_COMMAND );
327 writer.write( " " );
328 writeEscapedString( writer, name );
329 writer.write( "\r\n" );
330 }
331
332
333
334 for ( Object o : movedMap.keySet() )
335 {
336 String newName = (String) o;
337 String oldName = (String) movedMap.get( newName );
338
339 writer.write( MOVE_COMMAND );
340 writer.write( " " );
341 writeEscapedString( writer, oldName );
342 writer.write( " " );
343 writeEscapedString( writer, newName );
344 writer.write( "\r\n" );
345 }
346
347 JarEntry je = new JarEntry( INDEX_NAME );
348 byte[] bytes = writer.toString().getBytes( "UTF-8" );
349
350 writer.close();
351 jos.putNextEntry( je );
352 jos.write( bytes, 0, bytes.length );
353 }
354
355 private static void writeEscapedString( Writer writer, String string )
356 throws IOException
357 {
358 int index = 0;
359 int last = 0;
360 char[] chars = null;
361
362 while ( ( index = string.indexOf( ' ', index ) ) != -1 )
363 {
364 if ( last != index )
365 {
366 if ( chars == null )
367 {
368 chars = string.toCharArray();
369 }
370 writer.write( chars, last, index - last );
371 }
372 last = index;
373 index++;
374 writer.write( '\\' );
375 }
376 if ( last != 0 )
377 {
378 writer.write( chars, last, chars.length - last );
379 }
380 else
381 {
382
383 writer.write( string );
384 }
385 }
386
387 private static void writeEntry( JarOutputStream jos, JarEntry entry, JarFile2 file )
388 throws IOException
389 {
390 writeEntry( jos, entry, file.getJarFile().getInputStream( entry ) );
391 }
392
393 private static void writeEntry( JarOutputStream jos, JarEntry entry, InputStream data )
394 throws IOException
395 {
396 jos.putNextEntry( entry );
397
398 try
399 {
400
401 int size = data.read( newBytes );
402
403 while ( size != -1 )
404 {
405 jos.write( newBytes, 0, size );
406 size = data.read( newBytes );
407 }
408 }
409 catch ( IOException ioE )
410 {
411 throw ioE;
412 }
413 finally
414 {
415 try
416 {
417 data.close();
418 }
419 catch ( IOException e )
420 {
421
422 }
423 }
424 }
425
426
427
428
429
430 private static class JarFile2
431 {
432 private JarFile _jar;
433
434 private List _entries;
435
436 private HashMap _nameToEntryMap;
437
438 private HashMap _crcToEntryMap;
439
440 public JarFile2( String path )
441 throws IOException
442 {
443 _jar = new JarFile( new File( path ) );
444 index();
445 }
446
447 public JarFile getJarFile()
448 {
449 return _jar;
450 }
451
452 public Iterator getJarEntries()
453 {
454 return _entries.iterator();
455 }
456
457 public JarEntry getEntryByName( String name )
458 {
459 return (JarEntry) _nameToEntryMap.get( name );
460 }
461
462
463
464
465 private static boolean differs( InputStream oldIS, InputStream newIS )
466 throws IOException
467 {
468 int newSize = 0;
469 int oldSize;
470 int total = 0;
471 boolean retVal = false;
472
473 try
474 {
475 while ( newSize != -1 )
476 {
477 newSize = newIS.read( newBytes );
478 oldSize = oldIS.read( oldBytes );
479
480 if ( newSize != oldSize )
481 {
482 if ( _debug )
483 {
484 System.out.println( "\tread sizes differ: " + newSize + " " + oldSize + " total " + total );
485 }
486 retVal = true;
487 break;
488 }
489 if ( newSize > 0 )
490 {
491 while ( --newSize >= 0 )
492 {
493 total++;
494 if ( newBytes[newSize] != oldBytes[newSize] )
495 {
496 if ( _debug )
497 {
498 System.out.println( "\tbytes differ at " + total );
499 }
500 retVal = true;
501 break;
502 }
503 if ( retVal )
504 {
505
506 break;
507 }
508 newSize = 0;
509 }
510 }
511 }
512 }
513 catch ( IOException ioE )
514 {
515 throw ioE;
516 }
517 finally
518 {
519 try
520 {
521 oldIS.close();
522 }
523 catch ( IOException e )
524 {
525
526 }
527 try
528 {
529 newIS.close();
530 }
531 catch ( IOException e )
532 {
533
534 }
535 }
536 return retVal;
537 }
538
539 public String getBestMatch( JarFile2 file, JarEntry entry )
540 throws IOException
541 {
542
543 if ( contains( file, entry ) )
544 {
545 return ( entry.getName() );
546 }
547
548
549 return ( hasSameContent( file, entry ) );
550 }
551
552 public boolean contains( JarFile2 f, JarEntry e )
553 throws IOException
554 {
555
556 JarEntry thisEntry = getEntryByName( e.getName() );
557
558
559 if ( thisEntry == null )
560 {
561 return false;
562 }
563
564
565 if ( thisEntry.getCrc() != e.getCrc() )
566 {
567 return false;
568 }
569
570
571 InputStream oldIS = getJarFile().getInputStream( thisEntry );
572 InputStream newIS = f.getJarFile().getInputStream( e );
573 boolean retValue = differs( oldIS, newIS );
574
575 return !retValue;
576 }
577
578 public String hasSameContent( JarFile2 file, JarEntry entry )
579 throws IOException
580 {
581
582 String thisName = null;
583
584 Long crcL = entry.getCrc();
585
586
587 if ( _crcToEntryMap.containsKey( crcL ) )
588 {
589
590 LinkedList ll = (LinkedList) _crcToEntryMap.get( crcL );
591
592 ListIterator li = ll.listIterator( 0 );
593 while ( li.hasNext() )
594 {
595 JarEntry thisEntry = (JarEntry) li.next();
596
597
598 InputStream oldIS = getJarFile().getInputStream( thisEntry );
599 InputStream newIS = file.getJarFile().getInputStream( entry );
600
601 if ( !differs( oldIS, newIS ) )
602 {
603 thisName = thisEntry.getName();
604 return thisName;
605 }
606 }
607 }
608
609 return thisName;
610
611 }
612
613
614 private void index()
615 throws IOException
616 {
617 Enumeration entries = _jar.entries();
618
619 _nameToEntryMap = new HashMap();
620 _crcToEntryMap = new HashMap();
621
622 _entries = new ArrayList();
623 if ( _debug )
624 {
625 System.out.println( "indexing: " + _jar.getName() );
626 }
627 if ( entries != null )
628 {
629 while ( entries.hasMoreElements() )
630 {
631 JarEntry entry = (JarEntry) entries.nextElement();
632
633 long crc = entry.getCrc();
634
635 Long crcL = new Long( crc );
636
637 if ( _debug )
638 {
639 System.out.println( "\t" + entry.getName() + " CRC " + crc );
640 }
641
642 _nameToEntryMap.put( entry.getName(), entry );
643 _entries.add( entry );
644
645
646 if ( _crcToEntryMap.containsKey( crcL ) )
647 {
648
649
650
651
652 LinkedList ll = (LinkedList) _crcToEntryMap.get( crcL );
653
654
655 ll.add( entry );
656
657
658 _crcToEntryMap.put( crcL, ll );
659 }
660 else
661 {
662
663
664
665
666 LinkedList ll = new LinkedList();
667 ll.add( entry );
668
669
670 _crcToEntryMap.put( crcL, ll );
671 }
672
673 }
674 }
675 }
676
677 }
678
679
680 private static void showHelp()
681 {
682 System.out.println(
683 "JarDiff: [-nonminimal (for backward compatibility with 1.0.1/1.0] [-creatediff | -applydiff] [-output file] old.jar new.jar" );
684 }
685
686
687 public static void main( String[] args )
688 throws IOException
689 {
690 boolean diff = true;
691 boolean minimal = true;
692 String outputFile = "out.jardiff";
693
694 for ( int counter = 0; counter < args.length; counter++ )
695 {
696
697 if ( args[counter].equals( "-nonminimal" ) || args[counter].equals( "-n" ) )
698 {
699 minimal = false;
700 }
701 else if ( args[counter].equals( "-creatediff" ) || args[counter].equals( "-c" ) )
702 {
703 diff = true;
704 }
705 else if ( args[counter].equals( "-applydiff" ) || args[counter].equals( "-a" ) )
706 {
707 diff = false;
708 }
709 else if ( args[counter].equals( "-debug" ) || args[counter].equals( "-d" ) )
710 {
711 _debug = true;
712 }
713 else if ( args[counter].equals( "-output" ) || args[counter].equals( "-o" ) )
714 {
715 if ( ++counter < args.length )
716 {
717 outputFile = args[counter];
718 }
719 }
720 else if ( args[counter].equals( "-applydiff" ) || args[counter].equals( "-a" ) )
721 {
722 diff = false;
723 }
724 else
725 {
726 if ( ( counter + 2 ) != args.length )
727 {
728 showHelp();
729 System.exit( 0 );
730 }
731 if ( diff )
732 {
733 try
734 {
735 OutputStream os = new FileOutputStream( outputFile );
736
737 JarDiff.createPatch( args[counter], args[counter + 1], os, minimal );
738 os.close();
739 }
740 catch ( IOException ioe )
741 {
742 try
743 {
744 System.out.println( getResources().getString( "jardiff.error.create" ) + " " + ioe );
745 }
746 catch ( MissingResourceException mre )
747 {
748 }
749 }
750 }
751 else
752 {
753 try
754 {
755 OutputStream os = new FileOutputStream( outputFile );
756
757 new JarDiffPatcher().applyPatch( null, args[counter], args[counter + 1], os );
758 os.close();
759 }
760 catch ( IOException ioe )
761 {
762 try
763 {
764 System.out.println( getResources().getString( "jardiff.error.apply" ) + " " + ioe );
765 }
766 catch ( MissingResourceException mre )
767 {
768 }
769 }
770 }
771 System.exit( 0 );
772 }
773 }
774 showHelp();
775 }
776 }