/** * Proof of concept for delta rd5's * * @author ab */ package btools.mapaccess; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.Arrays; import btools.codec.DataBuffers; import btools.codec.MicroCache; import btools.codec.MicroCache2; import btools.codec.StatCoderContext; import btools.util.Crc32; final public class Rd5DiffTool { public static void main( String[] args ) throws Exception { if ( args[1].endsWith( ".rd5diff" ) ) { if ( args[0].endsWith( ".rd5diff" ) ) { addDeltas( new File( args[0] ),new File( args[1] ), new File( args[2] ) ); } else { recoverFromDelta( new File( args[0] ),new File( args[1] ), new File( args[2] ) /*, new File( args[3] ) */ ); } } else { diff2files( new File( args[0] ),new File( args[1] ), new File( args[2] ) ); } } private static long[] readFileIndex( DataInputStream dis, DataOutputStream dos ) throws Exception { long[] fileIndex = new long[25]; for( int i=0; i<25; i++ ) { long lv = dis.readLong(); fileIndex[i] = lv & 0xffffffffffffL; if ( dos != null ) { dos.writeLong( lv ); } } return fileIndex; } private static long getTileStart( long[] index, int tileIndex ) { return tileIndex > 0 ? index[tileIndex - 1] : 200L; } private static long getTileEnd( long[] index, int tileIndex ) { return index[tileIndex]; } private static int[] readPosIndex( DataInputStream dis, DataOutputStream dos ) throws Exception { int[] posIndex = new int[1024]; for( int i=0; i<1024; i++ ) { int iv = dis.readInt(); posIndex[i] = iv; if ( dos != null ) { dos.writeInt( iv ); } } return posIndex; } private static int getPosIdx( int[] posIdx, int idx ) { return idx == -1 ? 4096 : posIdx[idx]; } private static byte[] createMicroCache( int[] posIdx, int tileIdx, DataInputStream dis, boolean deltaMode ) throws Exception { if ( posIdx == null ) { return null; } int size = getPosIdx( posIdx, tileIdx ) - getPosIdx( posIdx, tileIdx-1 ); if ( size == 0 ) { return null; } if ( deltaMode ) { size = dis.readInt(); } byte[] ab = new byte[size]; dis.readFully( ab ); return ab; } private static MicroCache createMicroCache( byte[] ab, DataBuffers dataBuffers ) throws Exception { if ( ab == null || ab.length == 0 ) { return MicroCache.emptyCache(); } StatCoderContext bc = new StatCoderContext( ab ); return new MicroCache2( bc, dataBuffers, 0, 0, 32, null, null ); } /** * Compute the delta between 2 RD5 files and * show statistics on the expected size of the delta file */ public static void diff2files( File f1, File f2, File outFile ) throws Exception { byte[] abBuf1 = new byte[10 * 1024 * 1024]; byte[] abBuf2 = new byte[10 * 1024 * 1024]; int nodesDiff = 0; int diffedTiles = 0; long bytesDiff = 0L; DataInputStream dis1 = new DataInputStream( new BufferedInputStream( new FileInputStream( f1 ) ) ); DataInputStream dis2 = new DataInputStream( new BufferedInputStream( new FileInputStream( f2 ) ) ); DataOutputStream dos = new DataOutputStream( new BufferedOutputStream( new FileOutputStream( outFile ) ) ); // copy header to outfile long[] fileIndex1 = readFileIndex( dis1, null ); long[] fileIndex2 = readFileIndex( dis2, dos ); try { DataBuffers dataBuffers = new DataBuffers(); for ( int subFileIdx = 0; subFileIdx < 25; subFileIdx++ ) { boolean hasData1 = getTileStart( fileIndex1, subFileIdx ) < getTileEnd( fileIndex1, subFileIdx ); boolean hasData2 = getTileStart( fileIndex2, subFileIdx ) < getTileEnd( fileIndex2, subFileIdx ); int[] posIdx1 = hasData1 ? readPosIndex( dis1, null ) : null; int[] posIdx2 = hasData2 ? readPosIndex( dis2, dos ) : null; for ( int tileIdx = 0; tileIdx < 1024; tileIdx++ ) { byte[] ab1 = createMicroCache( posIdx1, tileIdx, dis1, false ); byte[] ab2 = createMicroCache( posIdx2, tileIdx, dis2, false ); if ( ab2 == null ) { continue; // empty target tile } MicroCache mc; if ( Arrays.equals( ab1, ab2 ) ) { mc = MicroCache.emptyCache(); // empty diff } else // calc diff of the 2 tiles { MicroCache mc1 = createMicroCache( ab1, dataBuffers ); MicroCache mc2 = createMicroCache( ab2, dataBuffers ); mc = new MicroCache2( mc1.getSize() + mc2.getSize(), abBuf2, 0, 0, 32 ); mc.calcDelta( mc1, mc2 ); } if ( mc.getSize() == 0 ) { dos.writeInt( 0 ); } else { int len = mc.encodeMicroCache( abBuf1 ); dos.writeInt( len ); dos.write( abBuf1, 0, len ); // do some consistemcy checks on the encoding byte[] bytes = new byte[len]; System.arraycopy( abBuf1, 0, bytes, 0, len ); bytesDiff += len; nodesDiff += mc.getSize(); diffedTiles++; // cross-check the encoding: decode again MicroCache mcCheck = new MicroCache2( new StatCoderContext( bytes ), new DataBuffers( null ), 0, 0, 32, null, null ); // due to link-order ambiguity, for decoded we can only compare node-count and datasize if ( mc.size() != mcCheck.size() ) { throw new IllegalArgumentException( "re-decoded data-size mismatch!" ); } if ( mc.getSize() != mcCheck.getSize() ) { throw new IllegalArgumentException( "re-decoded node-count mismatch!" ); } // .... so re-encode again int len2 = mcCheck.encodeMicroCache( abBuf1 ); byte[] bytes2 = new byte[len2]; System.arraycopy( abBuf1, 0, bytes2, 0, len2 ); // and here we can compare byte-by-byte if ( len != len2 ) { throw new IllegalArgumentException( "decoded size mismatch!" ); } for( int i=0; idelta->encode ) MicroCache mc1 = createMicroCache( ab1, dataBuffers ); MicroCache mc2 = createMicroCache( ab2, dataBuffers ); MicroCache mc = new MicroCache2( mc1.getSize() + mc2.getSize(), abBuf2, 0, 0, 32 ); mc.addDelta( mc1, mc2, false ); int len = mc.encodeMicroCache( abBuf1 ); /* System.out.println( "comparing for subFileIdx=" + subFileIdx + " tileIdx=" + tileIdx ); boolean isequal = true; for( int i=0; iadd->encode ) MicroCache mc1 = createMicroCache( ab1, dataBuffers ); MicroCache mc2 = createMicroCache( ab2, dataBuffers ); MicroCache mc = new MicroCache2( mc1.getSize() + mc2.getSize(), abBuf2, 0, 0, 32 ); mc.addDelta( mc1, mc2, true ); int len = mc.encodeMicroCache( abBuf1 ); dos.writeInt( len+4 ); dos.write( abBuf1, 0, len ); dos.writeInt( Crc32.crc( abBuf1, 0, len ) ^ 2 ); } } // write any remaining data to the output file for(;;) { int len = dis2.read( abBuf1 ); if (len < 0) { break; } dos.write( abBuf1, 0, len ); } } finally { if ( dis1 != null ) { try { dis1.close(); } catch (Exception ee) { } } if ( dis2 != null ) { try { dis2.close(); } catch (Exception ee) { } } if ( dos != null ) { try { dos.close(); } catch (Exception ee) { } } } } }