ant-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dona...@apache.org
Subject cvs commit: jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip AsiExtraField.java ExtraFieldUtils.java UnixStat.java UnrecognizedExtraField.java ZipEntry.java ZipExtraField.java ZipLong.java ZipOutputStream.java ZipShort.java
Date Sun, 20 Jan 2002 00:03:05 GMT
donaldp     02/01/19 16:03:05

  Added:       proposal/myrmidon/src/java/org/apache/aut/bzip2
                        BZip2Constants.java CBZip2InputStream.java
                        CBZip2OutputStream.java CRC.java
               proposal/myrmidon/src/java/org/apache/aut/tar TarBuffer.java
                        TarConstants.java TarEntry.java TarInputStream.java
                        TarOutputStream.java TarUtils.java
               proposal/myrmidon/src/java/org/apache/aut/zip
                        AsiExtraField.java ExtraFieldUtils.java
                        UnixStat.java UnrecognizedExtraField.java
                        ZipEntry.java ZipExtraField.java ZipLong.java
                        ZipOutputStream.java ZipShort.java
  Log:
  Move general purpose utility code for bzip/zip/tar into aut
  
  Revision  Changes    Path
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/bzip2/BZip2Constants.java
  
  Index: BZip2Constants.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.bzip2;
  
  /**
   * Base class for both the compress and decompress classes. Holds common arrays,
   * and static data.
   *
   * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a>
   */
  interface BZip2Constants
  {
      int BASE_BLOCK_SIZE = 100000;
      int MAX_ALPHA_SIZE = 258;
      int MAX_CODE_LEN = 23;
      int RUNA = 0;
      int RUNB = 1;
      int N_GROUPS = 6;
      int G_SIZE = 50;
      int N_ITERS = 4;
      int MAX_SELECTORS = ( 2 + ( 900000 / G_SIZE ) );
      int NUM_OVERSHOOT_BYTES = 20;
  
      int RAND_NUMS[] = new int[]
      {
          619, 720, 127, 481, 931, 816, 813, 233, 566, 247,
          985, 724, 205, 454, 863, 491, 741, 242, 949, 214,
          733, 859, 335, 708, 621, 574, 73, 654, 730, 472,
          419, 436, 278, 496, 867, 210, 399, 680, 480, 51,
          878, 465, 811, 169, 869, 675, 611, 697, 867, 561,
          862, 687, 507, 283, 482, 129, 807, 591, 733, 623,
          150, 238, 59, 379, 684, 877, 625, 169, 643, 105,
          170, 607, 520, 932, 727, 476, 693, 425, 174, 647,
          73, 122, 335, 530, 442, 853, 695, 249, 445, 515,
          909, 545, 703, 919, 874, 474, 882, 500, 594, 612,
          641, 801, 220, 162, 819, 984, 589, 513, 495, 799,
          161, 604, 958, 533, 221, 400, 386, 867, 600, 782,
          382, 596, 414, 171, 516, 375, 682, 485, 911, 276,
          98, 553, 163, 354, 666, 933, 424, 341, 533, 870,
          227, 730, 475, 186, 263, 647, 537, 686, 600, 224,
          469, 68, 770, 919, 190, 373, 294, 822, 808, 206,
          184, 943, 795, 384, 383, 461, 404, 758, 839, 887,
          715, 67, 618, 276, 204, 918, 873, 777, 604, 560,
          951, 160, 578, 722, 79, 804, 96, 409, 713, 940,
          652, 934, 970, 447, 318, 353, 859, 672, 112, 785,
          645, 863, 803, 350, 139, 93, 354, 99, 820, 908,
          609, 772, 154, 274, 580, 184, 79, 626, 630, 742,
          653, 282, 762, 623, 680, 81, 927, 626, 789, 125,
          411, 521, 938, 300, 821, 78, 343, 175, 128, 250,
          170, 774, 972, 275, 999, 639, 495, 78, 352, 126,
          857, 956, 358, 619, 580, 124, 737, 594, 701, 612,
          669, 112, 134, 694, 363, 992, 809, 743, 168, 974,
          944, 375, 748, 52, 600, 747, 642, 182, 862, 81,
          344, 805, 988, 739, 511, 655, 814, 334, 249, 515,
          897, 955, 664, 981, 649, 113, 974, 459, 893, 228,
          433, 837, 553, 268, 926, 240, 102, 654, 459, 51,
          686, 754, 806, 760, 493, 403, 415, 394, 687, 700,
          946, 670, 656, 610, 738, 392, 760, 799, 887, 653,
          978, 321, 576, 617, 626, 502, 894, 679, 243, 440,
          680, 879, 194, 572, 640, 724, 926, 56, 204, 700,
          707, 151, 457, 449, 797, 195, 791, 558, 945, 679,
          297, 59, 87, 824, 713, 663, 412, 693, 342, 606,
          134, 108, 571, 364, 631, 212, 174, 643, 304, 329,
          343, 97, 430, 751, 497, 314, 983, 374, 822, 928,
          140, 206, 73, 263, 980, 736, 876, 478, 430, 305,
          170, 514, 364, 692, 829, 82, 855, 953, 676, 246,
          369, 970, 294, 750, 807, 827, 150, 790, 288, 923,
          804, 378, 215, 828, 592, 281, 565, 555, 710, 82,
          896, 831, 547, 261, 524, 462, 293, 465, 502, 56,
          661, 821, 976, 991, 658, 869, 905, 758, 745, 193,
          768, 550, 608, 933, 378, 286, 215, 979, 792, 961,
          61, 688, 793, 644, 986, 403, 106, 366, 905, 644,
          372, 567, 466, 434, 645, 210, 389, 550, 919, 135,
          780, 773, 635, 389, 707, 100, 626, 958, 165, 504,
          920, 176, 193, 713, 857, 265, 203, 50, 668, 108,
          645, 990, 626, 197, 510, 357, 358, 850, 858, 364,
          936, 638
      };
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/bzip2/CBZip2InputStream.java
  
  Index: CBZip2InputStream.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.bzip2;
  
  import java.io.IOException;
  import java.io.InputStream;
  
  /**
   * An input stream that decompresses from the BZip2 format (without the file
   * header chars) to be read as any other stream.
   *
   * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a>
   */
  public class CBZip2InputStream
      extends InputStream
      implements BZip2Constants
  {
      private final static int START_BLOCK_STATE = 1;
      private final static int RAND_PART_A_STATE = 2;
      private final static int RAND_PART_B_STATE = 3;
      private final static int RAND_PART_C_STATE = 4;
      private final static int NO_RAND_PART_A_STATE = 5;
      private final static int NO_RAND_PART_B_STATE = 6;
      private final static int NO_RAND_PART_C_STATE = 7;
  
      private CRC m_crc = new CRC();
      private boolean m_inUse[] = new boolean[ 256 ];
      private char m_seqToUnseq[] = new char[ 256 ];
      private char m_unseqToSeq[] = new char[ 256 ];
      private char m_selector[] = new char[ MAX_SELECTORS ];
      private char m_selectorMtf[] = new char[ MAX_SELECTORS ];
  
      /*
       * freq table collected to save a pass over the data
       * during decompression.
       */
      private int m_unzftab[] = new int[ 256 ];
  
      private int m_limit[][] = new int[ N_GROUPS ][ MAX_ALPHA_SIZE ];
      private int m_base[][] = new int[ N_GROUPS ][ MAX_ALPHA_SIZE ];
      private int m_perm[][] = new int[ N_GROUPS ][ MAX_ALPHA_SIZE ];
      private int m_minLens[] = new int[ N_GROUPS ];
  
      private boolean m_streamEnd;
      private int m_currentChar = -1;
  
      private int m_currentState = START_BLOCK_STATE;
      private int m_rNToGo;
      private int m_rTPos;
      private int m_tPos;
  
      private int i2;
      private int count;
      private int chPrev;
      private int ch2;
      private int j2;
      private char z;
  
      private boolean m_blockRandomised;
  
      /*
       * always: in the range 0 .. 9.
       * The current block size is 100000 * this number.
       */
      private int m_blockSize100k;
      private int m_bsBuff;
      private int m_bsLive;
  
      private InputStream m_input;
  
      private int m_bytesIn;
      private int m_bytesOut;
      private int m_computedBlockCRC;
      private int m_computedCombinedCRC;
  
      /*
       * index of the last char in the block, so
       * the block size == last + 1.
       */
      private int m_last;
      private char[] m_ll8;
      private int m_nInUse;
  
      /*
       * index in zptr[] of original string after sorting.
       */
      private int m_origPtr;
  
      private int m_storedBlockCRC;
      private int m_storedCombinedCRC;
      private int[] m_tt;
  
      public CBZip2InputStream( final InputStream zStream )
      {
          bsSetStream( zStream );
          initialize();
          initBlock();
          setupBlock();
      }
  
      private static void badBlockHeader()
      {
          cadvise();
      }
  
      private static void blockOverrun()
      {
          cadvise();
      }
  
      private static void cadvise()
      {
          System.out.println( "CRC Error" );
          //throw new CCoruptionError();
      }
  
      private static void compressedStreamEOF()
      {
          cadvise();
      }
  
      private static void crcError()
      {
          cadvise();
      }
  
      public int read()
      {
          if( m_streamEnd )
          {
              return -1;
          }
          else
          {
              int retChar = m_currentChar;
              switch( m_currentState )
              {
                  case START_BLOCK_STATE:
                      break;
                  case RAND_PART_A_STATE:
                      break;
                  case RAND_PART_B_STATE:
                      setupRandPartB();
                      break;
                  case RAND_PART_C_STATE:
                      setupRandPartC();
                      break;
                  case NO_RAND_PART_A_STATE:
                      break;
                  case NO_RAND_PART_B_STATE:
                      setupNoRandPartB();
                      break;
                  case NO_RAND_PART_C_STATE:
                      setupNoRandPartC();
                      break;
                  default:
                      break;
              }
              return retChar;
          }
      }
  
      private void setDecompressStructureSizes( int newSize100k )
      {
          if( !( 0 <= newSize100k && newSize100k <= 9 && 0 <= m_blockSize100k
              && m_blockSize100k <= 9 ) )
          {
              // throw new IOException("Invalid block size");
          }
  
          m_blockSize100k = newSize100k;
  
          if( newSize100k == 0 )
              return;
  
          int n = BASE_BLOCK_SIZE * newSize100k;
          m_ll8 = new char[ n ];
          m_tt = new int[ n ];
      }
  
      private void setupBlock()
      {
          int cftab[] = new int[ 257 ];
          char ch;
  
          cftab[ 0 ] = 0;
          for( int i = 1; i <= 256; i++ )
          {
              cftab[ i ] = m_unzftab[ i - 1 ];
          }
          for( int i = 1; i <= 256; i++ )
          {
              cftab[ i ] += cftab[ i - 1 ];
          }
  
          for( int i = 0; i <= m_last; i++ )
          {
              ch = (char)m_ll8[ i ];
              m_tt[ cftab[ ch ] ] = i;
              cftab[ ch ]++;
          }
          cftab = null;
  
          m_tPos = m_tt[ m_origPtr ];
  
          count = 0;
          i2 = 0;
          ch2 = 256;
          /*
           * not a char and not EOF
           */
          if( m_blockRandomised )
          {
              m_rNToGo = 0;
              m_rTPos = 0;
              setupRandPartA();
          }
          else
          {
              setupNoRandPartA();
          }
      }
  
      private void setupNoRandPartA()
      {
          if( i2 <= m_last )
          {
              chPrev = ch2;
              ch2 = m_ll8[ m_tPos ];
              m_tPos = m_tt[ m_tPos ];
              i2++;
  
              m_currentChar = ch2;
              m_currentState = NO_RAND_PART_B_STATE;
              m_crc.updateCRC( ch2 );
          }
          else
          {
              endBlock();
              initBlock();
              setupBlock();
          }
      }
  
      private void setupNoRandPartB()
      {
          if( ch2 != chPrev )
          {
              m_currentState = NO_RAND_PART_A_STATE;
              count = 1;
              setupNoRandPartA();
          }
          else
          {
              count++;
              if( count >= 4 )
              {
                  z = m_ll8[ m_tPos ];
                  m_tPos = m_tt[ m_tPos ];
                  m_currentState = NO_RAND_PART_C_STATE;
                  j2 = 0;
                  setupNoRandPartC();
              }
              else
              {
                  m_currentState = NO_RAND_PART_A_STATE;
                  setupNoRandPartA();
              }
          }
      }
  
      private void setupNoRandPartC()
      {
          if( j2 < (int)z )
          {
              m_currentChar = ch2;
              m_crc.updateCRC( ch2 );
              j2++;
          }
          else
          {
              m_currentState = NO_RAND_PART_A_STATE;
              i2++;
              count = 0;
              setupNoRandPartA();
          }
      }
  
      private void setupRandPartA()
      {
          if( i2 <= m_last )
          {
              chPrev = ch2;
              ch2 = m_ll8[ m_tPos ];
              m_tPos = m_tt[ m_tPos ];
              if( m_rNToGo == 0 )
              {
                  m_rNToGo = RAND_NUMS[ m_rTPos ];
                  m_rTPos++;
                  if( m_rTPos == 512 )
                      m_rTPos = 0;
              }
              m_rNToGo--;
              ch2 ^= (int)( ( m_rNToGo == 1 ) ? 1 : 0 );
              i2++;
  
              m_currentChar = ch2;
              m_currentState = RAND_PART_B_STATE;
              m_crc.updateCRC( ch2 );
          }
          else
          {
              endBlock();
              initBlock();
              setupBlock();
          }
      }
  
      private void setupRandPartB()
      {
          if( ch2 != chPrev )
          {
              m_currentState = RAND_PART_A_STATE;
              count = 1;
              setupRandPartA();
          }
          else
          {
              count++;
              if( count >= 4 )
              {
                  z = m_ll8[ m_tPos ];
                  m_tPos = m_tt[ m_tPos ];
                  if( m_rNToGo == 0 )
                  {
                      m_rNToGo = RAND_NUMS[ m_rTPos ];
                      m_rTPos++;
                      if( m_rTPos == 512 )
                          m_rTPos = 0;
                  }
                  m_rNToGo--;
                  z ^= ( ( m_rNToGo == 1 ) ? 1 : 0 );
                  j2 = 0;
                  m_currentState = RAND_PART_C_STATE;
                  setupRandPartC();
              }
              else
              {
                  m_currentState = RAND_PART_A_STATE;
                  setupRandPartA();
              }
          }
      }
  
      private void setupRandPartC()
      {
          if( j2 < (int)z )
          {
              m_currentChar = ch2;
              m_crc.updateCRC( ch2 );
              j2++;
          }
          else
          {
              m_currentState = RAND_PART_A_STATE;
              i2++;
              count = 0;
              setupRandPartA();
          }
      }
  
      private void getAndMoveToFrontDecode()
      {
          int nextSym;
  
          int limitLast = BASE_BLOCK_SIZE * m_blockSize100k;
          m_origPtr = readVariableSizedInt( 24 );
  
          recvDecodingTables();
          int EOB = m_nInUse + 1;
          int groupNo = -1;
          int groupPos = 0;
  
          /*
           * Setting up the unzftab entries here is not strictly
           * necessary, but it does save having to do it later
           * in a separate pass, and so saves a block's worth of
           * cache misses.
           */
          for( int i = 0; i <= 255; i++ )
          {
              m_unzftab[ i ] = 0;
          }
  
          final char yy[] = new char[ 256 ];
          for( int i = 0; i <= 255; i++ )
          {
              yy[ i ] = (char)i;
          }
  
          m_last = -1;
          int zt;
          int zn;
          int zvec;
          int zj;
          if( groupPos == 0 )
          {
              groupNo++;
              groupPos = G_SIZE;
          }
          groupPos--;
  
          zt = m_selector[ groupNo ];
          zn = m_minLens[ zt ];
          zvec = bsR( zn );
          while( zvec > m_limit[ zt ][ zn ] )
          {
              zn++;
  
              while( m_bsLive < 1 )
              {
                  int zzi;
                  char thech = 0;
                  try
                  {
                      thech = (char)m_input.read();
                  }
                  catch( IOException e )
                  {
                      compressedStreamEOF();
                  }
                  if( thech == -1 )
                  {
                      compressedStreamEOF();
                  }
                  zzi = thech;
                  m_bsBuff = ( m_bsBuff << 8 ) | ( zzi & 0xff );
                  m_bsLive += 8;
              }
  
              zj = ( m_bsBuff >> ( m_bsLive - 1 ) ) & 1;
              m_bsLive--;
  
              zvec = ( zvec << 1 ) | zj;
          }
          nextSym = m_perm[ zt ][ zvec - m_base[ zt ][ zn ] ];
  
          while( true )
          {
              if( nextSym == EOB )
              {
                  break;
              }
  
              if( nextSym == RUNA || nextSym == RUNB )
              {
                  char ch;
                  int s = -1;
                  int N = 1;
                  do
                  {
                      if( nextSym == RUNA )
                          s = s + ( 0 + 1 ) * N;
                      else if( nextSym == RUNB )
                          s = s + ( 1 + 1 ) * N;
                      N = N * 2;
  
                      if( groupPos == 0 )
                      {
                          groupNo++;
                          groupPos = G_SIZE;
                      }
                      groupPos--;
                      zt = m_selector[ groupNo ];
                      zn = m_minLens[ zt ];
                      zvec = bsR( zn );
                      while( zvec > m_limit[ zt ][ zn ] )
                      {
                          zn++;
  
                          while( m_bsLive < 1 )
                          {
                              int zzi;
                              char thech = 0;
                              try
                              {
                                  thech = (char)m_input.read();
                              }
                              catch( IOException e )
                              {
                                  compressedStreamEOF();
                              }
                              if( thech == -1 )
                              {
                                  compressedStreamEOF();
                              }
                              zzi = thech;
                              m_bsBuff = ( m_bsBuff << 8 ) | ( zzi & 0xff );
                              m_bsLive += 8;
                          }
  
                          zj = ( m_bsBuff >> ( m_bsLive - 1 ) ) & 1;
                          m_bsLive--;
                          zvec = ( zvec << 1 ) | zj;
                      }
  
                      nextSym = m_perm[ zt ][ zvec - m_base[ zt ][ zn ] ];
  
                  } while( nextSym == RUNA || nextSym == RUNB );
  
                  s++;
                  ch = m_seqToUnseq[ yy[ 0 ] ];
                  m_unzftab[ ch ] += s;
  
                  while( s > 0 )
                  {
                      m_last++;
                      m_ll8[ m_last ] = ch;
                      s--;
                  }
  
                  if( m_last >= limitLast )
                  {
                      blockOverrun();
                  }
                  continue;
              }
              else
              {
                  char tmp;
                  m_last++;
                  if( m_last >= limitLast )
                  {
                      blockOverrun();
                  }
  
                  tmp = yy[ nextSym - 1 ];
                  m_unzftab[ m_seqToUnseq[ tmp ] ]++;
                  m_ll8[ m_last ] = m_seqToUnseq[ tmp ];
  
                  /*
                   * This loop is hammered during decompression,
                   * hence the unrolling.
                   * for (j = nextSym-1; j > 0; j--) yy[j] = yy[j-1];
                   */
                  int j = nextSym - 1;
                  for( ; j > 3; j -= 4 )
                  {
                      yy[ j ] = yy[ j - 1 ];
                      yy[ j - 1 ] = yy[ j - 2 ];
                      yy[ j - 2 ] = yy[ j - 3 ];
                      yy[ j - 3 ] = yy[ j - 4 ];
                  }
                  for( ; j > 0; j-- )
                  {
                      yy[ j ] = yy[ j - 1 ];
                  }
  
                  yy[ 0 ] = tmp;
  
                  if( groupPos == 0 )
                  {
                      groupNo++;
                      groupPos = G_SIZE;
                  }
                  groupPos--;
                  zt = m_selector[ groupNo ];
                  zn = m_minLens[ zt ];
                  zvec = bsR( zn );
                  while( zvec > m_limit[ zt ][ zn ] )
                  {
                      zn++;
  
                      while( m_bsLive < 1 )
                      {
                          char ch = 0;
                          try
                          {
                              ch = (char)m_input.read();
                          }
                          catch( IOException e )
                          {
                              compressedStreamEOF();
                          }
  
                          m_bsBuff = ( m_bsBuff << 8 ) | ( ch & 0xff );
                          m_bsLive += 8;
                      }
  
                      zj = ( m_bsBuff >> ( m_bsLive - 1 ) ) & 1;
                      m_bsLive--;
  
                      zvec = ( zvec << 1 ) | zj;
                  }
                  nextSym = m_perm[ zt ][ zvec - m_base[ zt ][ zn ] ];
  
                  continue;
              }
          }
      }
  
      private void bsFinishedWithStream()
      {
          m_input = null;
      }
  
      private int readVariableSizedInt( final int numBits )
      {
          return (int)bsR( numBits );
      }
  
      private char readUnsignedChar()
      {
          return (char)bsR( 8 );
      }
  
      private int readInt()
      {
          int u = 0;
          u = ( u << 8 ) | bsR( 8 );
          u = ( u << 8 ) | bsR( 8 );
          u = ( u << 8 ) | bsR( 8 );
          u = ( u << 8 ) | bsR( 8 );
          return u;
      }
  
      private int bsR( final int n )
      {
          while( m_bsLive < n )
          {
              char ch = 0;
              try
              {
                  ch = (char)m_input.read();
              }
              catch( final IOException ioe )
              {
                  compressedStreamEOF();
              }
  
              if( ch == -1 )
              {
                  compressedStreamEOF();
              }
  
              m_bsBuff = ( m_bsBuff << 8 ) | ( ch & 0xff );
              m_bsLive += 8;
          }
  
          final int result = ( m_bsBuff >> ( m_bsLive - n ) ) & ( ( 1 << n ) - 1 );
          m_bsLive -= n;
          return result;
      }
  
      private void bsSetStream( final InputStream input )
      {
          m_input = input;
          m_bsLive = 0;
          m_bsBuff = 0;
          m_bytesOut = 0;
          m_bytesIn = 0;
      }
  
      private void complete()
      {
          m_storedCombinedCRC = readInt();
          if( m_storedCombinedCRC != m_computedCombinedCRC )
          {
              crcError();
          }
  
          bsFinishedWithStream();
          m_streamEnd = true;
      }
  
      private void endBlock()
      {
          m_computedBlockCRC = m_crc.getFinalCRC();
          /*
           * A bad CRC is considered a fatal error.
           */
          if( m_storedBlockCRC != m_computedBlockCRC )
          {
              crcError();
          }
  
          m_computedCombinedCRC = ( m_computedCombinedCRC << 1 )
              | ( m_computedCombinedCRC >>> 31 );
          m_computedCombinedCRC ^= m_computedBlockCRC;
      }
  
      private void hbCreateDecodeTables( final int[] limit,
                                         final int[] base,
                                         final int[] perm,
                                         final char[] length,
                                         final int minLen,
                                         final int maxLen,
                                         final int alphaSize )
      {
          int pp = 0;
          for( int i = minLen; i <= maxLen; i++ )
          {
              for( int j = 0; j < alphaSize; j++ )
              {
                  if( length[ j ] == i )
                  {
                      perm[ pp ] = j;
                      pp++;
                  }
              }
          }
  
          for( int i = 0; i < MAX_CODE_LEN; i++ )
          {
              base[ i ] = 0;
          }
  
          for( int i = 0; i < alphaSize; i++ )
          {
              base[ length[ i ] + 1 ]++;
          }
  
          for( int i = 1; i < MAX_CODE_LEN; i++ )
          {
              base[ i ] += base[ i - 1 ];
          }
  
          for( int i = 0; i < MAX_CODE_LEN; i++ )
          {
              limit[ i ] = 0;
          }
  
          int vec = 0;
          for( int i = minLen; i <= maxLen; i++ )
          {
              vec += ( base[ i + 1 ] - base[ i ] );
              limit[ i ] = vec - 1;
              vec <<= 1;
          }
  
          for( int i = minLen + 1; i <= maxLen; i++ )
          {
              base[ i ] = ( ( limit[ i - 1 ] + 1 ) << 1 ) - base[ i ];
          }
      }
  
      private void initBlock()
      {
          final char magic1 = readUnsignedChar();
          final char magic2 = readUnsignedChar();
          final char magic3 = readUnsignedChar();
          final char magic4 = readUnsignedChar();
          final char magic5 = readUnsignedChar();
          final char magic6 = readUnsignedChar();
          if( magic1 == 0x17 && magic2 == 0x72 && magic3 == 0x45 &&
              magic4 == 0x38 && magic5 == 0x50 && magic6 == 0x90 )
          {
              complete();
              return;
          }
  
          if( magic1 != 0x31 || magic2 != 0x41 || magic3 != 0x59 ||
              magic4 != 0x26 || magic5 != 0x53 || magic6 != 0x59 )
          {
              badBlockHeader();
              m_streamEnd = true;
              return;
          }
  
          m_storedBlockCRC = readInt();
  
          if( bsR( 1 ) == 1 )
          {
              m_blockRandomised = true;
          }
          else
          {
              m_blockRandomised = false;
          }
  
          //        currBlockNo++;
          getAndMoveToFrontDecode();
  
          m_crc.initialiseCRC();
          m_currentState = START_BLOCK_STATE;
      }
  
      private void initialize()
      {
          final char magic3 = readUnsignedChar();
          final char magic4 = readUnsignedChar();
          if( magic3 != 'h' || magic4 < '1' || magic4 > '9' )
          {
              bsFinishedWithStream();
              m_streamEnd = true;
              return;
          }
  
          setDecompressStructureSizes( magic4 - '0' );
          m_computedCombinedCRC = 0;
      }
  
      private void makeMaps()
      {
          m_nInUse = 0;
          for( int i = 0; i < 256; i++ )
          {
              if( m_inUse[ i ] )
              {
                  m_seqToUnseq[ m_nInUse ] = (char)i;
                  m_unseqToSeq[ i ] = (char)m_nInUse;
                  m_nInUse++;
              }
          }
      }
  
      private void recvDecodingTables()
      {
          buildInUseTable();
          makeMaps();
          final int alphaSize = m_nInUse + 2;
  
          /*
           * Now the selectors
           */
          final int groupCount = bsR( 3 );
          final int selectorCount = bsR( 15 );
          for( int i = 0; i < selectorCount; i++ )
          {
              int run = 0;
              while( bsR( 1 ) == 1 )
              {
                  run++;
              }
              m_selectorMtf[ i ] = (char)run;
          }
  
          /*
           * Undo the MTF values for the selectors.
           */
          final char pos[] = new char[ N_GROUPS ];
          for( char v = 0; v < groupCount; v++ )
          {
              pos[ v ] = v;
          }
  
          for( int i = 0; i < selectorCount; i++ )
          {
              int v = m_selectorMtf[ i ];
              final char tmp = pos[ v ];
              while( v > 0 )
              {
                  pos[ v ] = pos[ v - 1 ];
                  v--;
              }
              pos[ 0 ] = tmp;
              m_selector[ i ] = tmp;
          }
  
          final char len[][] = new char[ N_GROUPS ][ MAX_ALPHA_SIZE ];
          /*
           * Now the coding tables
           */
          for( int i = 0; i < groupCount; i++ )
          {
              int curr = bsR( 5 );
              for( int j = 0; j < alphaSize; j++ )
              {
                  while( bsR( 1 ) == 1 )
                  {
                      if( bsR( 1 ) == 0 )
                      {
                          curr++;
                      }
                      else
                      {
                          curr--;
                      }
                  }
                  len[ i ][ j ] = (char)curr;
              }
          }
  
          /*
           * Create the Huffman decoding tables
           */
          for( int k = 0; k < groupCount; k++ )
          {
              int minLen = 32;
              int maxLen = 0;
              for( int i = 0; i < alphaSize; i++ )
              {
                  if( len[ k ][ i ] > maxLen )
                  {
                      maxLen = len[ k ][ i ];
                  }
                  if( len[ k ][ i ] < minLen )
                  {
                      minLen = len[ k ][ i ];
                  }
              }
              hbCreateDecodeTables( m_limit[ k ], m_base[ k ], m_perm[ k ], len[ k ], minLen,
                                    maxLen, alphaSize );
              m_minLens[ k ] = minLen;
          }
      }
  
      private void buildInUseTable()
      {
          final boolean inUse16[] = new boolean[ 16 ];
  
          /*
           * Receive the mapping table
           */
          for( int i = 0; i < 16; i++ )
          {
              if( bsR( 1 ) == 1 )
              {
                  inUse16[ i ] = true;
              }
              else
              {
                  inUse16[ i ] = false;
              }
          }
  
          for( int i = 0; i < 256; i++ )
          {
              m_inUse[ i ] = false;
          }
  
          for( int i = 0; i < 16; i++ )
          {
              if( inUse16[ i ] )
              {
                  for( int j = 0; j < 16; j++ )
                  {
                      if( bsR( 1 ) == 1 )
                      {
                          m_inUse[ i * 16 + j ] = true;
                      }
                  }
              }
          }
      }
  }
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/bzip2/CBZip2OutputStream.java
  
  Index: CBZip2OutputStream.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.bzip2;
  
  import java.io.IOException;
  import java.io.OutputStream;
  
  /**
   * An output stream that compresses into the BZip2 format (without the file
   * header chars) into another stream.
   *
   * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a> TODO: Update to
   *      BZip2 1.0.1
   */
  public class CBZip2OutputStream extends OutputStream implements BZip2Constants
  {
      protected final static int SETMASK = ( 1 << 21 );
      protected final static int CLEARMASK = ( ~SETMASK );
      protected final static int GREATER_ICOST = 15;
      protected final static int LESSER_ICOST = 0;
      protected final static int SMALL_THRESH = 20;
      protected final static int DEPTH_THRESH = 10;
  
      /*
       * If you are ever unlucky/improbable enough
       * to get a stack overflow whilst sorting,
       * increase the following constant and try
       * again.  In practice I have never seen the
       * stack go above 27 elems, so the following
       * limit seems very generous.
       */
      protected final static int QSORT_STACK_SIZE = 1000;
      CRC mCrc = new CRC();
  
      private boolean inUse[] = new boolean[ 256 ];
  
      private char seqToUnseq[] = new char[ 256 ];
      private char unseqToSeq[] = new char[ 256 ];
  
      private char selector[] = new char[ MAX_SELECTORS ];
      private char selectorMtf[] = new char[ MAX_SELECTORS ];
  
      private int mtfFreq[] = new int[ MAX_ALPHA_SIZE ];
  
      private int currentChar = -1;
      private int runLength = 0;
  
      boolean closed = false;
  
      /*
       * Knuth's increments seem to work better
       * than Incerpi-Sedgewick here.  Possibly
       * because the number of elems to sort is
       * usually small, typically <= 20.
       */
      private int incs[] = {1, 4, 13, 40, 121, 364, 1093, 3280,
                            9841, 29524, 88573, 265720,
                            797161, 2391484};
  
      boolean blockRandomised;
  
      /*
       * always: in the range 0 .. 9.
       * The current block size is 100000 * this number.
       */
      int blockSize100k;
      int bsBuff;
      int bsLive;
  
      int bytesIn;
      int bytesOut;
  
      /*
       * index of the last char in the block, so
       * the block size == last + 1.
       */
      int last;
  
      /*
       * index in zptr[] of original string after sorting.
       */
      int origPtr;
  
      private int allowableBlockSize;
  
      private char block[];
  
      private int blockCRC, combinedCRC;
  
      private OutputStream bsStream;
      private boolean firstAttempt;
      private int ftab[];
      private int nBlocksRandomised;
      private int nInUse;
  
      private int nMTF;
      private int quadrant[];
      private short szptr[];
      private int workDone;
  
      /*
       * Used when sorting.  If too many long comparisons
       * happen, we stop sorting, randomise the block
       * slightly, and try again.
       */
      private int workFactor;
      private int workLimit;
      private int zptr[];
  
      public CBZip2OutputStream( OutputStream inStream )
          throws IOException
      {
          this( inStream, 9 );
      }
  
      public CBZip2OutputStream( OutputStream inStream, int inBlockSize )
          throws IOException
      {
          block = null;
          quadrant = null;
          zptr = null;
          ftab = null;
  
          bsSetStream( inStream );
  
          workFactor = 50;
          if( inBlockSize > 9 )
          {
              inBlockSize = 9;
          }
          if( inBlockSize < 1 )
          {
              inBlockSize = 1;
          }
          blockSize100k = inBlockSize;
          allocateCompressStructures();
          initialize();
          initBlock();
      }
  
      protected static void hbMakeCodeLengths( char[] len, int[] freq,
                                               int alphaSize, int maxLen )
      {
          /*
           * Nodes and heap entries run from 1.  Entry 0
           * for both the heap and nodes is a sentinel.
           */
          int nNodes;
          /*
           * Nodes and heap entries run from 1.  Entry 0
           * for both the heap and nodes is a sentinel.
           */
          int nHeap;
          /*
           * Nodes and heap entries run from 1.  Entry 0
           * for both the heap and nodes is a sentinel.
           */
          int n1;
          /*
           * Nodes and heap entries run from 1.  Entry 0
           * for both the heap and nodes is a sentinel.
           */
          int n2;
          /*
           * Nodes and heap entries run from 1.  Entry 0
           * for both the heap and nodes is a sentinel.
           */
          int i;
          /*
           * Nodes and heap entries run from 1.  Entry 0
           * for both the heap and nodes is a sentinel.
           */
          int j;
          /*
           * Nodes and heap entries run from 1.  Entry 0
           * for both the heap and nodes is a sentinel.
           */
          int k;
          boolean tooLong;
  
          int heap[] = new int[ MAX_ALPHA_SIZE + 2 ];
          int weight[] = new int[ MAX_ALPHA_SIZE * 2 ];
          int parent[] = new int[ MAX_ALPHA_SIZE * 2 ];
  
          for( i = 0; i < alphaSize; i++ )
              weight[ i + 1 ] = ( freq[ i ] == 0 ? 1 : freq[ i ] ) << 8;
  
          while( true )
          {
              nNodes = alphaSize;
              nHeap = 0;
  
              heap[ 0 ] = 0;
              weight[ 0 ] = 0;
              parent[ 0 ] = -2;
  
              for( i = 1; i <= alphaSize; i++ )
              {
                  parent[ i ] = -1;
                  nHeap++;
                  heap[ nHeap ] = i;
                  {
                      int zz;
                      int tmp;
                      zz = nHeap;
                      tmp = heap[ zz ];
                      while( weight[ tmp ] < weight[ heap[ zz >> 1 ] ] )
                      {
                          heap[ zz ] = heap[ zz >> 1 ];
                          zz >>= 1;
                      }
                      heap[ zz ] = tmp;
                  }
              }
              if( !( nHeap < ( MAX_ALPHA_SIZE + 2 ) ) )
                  panic();
  
              while( nHeap > 1 )
              {
                  n1 = heap[ 1 ];
                  heap[ 1 ] = heap[ nHeap ];
                  nHeap--;
                  {
                      int zz = 0;
                      int yy = 0;
                      int tmp = 0;
                      zz = 1;
                      tmp = heap[ zz ];
                      while( true )
                      {
                          yy = zz << 1;
                          if( yy > nHeap )
                              break;
                          if( yy < nHeap &&
                              weight[ heap[ yy + 1 ] ] < weight[ heap[ yy ] ] )
                              yy++;
                          if( weight[ tmp ] < weight[ heap[ yy ] ] )
                              break;
                          heap[ zz ] = heap[ yy ];
                          zz = yy;
                      }
                      heap[ zz ] = tmp;
                  }
                  n2 = heap[ 1 ];
                  heap[ 1 ] = heap[ nHeap ];
                  nHeap--;
                  {
                      int zz = 0;
                      int yy = 0;
                      int tmp = 0;
                      zz = 1;
                      tmp = heap[ zz ];
                      while( true )
                      {
                          yy = zz << 1;
                          if( yy > nHeap )
                              break;
                          if( yy < nHeap &&
                              weight[ heap[ yy + 1 ] ] < weight[ heap[ yy ] ] )
                              yy++;
                          if( weight[ tmp ] < weight[ heap[ yy ] ] )
                              break;
                          heap[ zz ] = heap[ yy ];
                          zz = yy;
                      }
                      heap[ zz ] = tmp;
                  }
                  nNodes++;
                  parent[ n1 ] = parent[ n2 ] = nNodes;
  
                  weight[ nNodes ] = ( ( weight[ n1 ] & 0xffffff00 )
                      + ( weight[ n2 ] & 0xffffff00 ) )
                      | ( 1 + ( ( ( weight[ n1 ] & 0x000000ff ) >
                      ( weight[ n2 ] & 0x000000ff ) ) ?
                      ( weight[ n1 ] & 0x000000ff ) :
                      ( weight[ n2 ] & 0x000000ff ) ) );
  
                  parent[ nNodes ] = -1;
                  nHeap++;
                  heap[ nHeap ] = nNodes;
                  {
                      int zz = 0;
                      int tmp = 0;
                      zz = nHeap;
                      tmp = heap[ zz ];
                      while( weight[ tmp ] < weight[ heap[ zz >> 1 ] ] )
                      {
                          heap[ zz ] = heap[ zz >> 1 ];
                          zz >>= 1;
                      }
                      heap[ zz ] = tmp;
                  }
              }
              if( !( nNodes < ( MAX_ALPHA_SIZE * 2 ) ) )
                  panic();
  
              tooLong = false;
              for( i = 1; i <= alphaSize; i++ )
              {
                  j = 0;
                  k = i;
                  while( parent[ k ] >= 0 )
                  {
                      k = parent[ k ];
                      j++;
                  }
                  len[ i - 1 ] = (char)j;
                  if( j > maxLen )
                      tooLong = true;
              }
  
              if( !tooLong )
                  break;
  
              for( i = 1; i < alphaSize; i++ )
              {
                  j = weight[ i ] >> 8;
                  j = 1 + ( j / 2 );
                  weight[ i ] = j << 8;
              }
          }
      }
  
      private static void panic()
      {
          System.out.println( "panic" );
          //throw new CError();
      }
  
      public void close()
          throws IOException
      {
          if( closed )
              return;
  
          if( runLength > 0 )
              writeRun();
          currentChar = -1;
          endBlock();
          endCompression();
          closed = true;
          super.close();
          bsStream.close();
      }
  
      public void finalize()
          throws Throwable
      {
          close();
      }
  
      public void flush()
          throws IOException
      {
          super.flush();
          bsStream.flush();
      }
  
      /**
       * modified by Oliver Merkel, 010128
       *
       * @param bv Description of Parameter
       * @exception IOException Description of Exception
       */
      public void write( int bv )
          throws IOException
      {
          int b = ( 256 + bv ) % 256;
          if( currentChar != -1 )
          {
              if( currentChar == b )
              {
                  runLength++;
                  if( runLength > 254 )
                  {
                      writeRun();
                      currentChar = -1;
                      runLength = 0;
                  }
              }
              else
              {
                  writeRun();
                  runLength = 1;
                  currentChar = b;
              }
          }
          else
          {
              currentChar = b;
              runLength++;
          }
      }
  
      private void allocateCompressStructures()
      {
          int n = BASE_BLOCK_SIZE * blockSize100k;
          block = new char[ ( n + 1 + NUM_OVERSHOOT_BYTES ) ];
          quadrant = new int[ ( n + NUM_OVERSHOOT_BYTES ) ];
          zptr = new int[ n ];
          ftab = new int[ 65537 ];
  
          if( block == null || quadrant == null || zptr == null
              || ftab == null )
          {
              //int totalDraw = (n + 1 + NUM_OVERSHOOT_BYTES) + (n + NUM_OVERSHOOT_BYTES) + n + 65537;
              //compressOutOfMemory ( totalDraw, n );
          }
  
          /*
           * The back end needs a place to store the MTF values
           * whilst it calculates the coding tables.  We could
           * put them in the zptr array.  However, these values
           * will fit in a short, so we overlay szptr at the
           * start of zptr, in the hope of reducing the number
           * of cache misses induced by the multiple traversals
           * of the MTF values when calculating coding tables.
           * Seems to improve compression speed by about 1%.
           */
          //    szptr = zptr;
  
          szptr = new short[ 2 * n ];
      }
  
      private void bsFinishedWithStream()
          throws IOException
      {
          while( bsLive > 0 )
          {
              int ch = ( bsBuff >> 24 );
              try
              {
                  bsStream.write( ch );// write 8-bit
              }
              catch( IOException e )
              {
                  throw e;
              }
              bsBuff <<= 8;
              bsLive -= 8;
              bytesOut++;
          }
      }
  
      private void bsPutIntVS( int numBits, int c )
          throws IOException
      {
          bsW( numBits, c );
      }
  
      private void bsPutUChar( int c )
          throws IOException
      {
          bsW( 8, c );
      }
  
      private void bsPutint( int u )
          throws IOException
      {
          bsW( 8, ( u >> 24 ) & 0xff );
          bsW( 8, ( u >> 16 ) & 0xff );
          bsW( 8, ( u >> 8 ) & 0xff );
          bsW( 8, u & 0xff );
      }
  
      private void bsSetStream( OutputStream f )
      {
          bsStream = f;
          bsLive = 0;
          bsBuff = 0;
          bytesOut = 0;
          bytesIn = 0;
      }
  
      private void bsW( int n, int v )
          throws IOException
      {
          while( bsLive >= 8 )
          {
              int ch = ( bsBuff >> 24 );
              try
              {
                  bsStream.write( ch );// write 8-bit
              }
              catch( IOException e )
              {
                  throw e;
              }
              bsBuff <<= 8;
              bsLive -= 8;
              bytesOut++;
          }
          bsBuff |= ( v << ( 32 - bsLive - n ) );
          bsLive += n;
      }
  
      private void doReversibleTransformation()
      {
          int i;
  
          workLimit = workFactor * last;
          workDone = 0;
          blockRandomised = false;
          firstAttempt = true;
  
          mainSort();
  
          if( workDone > workLimit && firstAttempt )
          {
              randomiseBlock();
              workLimit = workDone = 0;
              blockRandomised = true;
              firstAttempt = false;
              mainSort();
          }
  
          origPtr = -1;
          for( i = 0; i <= last; i++ )
              if( zptr[ i ] == 0 )
              {
                  origPtr = i;
                  break;
              }
          ;
  
          if( origPtr == -1 )
              panic();
      }
  
      private void endBlock()
          throws IOException
      {
          blockCRC = mCrc.getFinalCRC();
          combinedCRC = ( combinedCRC << 1 ) | ( combinedCRC >>> 31 );
          combinedCRC ^= blockCRC;
  
          /*
           * sort the block and establish posn of original string
           */
          doReversibleTransformation();
  
          /*
           * A 6-byte block header, the value chosen arbitrarily
           * as 0x314159265359 :-).  A 32 bit value does not really
           * give a strong enough guarantee that the value will not
           * appear by chance in the compressed datastream.  Worst-case
           * probability of this event, for a 900k block, is about
           * 2.0e-3 for 32 bits, 1.0e-5 for 40 bits and 4.0e-8 for 48 bits.
           * For a compressed file of size 100Gb -- about 100000 blocks --
           * only a 48-bit marker will do.  NB: normal compression/
           * decompression do *not* rely on these statistical properties.
           * They are only important when trying to recover blocks from
           * damaged files.
           */
          bsPutUChar( 0x31 );
          bsPutUChar( 0x41 );
          bsPutUChar( 0x59 );
          bsPutUChar( 0x26 );
          bsPutUChar( 0x53 );
          bsPutUChar( 0x59 );
  
          /*
           * Now the block's CRC, so it is in a known place.
           */
          bsPutint( blockCRC );
  
          /*
           * Now a single bit indicating randomisation.
           */
          if( blockRandomised )
          {
              bsW( 1, 1 );
              nBlocksRandomised++;
          }
          else
          {
              bsW( 1, 0 );
          }
  
          /*
           * Finally, block's contents proper.
           */
          moveToFrontCodeAndSend();
      }
  
      private void endCompression()
          throws IOException
      {
          /*
           * Now another magic 48-bit number, 0x177245385090, to
           * indicate the end of the last block.  (sqrt(pi), if
           * you want to know.  I did want to use e, but it contains
           * too much repetition -- 27 18 28 18 28 46 -- for me
           * to feel statistically comfortable.  Call me paranoid.)
           */
          bsPutUChar( 0x17 );
          bsPutUChar( 0x72 );
          bsPutUChar( 0x45 );
          bsPutUChar( 0x38 );
          bsPutUChar( 0x50 );
          bsPutUChar( 0x90 );
  
          bsPutint( combinedCRC );
  
          bsFinishedWithStream();
      }
  
      private boolean fullGtU( int i1, int i2 )
      {
          int k;
          char c1;
          char c2;
          int s1;
          int s2;
  
          c1 = block[ i1 + 1 ];
          c2 = block[ i2 + 1 ];
          if( c1 != c2 )
              return ( c1 > c2 );
          i1++;
          i2++;
  
          c1 = block[ i1 + 1 ];
          c2 = block[ i2 + 1 ];
          if( c1 != c2 )
              return ( c1 > c2 );
          i1++;
          i2++;
  
          c1 = block[ i1 + 1 ];
          c2 = block[ i2 + 1 ];
          if( c1 != c2 )
              return ( c1 > c2 );
          i1++;
          i2++;
  
          c1 = block[ i1 + 1 ];
          c2 = block[ i2 + 1 ];
          if( c1 != c2 )
              return ( c1 > c2 );
          i1++;
          i2++;
  
          c1 = block[ i1 + 1 ];
          c2 = block[ i2 + 1 ];
          if( c1 != c2 )
              return ( c1 > c2 );
          i1++;
          i2++;
  
          c1 = block[ i1 + 1 ];
          c2 = block[ i2 + 1 ];
          if( c1 != c2 )
              return ( c1 > c2 );
          i1++;
          i2++;
  
          k = last + 1;
  
          do
          {
              c1 = block[ i1 + 1 ];
              c2 = block[ i2 + 1 ];
              if( c1 != c2 )
                  return ( c1 > c2 );
              s1 = quadrant[ i1 ];
              s2 = quadrant[ i2 ];
              if( s1 != s2 )
                  return ( s1 > s2 );
              i1++;
              i2++;
  
              c1 = block[ i1 + 1 ];
              c2 = block[ i2 + 1 ];
              if( c1 != c2 )
                  return ( c1 > c2 );
              s1 = quadrant[ i1 ];
              s2 = quadrant[ i2 ];
              if( s1 != s2 )
                  return ( s1 > s2 );
              i1++;
              i2++;
  
              c1 = block[ i1 + 1 ];
              c2 = block[ i2 + 1 ];
              if( c1 != c2 )
                  return ( c1 > c2 );
              s1 = quadrant[ i1 ];
              s2 = quadrant[ i2 ];
              if( s1 != s2 )
                  return ( s1 > s2 );
              i1++;
              i2++;
  
              c1 = block[ i1 + 1 ];
              c2 = block[ i2 + 1 ];
              if( c1 != c2 )
                  return ( c1 > c2 );
              s1 = quadrant[ i1 ];
              s2 = quadrant[ i2 ];
              if( s1 != s2 )
                  return ( s1 > s2 );
              i1++;
              i2++;
  
              if( i1 > last )
              {
                  i1 -= last;
                  i1--;
              }
              ;
              if( i2 > last )
              {
                  i2 -= last;
                  i2--;
              }
              ;
  
              k -= 4;
              workDone++;
          } while( k >= 0 );
  
          return false;
      }
  
      private void generateMTFValues()
      {
          char yy[] = new char[ 256 ];
          int i;
          int j;
          char tmp;
          char tmp2;
          int zPend;
          int wr;
          int EOB;
  
          makeMaps();
          EOB = nInUse + 1;
  
          for( i = 0; i <= EOB; i++ )
              mtfFreq[ i ] = 0;
  
          wr = 0;
          zPend = 0;
          for( i = 0; i < nInUse; i++ )
              yy[ i ] = (char)i;
  
          for( i = 0; i <= last; i++ )
          {
              char ll_i;
  
              ll_i = unseqToSeq[ block[ zptr[ i ] ] ];
  
              j = 0;
              tmp = yy[ j ];
              while( ll_i != tmp )
              {
                  j++;
                  tmp2 = tmp;
                  tmp = yy[ j ];
                  yy[ j ] = tmp2;
              }
              ;
              yy[ 0 ] = tmp;
  
              if( j == 0 )
              {
                  zPend++;
              }
              else
              {
                  if( zPend > 0 )
                  {
                      zPend--;
                      while( true )
                      {
                          switch( zPend % 2 )
                          {
                              case 0:
                                  szptr[ wr ] = (short)RUNA;
                                  wr++;
                                  mtfFreq[ RUNA ]++;
                                  break;
                              case 1:
                                  szptr[ wr ] = (short)RUNB;
                                  wr++;
                                  mtfFreq[ RUNB ]++;
                                  break;
                          }
                          ;
                          if( zPend < 2 )
                              break;
                          zPend = ( zPend - 2 ) / 2;
                      }
                      ;
                      zPend = 0;
                  }
                  szptr[ wr ] = (short)( j + 1 );
                  wr++;
                  mtfFreq[ j + 1 ]++;
              }
          }
  
          if( zPend > 0 )
          {
              zPend--;
              while( true )
              {
                  switch( zPend % 2 )
                  {
                      case 0:
                          szptr[ wr ] = (short)RUNA;
                          wr++;
                          mtfFreq[ RUNA ]++;
                          break;
                      case 1:
                          szptr[ wr ] = (short)RUNB;
                          wr++;
                          mtfFreq[ RUNB ]++;
                          break;
                  }
                  if( zPend < 2 )
                      break;
                  zPend = ( zPend - 2 ) / 2;
              }
          }
  
          szptr[ wr ] = (short)EOB;
          wr++;
          mtfFreq[ EOB ]++;
  
          nMTF = wr;
      }
  
      private void hbAssignCodes( int[] code, char[] length, int minLen,
                                  int maxLen, int alphaSize )
      {
          int n;
          int vec;
          int i;
  
          vec = 0;
          for( n = minLen; n <= maxLen; n++ )
          {
              for( i = 0; i < alphaSize; i++ )
                  if( length[ i ] == n )
                  {
                      code[ i ] = vec;
                      vec++;
                  }
              ;
              vec <<= 1;
          }
      }
  
      private void initBlock()
      {
          //        blockNo++;
          mCrc.initialiseCRC();
          last = -1;
          //        ch = 0;
  
          for( int i = 0; i < 256; i++ )
              inUse[ i ] = false;
  
          /*
           * 20 is just a paranoia constant
           */
          allowableBlockSize = BASE_BLOCK_SIZE * blockSize100k - 20;
      }
  
      private void initialize()
          throws IOException
      {
          bytesIn = 0;
          bytesOut = 0;
          nBlocksRandomised = 0;
  
          /*
           * Write `magic' bytes h indicating file-format == huffmanised,
           * followed by a digit indicating blockSize100k.
           */
          bsPutUChar( 'h' );
          bsPutUChar( '0' + blockSize100k );
  
          combinedCRC = 0;
      }
  
      private void mainSort()
      {
          int i;
          int j;
          int ss;
          int sb;
          int runningOrder[] = new int[ 256 ];
          int copy[] = new int[ 256 ];
          boolean bigDone[] = new boolean[ 256 ];
          int c1;
          int c2;
          int numQSorted;
  
          /*
           * In the various block-sized structures, live data runs
           * from 0 to last+NUM_OVERSHOOT_BYTES inclusive.  First,
           * set up the overshoot area for block.
           */
          //   if (verbosity >= 4) fprintf ( stderr, "        sort initialise ...\n" );
          for( i = 0; i < NUM_OVERSHOOT_BYTES; i++ )
              block[ last + i + 2 ] = block[ ( i % ( last + 1 ) ) + 1 ];
          for( i = 0; i <= last + NUM_OVERSHOOT_BYTES; i++ )
              quadrant[ i ] = 0;
  
          block[ 0 ] = (char)( block[ last + 1 ] );
  
          if( last < 4000 )
          {
              /*
               * Use simpleSort(), since the full sorting mechanism
               * has quite a large constant overhead.
               */
              for( i = 0; i <= last; i++ )
                  zptr[ i ] = i;
              firstAttempt = false;
              workDone = workLimit = 0;
              simpleSort( 0, last, 0 );
          }
          else
          {
              numQSorted = 0;
              for( i = 0; i <= 255; i++ )
                  bigDone[ i ] = false;
  
              for( i = 0; i <= 65536; i++ )
                  ftab[ i ] = 0;
  
              c1 = block[ 0 ];
              for( i = 0; i <= last; i++ )
              {
                  c2 = block[ i + 1 ];
                  ftab[ ( c1 << 8 ) + c2 ]++;
                  c1 = c2;
              }
  
              for( i = 1; i <= 65536; i++ )
                  ftab[ i ] += ftab[ i - 1 ];
  
              c1 = block[ 1 ];
              for( i = 0; i < last; i++ )
              {
                  c2 = block[ i + 2 ];
                  j = ( c1 << 8 ) + c2;
                  c1 = c2;
                  ftab[ j ]--;
                  zptr[ ftab[ j ] ] = i;
              }
  
              j = ( ( block[ last + 1 ] ) << 8 ) + ( block[ 1 ] );
              ftab[ j ]--;
              zptr[ ftab[ j ] ] = last;
  
              /*
               * Now ftab contains the first loc of every small bucket.
               * Calculate the running order, from smallest to largest
               * big bucket.
               */
              for( i = 0; i <= 255; i++ )
                  runningOrder[ i ] = i;
              {
                  int vv;
                  int h = 1;
                  do
                      h = 3 * h + 1;
                  while( h <= 256 );
                  do
                  {
                      h = h / 3;
                      for( i = h; i <= 255; i++ )
                      {
                          vv = runningOrder[ i ];
                          j = i;
                          while( ( ftab[ ( ( runningOrder[ j - h ] ) + 1 ) << 8 ]
                              - ftab[ ( runningOrder[ j - h ] ) << 8 ] ) >
                              ( ftab[ ( ( vv ) + 1 ) << 8 ] - ftab[ ( vv ) << 8 ] ) )
                          {
                              runningOrder[ j ] = runningOrder[ j - h ];
                              j = j - h;
                              if( j <= ( h - 1 ) )
                                  break;
                          }
                          runningOrder[ j ] = vv;
                      }
                  } while( h != 1 );
              }
  
              /*
               * The main sorting loop.
               */
              for( i = 0; i <= 255; i++ )
              {
  
                  /*
                   * Process big buckets, starting with the least full.
                   */
                  ss = runningOrder[ i ];
  
                  /*
                   * Complete the big bucket [ss] by quicksorting
                   * any unsorted small buckets [ss, j].  Hopefully
                   * previous pointer-scanning phases have already
                   * completed many of the small buckets [ss, j], so
                   * we don't have to sort them at all.
                   */
                  for( j = 0; j <= 255; j++ )
                  {
                      sb = ( ss << 8 ) + j;
                      if( !( ( ftab[ sb ] & SETMASK ) == SETMASK ) )
                      {
                          int lo = ftab[ sb ] & CLEARMASK;
                          int hi = ( ftab[ sb + 1 ] & CLEARMASK ) - 1;
                          if( hi > lo )
                          {
                              qSort3( lo, hi, 2 );
                              numQSorted += ( hi - lo + 1 );
                              if( workDone > workLimit && firstAttempt )
                                  return;
                          }
                          ftab[ sb ] |= SETMASK;
                      }
                  }
  
                  /*
                   * The ss big bucket is now done.  Record this fact,
                   * and update the quadrant descriptors.  Remember to
                   * update quadrants in the overshoot area too, if
                   * necessary.  The "if (i < 255)" test merely skips
                   * this updating for the last bucket processed, since
                   * updating for the last bucket is pointless.
                   */
                  bigDone[ ss ] = true;
  
                  if( i < 255 )
                  {
                      int bbStart = ftab[ ss << 8 ] & CLEARMASK;
                      int bbSize = ( ftab[ ( ss + 1 ) << 8 ] & CLEARMASK ) - bbStart;
                      int shifts = 0;
  
                      while( ( bbSize >> shifts ) > 65534 )
                          shifts++;
  
                      for( j = 0; j < bbSize; j++ )
                      {
                          int a2update = zptr[ bbStart + j ];
                          int qVal = ( j >> shifts );
                          quadrant[ a2update ] = qVal;
                          if( a2update < NUM_OVERSHOOT_BYTES )
                              quadrant[ a2update + last + 1 ] = qVal;
                      }
  
                      if( !( ( ( bbSize - 1 ) >> shifts ) <= 65535 ) )
                          panic();
                  }
  
                  /*
                   * Now scan this big bucket so as to synthesise the
                   * sorted order for small buckets [t, ss] for all t != ss.
                   */
                  for( j = 0; j <= 255; j++ )
                      copy[ j ] = ftab[ ( j << 8 ) + ss ] & CLEARMASK;
  
                  for( j = ftab[ ss << 8 ] & CLEARMASK;
                       j < ( ftab[ ( ss + 1 ) << 8 ] & CLEARMASK ); j++ )
                  {
                      c1 = block[ zptr[ j ] ];
                      if( !bigDone[ c1 ] )
                      {
                          zptr[ copy[ c1 ] ] = zptr[ j ] == 0 ? last : zptr[ j ] - 1;
                          copy[ c1 ]++;
                      }
                  }
  
                  for( j = 0; j <= 255; j++ )
                      ftab[ ( j << 8 ) + ss ] |= SETMASK;
              }
          }
      }
  
      private void makeMaps()
      {
          int i;
          nInUse = 0;
          for( i = 0; i < 256; i++ )
              if( inUse[ i ] )
              {
                  seqToUnseq[ nInUse ] = (char)i;
                  unseqToSeq[ i ] = (char)nInUse;
                  nInUse++;
              }
      }
  
      private char med3( char a, char b, char c )
      {
          char t;
          if( a > b )
          {
              t = a;
              a = b;
              b = t;
          }
          if( b > c )
          {
              t = b;
              b = c;
              c = t;
          }
          if( a > b )
              b = a;
          return b;
      }
  
      private void moveToFrontCodeAndSend()
          throws IOException
      {
          bsPutIntVS( 24, origPtr );
          generateMTFValues();
          sendMTFValues();
      }
  
      private void qSort3( int loSt, int hiSt, int dSt )
      {
          int unLo;
          int unHi;
          int ltLo;
          int gtHi;
          int med;
          int n;
          int m;
          int sp;
          int lo;
          int hi;
          int d;
          StackElem[] stack = new StackElem[ QSORT_STACK_SIZE ];
          for( int count = 0; count < QSORT_STACK_SIZE; count++ )
              stack[ count ] = new StackElem();
  
          sp = 0;
  
          stack[ sp ].ll = loSt;
          stack[ sp ].hh = hiSt;
          stack[ sp ].dd = dSt;
          sp++;
  
          while( sp > 0 )
          {
              if( sp >= QSORT_STACK_SIZE )
                  panic();
  
              sp--;
              lo = stack[ sp ].ll;
              hi = stack[ sp ].hh;
              d = stack[ sp ].dd;
  
              if( hi - lo < SMALL_THRESH || d > DEPTH_THRESH )
              {
                  simpleSort( lo, hi, d );
                  if( workDone > workLimit && firstAttempt )
                      return;
                  continue;
              }
  
              med = med3( block[ zptr[ lo ] + d + 1 ],
                          block[ zptr[ hi ] + d + 1 ],
                          block[ zptr[ ( lo + hi ) >> 1 ] + d + 1 ] );
  
              unLo = ltLo = lo;
              unHi = gtHi = hi;
  
              while( true )
              {
                  while( true )
                  {
                      if( unLo > unHi )
                          break;
                      n = ( (int)block[ zptr[ unLo ] + d + 1 ] ) - med;
                      if( n == 0 )
                      {
                          int temp = 0;
                          temp = zptr[ unLo ];
                          zptr[ unLo ] = zptr[ ltLo ];
                          zptr[ ltLo ] = temp;
                          ltLo++;
                          unLo++;
                          continue;
                      }
                      ;
                      if( n > 0 )
                          break;
                      unLo++;
                  }
                  while( true )
                  {
                      if( unLo > unHi )
                          break;
                      n = ( (int)block[ zptr[ unHi ] + d + 1 ] ) - med;
                      if( n == 0 )
                      {
                          int temp = 0;
                          temp = zptr[ unHi ];
                          zptr[ unHi ] = zptr[ gtHi ];
                          zptr[ gtHi ] = temp;
                          gtHi--;
                          unHi--;
                          continue;
                      }
                      ;
                      if( n < 0 )
                          break;
                      unHi--;
                  }
                  if( unLo > unHi )
                      break;
                  int temp = 0;
                  temp = zptr[ unLo ];
                  zptr[ unLo ] = zptr[ unHi ];
                  zptr[ unHi ] = temp;
                  unLo++;
                  unHi--;
              }
  
              if( gtHi < ltLo )
              {
                  stack[ sp ].ll = lo;
                  stack[ sp ].hh = hi;
                  stack[ sp ].dd = d + 1;
                  sp++;
                  continue;
              }
  
              n = ( ( ltLo - lo ) < ( unLo - ltLo ) ) ? ( ltLo - lo ) : ( unLo - ltLo );
              vswap( lo, unLo - n, n );
              m = ( ( hi - gtHi ) < ( gtHi - unHi ) ) ? ( hi - gtHi ) : ( gtHi - unHi );
              vswap( unLo, hi - m + 1, m );
  
              n = lo + unLo - ltLo - 1;
              m = hi - ( gtHi - unHi ) + 1;
  
              stack[ sp ].ll = lo;
              stack[ sp ].hh = n;
              stack[ sp ].dd = d;
              sp++;
  
              stack[ sp ].ll = n + 1;
              stack[ sp ].hh = m - 1;
              stack[ sp ].dd = d + 1;
              sp++;
  
              stack[ sp ].ll = m;
              stack[ sp ].hh = hi;
              stack[ sp ].dd = d;
              sp++;
          }
      }
  
      private void randomiseBlock()
      {
          int i;
          int rNToGo = 0;
          int rTPos = 0;
          for( i = 0; i < 256; i++ )
              inUse[ i ] = false;
  
          for( i = 0; i <= last; i++ )
          {
              if( rNToGo == 0 )
              {
                  rNToGo = (char)RAND_NUMS[ rTPos ];
                  rTPos++;
                  if( rTPos == 512 )
                      rTPos = 0;
              }
              rNToGo--;
              block[ i + 1 ] ^= ( ( rNToGo == 1 ) ? 1 : 0 );
              // handle 16 bit signed numbers
              block[ i + 1 ] &= 0xFF;
  
              inUse[ block[ i + 1 ] ] = true;
          }
      }
  
      private void sendMTFValues()
          throws IOException
      {
          char len[][] = new char[ N_GROUPS ][ MAX_ALPHA_SIZE ];
  
          int v;
  
          int t;
  
          int i;
  
          int j;
  
          int gs;
  
          int ge;
  
          int totc;
  
          int bt;
  
          int bc;
  
          int iter;
          int nSelectors = 0;
          int alphaSize;
          int minLen;
          int maxLen;
          int selCtr;
          int nGroups;
          int nBytes;
  
          alphaSize = nInUse + 2;
          for( t = 0; t < N_GROUPS; t++ )
              for( v = 0; v < alphaSize; v++ )
                  len[ t ][ v ] = (char)GREATER_ICOST;
  
          /*
           * Decide how many coding tables to use
           */
          if( nMTF <= 0 )
              panic();
  
          if( nMTF < 200 )
              nGroups = 2;
          else if( nMTF < 600 )
              nGroups = 3;
          else if( nMTF < 1200 )
              nGroups = 4;
          else if( nMTF < 2400 )
              nGroups = 5;
          else
              nGroups = 6;
          {
              /*
               * Generate an initial set of coding tables
               */
              int nPart;
              int remF;
              int tFreq;
              int aFreq;
  
              nPart = nGroups;
              remF = nMTF;
              gs = 0;
              while( nPart > 0 )
              {
                  tFreq = remF / nPart;
                  ge = gs - 1;
                  aFreq = 0;
                  while( aFreq < tFreq && ge < alphaSize - 1 )
                  {
                      ge++;
                      aFreq += mtfFreq[ ge ];
                  }
  
                  if( ge > gs && nPart != nGroups && nPart != 1
                      && ( ( nGroups - nPart ) % 2 == 1 ) )
                  {
                      aFreq -= mtfFreq[ ge ];
                      ge--;
                  }
  
                  for( v = 0; v < alphaSize; v++ )
                      if( v >= gs && v <= ge )
                          len[ nPart - 1 ][ v ] = (char)LESSER_ICOST;
                      else
                          len[ nPart - 1 ][ v ] = (char)GREATER_ICOST;
  
                  nPart--;
                  gs = ge + 1;
                  remF -= aFreq;
              }
          }
  
          int rfreq[][] = new int[ N_GROUPS ][ MAX_ALPHA_SIZE ];
          int fave[] = new int[ N_GROUPS ];
          short cost[] = new short[ N_GROUPS ];
          /*
           * Iterate up to N_ITERS times to improve the tables.
           */
          for( iter = 0; iter < N_ITERS; iter++ )
          {
              for( t = 0; t < nGroups; t++ )
                  fave[ t ] = 0;
  
              for( t = 0; t < nGroups; t++ )
                  for( v = 0; v < alphaSize; v++ )
                      rfreq[ t ][ v ] = 0;
  
              nSelectors = 0;
              totc = 0;
              gs = 0;
              while( true )
              {
  
                  /*
                   * Set group start & end marks.
                   */
                  if( gs >= nMTF )
                      break;
                  ge = gs + G_SIZE - 1;
                  if( ge >= nMTF )
                      ge = nMTF - 1;
  
                  /*
                   * Calculate the cost of this group as coded
                   * by each of the coding tables.
                   */
                  for( t = 0; t < nGroups; t++ )
                      cost[ t ] = 0;
  
                  if( nGroups == 6 )
                  {
                      short cost0;
                      short cost1;
                      short cost2;
                      short cost3;
                      short cost4;
                      short cost5;
                      cost0 = cost1 = cost2 = cost3 = cost4 = cost5 = 0;
                      for( i = gs; i <= ge; i++ )
                      {
                          short icv = szptr[ i ];
                          cost0 += len[ 0 ][ icv ];
                          cost1 += len[ 1 ][ icv ];
                          cost2 += len[ 2 ][ icv ];
                          cost3 += len[ 3 ][ icv ];
                          cost4 += len[ 4 ][ icv ];
                          cost5 += len[ 5 ][ icv ];
                      }
                      cost[ 0 ] = cost0;
                      cost[ 1 ] = cost1;
                      cost[ 2 ] = cost2;
                      cost[ 3 ] = cost3;
                      cost[ 4 ] = cost4;
                      cost[ 5 ] = cost5;
                  }
                  else
                  {
                      for( i = gs; i <= ge; i++ )
                      {
                          short icv = szptr[ i ];
                          for( t = 0; t < nGroups; t++ )
                              cost[ t ] += len[ t ][ icv ];
                      }
                  }
  
                  /*
                   * Find the coding table which is best for this group,
                   * and record its identity in the selector table.
                   */
                  bc = 999999999;
                  bt = -1;
                  for( t = 0; t < nGroups; t++ )
                      if( cost[ t ] < bc )
                      {
                          bc = cost[ t ];
                          bt = t;
                      }
                  ;
                  totc += bc;
                  fave[ bt ]++;
                  selector[ nSelectors ] = (char)bt;
                  nSelectors++;
  
                  /*
                   * Increment the symbol frequencies for the selected table.
                   */
                  for( i = gs; i <= ge; i++ )
                      rfreq[ bt ][ szptr[ i ] ]++;
  
                  gs = ge + 1;
              }
  
              /*
               * Recompute the tables based on the accumulated frequencies.
               */
              for( t = 0; t < nGroups; t++ )
                  hbMakeCodeLengths( len[ t ], rfreq[ t ], alphaSize, 20 );
          }
  
          rfreq = null;
          fave = null;
          cost = null;
  
          if( !( nGroups < 8 ) )
              panic();
          if( !( nSelectors < 32768 && nSelectors <= ( 2 + ( 900000 / G_SIZE ) ) ) )
              panic();
          {
              /*
               * Compute MTF values for the selectors.
               */
              char pos[] = new char[ N_GROUPS ];
              char ll_i;
              char tmp2;
              char tmp;
              for( i = 0; i < nGroups; i++ )
                  pos[ i ] = (char)i;
              for( i = 0; i < nSelectors; i++ )
              {
                  ll_i = selector[ i ];
                  j = 0;
                  tmp = pos[ j ];
                  while( ll_i != tmp )
                  {
                      j++;
                      tmp2 = tmp;
                      tmp = pos[ j ];
                      pos[ j ] = tmp2;
                  }
                  pos[ 0 ] = tmp;
                  selectorMtf[ i ] = (char)j;
              }
          }
  
          int code[][] = new int[ N_GROUPS ][ MAX_ALPHA_SIZE ];
  
          /*
           * Assign actual codes for the tables.
           */
          for( t = 0; t < nGroups; t++ )
          {
              minLen = 32;
              maxLen = 0;
              for( i = 0; i < alphaSize; i++ )
              {
                  if( len[ t ][ i ] > maxLen )
                      maxLen = len[ t ][ i ];
                  if( len[ t ][ i ] < minLen )
                      minLen = len[ t ][ i ];
              }
              if( maxLen > 20 )
                  panic();
              if( minLen < 1 )
                  panic();
              hbAssignCodes( code[ t ], len[ t ], minLen, maxLen, alphaSize );
          }
          {
              /*
               * Transmit the mapping table.
               */
              boolean inUse16[] = new boolean[ 16 ];
              for( i = 0; i < 16; i++ )
              {
                  inUse16[ i ] = false;
                  for( j = 0; j < 16; j++ )
                      if( inUse[ i * 16 + j ] )
                          inUse16[ i ] = true;
              }
  
              nBytes = bytesOut;
              for( i = 0; i < 16; i++ )
                  if( inUse16[ i ] )
                      bsW( 1, 1 );
                  else
                      bsW( 1, 0 );
  
              for( i = 0; i < 16; i++ )
                  if( inUse16[ i ] )
                      for( j = 0; j < 16; j++ )
                          if( inUse[ i * 16 + j ] )
                              bsW( 1, 1 );
                          else
                              bsW( 1, 0 );
  
          }
  
          /*
           * Now the selectors.
           */
          nBytes = bytesOut;
          bsW( 3, nGroups );
          bsW( 15, nSelectors );
          for( i = 0; i < nSelectors; i++ )
          {
              for( j = 0; j < selectorMtf[ i ]; j++ )
                  bsW( 1, 1 );
              bsW( 1, 0 );
          }
  
          /*
           * Now the coding tables.
           */
          nBytes = bytesOut;
  
          for( t = 0; t < nGroups; t++ )
          {
              int curr = len[ t ][ 0 ];
              bsW( 5, curr );
              for( i = 0; i < alphaSize; i++ )
              {
                  while( curr < len[ t ][ i ] )
                  {
                      bsW( 2, 2 );
                      curr++;
                      /*
                       * 10
                       */
                  }
                  while( curr > len[ t ][ i ] )
                  {
                      bsW( 2, 3 );
                      curr--;
                      /*
                       * 11
                       */
                  }
                  bsW( 1, 0 );
              }
          }
  
          /*
           * And finally, the block data proper
           */
          nBytes = bytesOut;
          selCtr = 0;
          gs = 0;
          while( true )
          {
              if( gs >= nMTF )
                  break;
              ge = gs + G_SIZE - 1;
              if( ge >= nMTF )
                  ge = nMTF - 1;
              for( i = gs; i <= ge; i++ )
              {
                  bsW( len[ selector[ selCtr ] ][ szptr[ i ] ],
                       code[ selector[ selCtr ] ][ szptr[ i ] ] );
              }
  
              gs = ge + 1;
              selCtr++;
          }
          if( !( selCtr == nSelectors ) )
              panic();
      }
  
      private void simpleSort( int lo, int hi, int d )
      {
          int i;
          int j;
          int h;
          int bigN;
          int hp;
          int v;
  
          bigN = hi - lo + 1;
          if( bigN < 2 )
              return;
  
          hp = 0;
          while( incs[ hp ] < bigN )
              hp++;
          hp--;
  
          for( ; hp >= 0; hp-- )
          {
              h = incs[ hp ];
  
              i = lo + h;
              while( true )
              {
                  /*
                   * copy 1
                   */
                  if( i > hi )
                      break;
                  v = zptr[ i ];
                  j = i;
                  while( fullGtU( zptr[ j - h ] + d, v + d ) )
                  {
                      zptr[ j ] = zptr[ j - h ];
                      j = j - h;
                      if( j <= ( lo + h - 1 ) )
                          break;
                  }
                  zptr[ j ] = v;
                  i++;
  
                  /*
                   * copy 2
                   */
                  if( i > hi )
                      break;
                  v = zptr[ i ];
                  j = i;
                  while( fullGtU( zptr[ j - h ] + d, v + d ) )
                  {
                      zptr[ j ] = zptr[ j - h ];
                      j = j - h;
                      if( j <= ( lo + h - 1 ) )
                          break;
                  }
                  zptr[ j ] = v;
                  i++;
  
                  /*
                   * copy 3
                   */
                  if( i > hi )
                      break;
                  v = zptr[ i ];
                  j = i;
                  while( fullGtU( zptr[ j - h ] + d, v + d ) )
                  {
                      zptr[ j ] = zptr[ j - h ];
                      j = j - h;
                      if( j <= ( lo + h - 1 ) )
                          break;
                  }
                  zptr[ j ] = v;
                  i++;
  
                  if( workDone > workLimit && firstAttempt )
                      return;
              }
          }
      }
  
      private void vswap( int p1, int p2, int n )
      {
          int temp = 0;
          while( n > 0 )
          {
              temp = zptr[ p1 ];
              zptr[ p1 ] = zptr[ p2 ];
              zptr[ p2 ] = temp;
              p1++;
              p2++;
              n--;
          }
      }
  
      private void writeRun()
          throws IOException
      {
          if( last < allowableBlockSize )
          {
              inUse[ currentChar ] = true;
              for( int i = 0; i < runLength; i++ )
              {
                  mCrc.updateCRC( (char)currentChar );
              }
              switch( runLength )
              {
                  case 1:
                      last++;
                      block[ last + 1 ] = (char)currentChar;
                      break;
                  case 2:
                      last++;
                      block[ last + 1 ] = (char)currentChar;
                      last++;
                      block[ last + 1 ] = (char)currentChar;
                      break;
                  case 3:
                      last++;
                      block[ last + 1 ] = (char)currentChar;
                      last++;
                      block[ last + 1 ] = (char)currentChar;
                      last++;
                      block[ last + 1 ] = (char)currentChar;
                      break;
                  default:
                      inUse[ runLength - 4 ] = true;
                      last++;
                      block[ last + 1 ] = (char)currentChar;
                      last++;
                      block[ last + 1 ] = (char)currentChar;
                      last++;
                      block[ last + 1 ] = (char)currentChar;
                      last++;
                      block[ last + 1 ] = (char)currentChar;
                      last++;
                      block[ last + 1 ] = (char)( runLength - 4 );
                      break;
              }
          }
          else
          {
              endBlock();
              initBlock();
              writeRun();
          }
      }
  
      private class StackElem
      {
          int dd;
          int hh;
          int ll;
      }
  }
  
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/bzip2/CRC.java
  
  Index: CRC.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.bzip2;
  
  /**
   * A simple class the hold and calculate the CRC for sanity checking of the
   * data.
   *
   * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a>
   */
  class CRC
  {
      private static int CRC32_TABLE[] = new int[]
      {
          0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
          0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
          0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
          0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
          0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
          0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
          0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
          0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
          0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
          0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
          0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
          0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
          0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
          0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
          0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
          0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
          0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
          0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
          0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
          0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
          0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
          0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
          0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
          0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
          0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
          0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
          0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
          0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
          0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
          0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
          0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
          0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
          0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
          0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
          0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
          0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
          0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
          0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
          0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
          0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
          0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
          0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
          0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
          0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
          0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
          0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
          0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
          0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
          0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
          0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
          0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
          0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
          0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
          0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
          0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
          0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
          0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
          0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
          0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
          0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
          0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
          0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
          0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
          0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
      };
  
      private int m_globalCrc;
  
      protected CRC()
      {
          initialiseCRC();
      }
  
      void setGlobalCRC( final int newCrc )
      {
          m_globalCrc = newCrc;
      }
  
      int getFinalCRC()
      {
          return ~m_globalCrc;
      }
  
      int getGlobalCRC()
      {
          return m_globalCrc;
      }
  
      void initialiseCRC()
      {
          m_globalCrc = 0xffffffff;
      }
  
      void updateCRC( final int inCh )
      {
          int temp = ( m_globalCrc >> 24 ) ^ inCh;
          if( temp < 0 )
          {
              temp = 256 + temp;
          }
          m_globalCrc = ( m_globalCrc << 8 ) ^ CRC32_TABLE[ temp ];
      }
  }
  
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/tar/TarBuffer.java
  
  Index: TarBuffer.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.tar;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  
  /**
   * The TarBuffer class implements the tar archive concept of a buffered input
   * stream. This concept goes back to the days of blocked tape drives and special
   * io devices. In the Java universe, the only real function that this class
   * performs is to ensure that files have the correct "block" size, or other tars
   * will complain. <p>
   *
   * You should never have a need to access this class directly. TarBuffers are
   * created by Tar IO Streams.
   *
   * @author Timothy Gerard Endres <a href="mailto:time@ice.com">time@ice.com</a>
   */
  
  public class TarBuffer
  {
  
      public final static int DEFAULT_RCDSIZE = ( 512 );
      public final static int DEFAULT_BLKSIZE = ( DEFAULT_RCDSIZE * 20 );
      private byte[] blockBuffer;
      private int blockSize;
      private int currBlkIdx;
      private int currRecIdx;
      private boolean debug;
  
      private InputStream inStream;
      private OutputStream outStream;
      private int recordSize;
      private int recsPerBlock;
  
      public TarBuffer( InputStream inStream )
      {
          this( inStream, TarBuffer.DEFAULT_BLKSIZE );
      }
  
      public TarBuffer( InputStream inStream, int blockSize )
      {
          this( inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
      }
  
      public TarBuffer( InputStream inStream, int blockSize, int recordSize )
      {
          this.inStream = inStream;
          this.outStream = null;
  
          this.initialize( blockSize, recordSize );
      }
  
      public TarBuffer( OutputStream outStream )
      {
          this( outStream, TarBuffer.DEFAULT_BLKSIZE );
      }
  
      public TarBuffer( OutputStream outStream, int blockSize )
      {
          this( outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
      }
  
      public TarBuffer( OutputStream outStream, int blockSize, int recordSize )
      {
          this.inStream = null;
          this.outStream = outStream;
  
          this.initialize( blockSize, recordSize );
      }
  
      /**
       * Set the debugging flag for the buffer.
       *
       * @param debug If true, print debugging output.
       */
      public void setDebug( boolean debug )
      {
          this.debug = debug;
      }
  
      /**
       * Get the TAR Buffer's block size. Blocks consist of multiple records.
       *
       * @return The BlockSize value
       */
      public int getBlockSize()
      {
          return this.blockSize;
      }
  
      /**
       * Get the current block number, zero based.
       *
       * @return The current zero based block number.
       */
      public int getCurrentBlockNum()
      {
          return this.currBlkIdx;
      }
  
      /**
       * Get the current record number, within the current block, zero based.
       * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
       *
       * @return The current zero based record number.
       */
      public int getCurrentRecordNum()
      {
          return this.currRecIdx - 1;
      }
  
      /**
       * Get the TAR Buffer's record size.
       *
       * @return The RecordSize value
       */
      public int getRecordSize()
      {
          return this.recordSize;
      }
  
      /**
       * Determine if an archive record indicate End of Archive. End of archive is
       * indicated by a record that consists entirely of null bytes.
       *
       * @param record The record data to check.
       * @return The EOFRecord value
       */
      public boolean isEOFRecord( byte[] record )
      {
          for( int i = 0, sz = this.getRecordSize(); i < sz; ++i )
          {
              if( record[ i ] != 0 )
              {
                  return false;
              }
          }
  
          return true;
      }
  
      /**
       * Close the TarBuffer. If this is an output buffer, also flush the current
       * block before closing.
       *
       * @exception IOException Description of Exception
       */
      public void close()
          throws IOException
      {
          if( this.debug )
          {
              System.err.println( "TarBuffer.closeBuffer()." );
          }
  
          if( this.outStream != null )
          {
              this.flushBlock();
  
              if( this.outStream != System.out
                  && this.outStream != System.err )
              {
                  this.outStream.close();
  
                  this.outStream = null;
              }
          }
          else if( this.inStream != null )
          {
              if( this.inStream != System.in )
              {
                  this.inStream.close();
  
                  this.inStream = null;
              }
          }
      }
  
      /**
       * Read a record from the input stream and return the data.
       *
       * @return The record data.
       * @exception IOException Description of Exception
       */
      public byte[] readRecord()
          throws IOException
      {
          if( this.debug )
          {
              System.err.println( "ReadRecord: recIdx = " + this.currRecIdx
                                  + " blkIdx = " + this.currBlkIdx );
          }
  
          if( this.inStream == null )
          {
              throw new IOException( "reading from an output buffer" );
          }
  
          if( this.currRecIdx >= this.recsPerBlock )
          {
              if( !this.readBlock() )
              {
                  return null;
              }
          }
  
          byte[] result = new byte[ this.recordSize ];
  
          System.arraycopy( this.blockBuffer,
                            ( this.currRecIdx * this.recordSize ), result, 0,
                            this.recordSize );
  
          this.currRecIdx++;
  
          return result;
      }
  
      /**
       * Skip over a record on the input stream.
       *
       * @exception IOException Description of Exception
       */
      public void skipRecord()
          throws IOException
      {
          if( this.debug )
          {
              System.err.println( "SkipRecord: recIdx = " + this.currRecIdx
                                  + " blkIdx = " + this.currBlkIdx );
          }
  
          if( this.inStream == null )
          {
              throw new IOException( "reading (via skip) from an output buffer" );
          }
  
          if( this.currRecIdx >= this.recsPerBlock )
          {
              if( !this.readBlock() )
              {
                  return;// UNDONE
              }
          }
  
          this.currRecIdx++;
      }
  
      /**
       * Write an archive record to the archive.
       *
       * @param record The record data to write to the archive.
       * @exception IOException Description of Exception
       */
      public void writeRecord( byte[] record )
          throws IOException
      {
          if( this.debug )
          {
              System.err.println( "WriteRecord: recIdx = " + this.currRecIdx
                                  + " blkIdx = " + this.currBlkIdx );
          }
  
          if( this.outStream == null )
          {
              throw new IOException( "writing to an input buffer" );
          }
  
          if( record.length != this.recordSize )
          {
              throw new IOException( "record to write has length '"
                                     + record.length
                                     + "' which is not the record size of '"
                                     + this.recordSize + "'" );
          }
  
          if( this.currRecIdx >= this.recsPerBlock )
          {
              this.writeBlock();
          }
  
          System.arraycopy( record, 0, this.blockBuffer,
                            ( this.currRecIdx * this.recordSize ),
                            this.recordSize );
  
          this.currRecIdx++;
      }
  
      /**
       * Write an archive record to the archive, where the record may be inside of
       * a larger array buffer. The buffer must be "offset plus record size" long.
       *
       * @param buf The buffer containing the record data to write.
       * @param offset The offset of the record data within buf.
       * @exception IOException Description of Exception
       */
      public void writeRecord( byte[] buf, int offset )
          throws IOException
      {
          if( this.debug )
          {
              System.err.println( "WriteRecord: recIdx = " + this.currRecIdx
                                  + " blkIdx = " + this.currBlkIdx );
          }
  
          if( this.outStream == null )
          {
              throw new IOException( "writing to an input buffer" );
          }
  
          if( ( offset + this.recordSize ) > buf.length )
          {
              throw new IOException( "record has length '" + buf.length
                                     + "' with offset '" + offset
                                     + "' which is less than the record size of '"
                                     + this.recordSize + "'" );
          }
  
          if( this.currRecIdx >= this.recsPerBlock )
          {
              this.writeBlock();
          }
  
          System.arraycopy( buf, offset, this.blockBuffer,
                            ( this.currRecIdx * this.recordSize ),
                            this.recordSize );
  
          this.currRecIdx++;
      }
  
      /**
       * Flush the current data block if it has any data in it.
       *
       * @exception IOException Description of Exception
       */
      private void flushBlock()
          throws IOException
      {
          if( this.debug )
          {
              System.err.println( "TarBuffer.flushBlock() called." );
          }
  
          if( this.outStream == null )
          {
              throw new IOException( "writing to an input buffer" );
          }
  
          if( this.currRecIdx > 0 )
          {
              this.writeBlock();
          }
      }
  
      /**
       * Initialization common to all constructors.
       *
       * @param blockSize Description of Parameter
       * @param recordSize Description of Parameter
       */
      private void initialize( int blockSize, int recordSize )
      {
          this.debug = false;
          this.blockSize = blockSize;
          this.recordSize = recordSize;
          this.recsPerBlock = ( this.blockSize / this.recordSize );
          this.blockBuffer = new byte[ this.blockSize ];
  
          if( this.inStream != null )
          {
              this.currBlkIdx = -1;
              this.currRecIdx = this.recsPerBlock;
          }
          else
          {
              this.currBlkIdx = 0;
              this.currRecIdx = 0;
          }
      }
  
      /**
       * @return false if End-Of-File, else true
       * @exception IOException Description of Exception
       */
      private boolean readBlock()
          throws IOException
      {
          if( this.debug )
          {
              System.err.println( "ReadBlock: blkIdx = " + this.currBlkIdx );
          }
  
          if( this.inStream == null )
          {
              throw new IOException( "reading from an output buffer" );
          }
  
          this.currRecIdx = 0;
  
          int offset = 0;
          int bytesNeeded = this.blockSize;
  
          while( bytesNeeded > 0 )
          {
              long numBytes = this.inStream.read( this.blockBuffer, offset,
                                                  bytesNeeded );
  
              //
              // NOTE
              // We have fit EOF, and the block is not full!
              //
              // This is a broken archive. It does not follow the standard
              // blocking algorithm. However, because we are generous, and
              // it requires little effort, we will simply ignore the error
              // and continue as if the entire block were read. This does
              // not appear to break anything upstream. We used to return
              // false in this case.
              //
              // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
              //
              if( numBytes == -1 )
              {
                  break;
              }
  
              offset += numBytes;
              bytesNeeded -= numBytes;
  
              if( numBytes != this.blockSize )
              {
                  if( this.debug )
                  {
                      System.err.println( "ReadBlock: INCOMPLETE READ "
                                          + numBytes + " of " + this.blockSize
                                          + " bytes read." );
                  }
              }
          }
  
          this.currBlkIdx++;
  
          return true;
      }
  
      /**
       * Write a TarBuffer block to the archive.
       *
       * @exception IOException Description of Exception
       */
      private void writeBlock()
          throws IOException
      {
          if( this.debug )
          {
              System.err.println( "WriteBlock: blkIdx = " + this.currBlkIdx );
          }
  
          if( this.outStream == null )
          {
              throw new IOException( "writing to an input buffer" );
          }
  
          this.outStream.write( this.blockBuffer, 0, this.blockSize );
          this.outStream.flush();
  
          this.currRecIdx = 0;
          this.currBlkIdx++;
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/tar/TarConstants.java
  
  Index: TarConstants.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.tar;
  
  /**
   * This interface contains all the definitions used in the package.
   *
   * @author Timothy Gerard Endres <a href="mailto:time@ice.com">time@ice.com</a>
   * @author Stefano Mazzocchi <a href="mailto:stefano@apache.org">
   *      stefano@apache.org</a>
   */
  
  public interface TarConstants
  {
  
      /**
       * The length of the name field in a header buffer.
       */
      int NAMELEN = 100;
  
      /**
       * The length of the mode field in a header buffer.
       */
      int MODELEN = 8;
  
      /**
       * The length of the user id field in a header buffer.
       */
      int UIDLEN = 8;
  
      /**
       * The length of the group id field in a header buffer.
       */
      int GIDLEN = 8;
  
      /**
       * The length of the checksum field in a header buffer.
       */
      int CHKSUMLEN = 8;
  
      /**
       * The length of the size field in a header buffer.
       */
      int SIZELEN = 12;
  
      /**
       * The length of the magic field in a header buffer.
       */
      int MAGICLEN = 8;
  
      /**
       * The length of the modification time field in a header buffer.
       */
      int MODTIMELEN = 12;
  
      /**
       * The length of the user name field in a header buffer.
       */
      int UNAMELEN = 32;
  
      /**
       * The length of the group name field in a header buffer.
       */
      int GNAMELEN = 32;
  
      /**
       * The length of the devices field in a header buffer.
       */
      int DEVLEN = 8;
  
      /**
       * LF_ constants represent the "link flag" of an entry, or more commonly,
       * the "entry type". This is the "old way" of indicating a normal file.
       */
      byte LF_OLDNORM = 0;
  
      /**
       * Normal file type.
       */
      byte LF_NORMAL = (byte)'0';
  
      /**
       * Link file type.
       */
      byte LF_LINK = (byte)'1';
  
      /**
       * Symbolic link file type.
       */
      byte LF_SYMLINK = (byte)'2';
  
      /**
       * Character device file type.
       */
      byte LF_CHR = (byte)'3';
  
      /**
       * Block device file type.
       */
      byte LF_BLK = (byte)'4';
  
      /**
       * Directory file type.
       */
      byte LF_DIR = (byte)'5';
  
      /**
       * FIFO (pipe) file type.
       */
      byte LF_FIFO = (byte)'6';
  
      /**
       * Contiguous file type.
       */
      byte LF_CONTIG = (byte)'7';
  
      /**
       * The magic tag representing a POSIX tar archive.
       */
      String TMAGIC = "ustar";
  
      /**
       * The magic tag representing a GNU tar archive.
       */
      String GNU_TMAGIC = "ustar  ";
  
      /**
       * The namr of the GNU tar entry which contains a long name.
       */
      String GNU_LONGLINK = "././@LongLink";
  
      /**
       * Identifies the *next* file on the tape as having a long name.
       */
      byte LF_GNUTYPE_LONGNAME = (byte)'L';
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/tar/TarEntry.java
  
  Index: TarEntry.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.tar;
  
  import java.io.File;
  import java.util.Date;
  
  /**
   * This class represents an entry in a Tar archive. It consists of the entry's
   * header, as well as the entry's File. Entries can be instantiated in one of
   * three ways, depending on how they are to be used. <p>
   *
   * TarEntries that are created from the header bytes read from an archive are
   * instantiated with the TarEntry( byte[] ) constructor. These entries will be
   * used when extracting from or listing the contents of an archive. These
   * entries have their header filled in using the header bytes. They also set the
   * File to null, since they reference an archive entry not a file. <p>
   *
   * TarEntries that are created from Files that are to be written into an archive
   * are instantiated with the TarEntry( File ) constructor. These entries have
   * their header filled in using the File's information. They also keep a
   * reference to the File for convenience when writing entries. <p>
   *
   * Finally, TarEntries can be constructed from nothing but a name. This allows
   * the programmer to construct the entry by hand, for instance when only an
   * InputStream is available for writing to the archive, and the header
   * information is constructed from other information. In this case the header
   * fields are set to defaults and the File is set to null. <p>
   *
   * The C structure for a Tar Entry's header is: <pre>
   * struct header {
   * char name[NAMSIZ];
   * char mode[8];
   * char uid[8];
   * char gid[8];
   * char size[12];
   * char mtime[12];
   * char chksum[8];
   * char linkflag;
   * char linkname[NAMSIZ];
   * char magic[8];
   * char uname[TUNMLEN];
   * char gname[TGNMLEN];
   * char devmajor[8];
   * char devminor[8];
   * } header;
   * </pre>
   *
   * @author Timothy Gerard Endres <a href="mailto:time@ice.com">time@ice.com</a>
   * @author Stefano Mazzocchi <a href="mailto:stefano@apache.org">
   *      stefano@apache.org</a>
   */
  
  public class TarEntry implements TarConstants
  {
      /**
       * The entry's modification time.
       */
      private int checkSum;
      /**
       * The entry's group name.
       */
      private int devMajor;
      /**
       * The entry's major device number.
       */
      private int devMinor;
      /**
       * The entry's minor device number.
       */
      private File file;
      /**
       * The entry's user id.
       */
      private int groupId;
      /**
       * The entry's user name.
       */
      private StringBuffer groupName;
      /**
       * The entry's checksum.
       */
      private byte linkFlag;
      /**
       * The entry's link flag.
       */
      private StringBuffer linkName;
      /**
       * The entry's link name.
       */
      private StringBuffer magic;
      /**
       * The entry's size.
       */
      private long modTime;
      /**
       * The entry's name.
       */
      private int mode;
  
      private StringBuffer name;
      /**
       * The entry's group id.
       */
      private long size;
      /**
       * The entry's permission mode.
       */
      private int userId;
      /**
       * The entry's magic tag.
       */
      private StringBuffer userName;
  
      /**
       * Construct an entry with only a name. This allows the programmer to
       * construct the entry's header "by hand". File is set to null.
       *
       * @param name Description of Parameter
       */
      public TarEntry( String name )
      {
          this();
  
          boolean isDir = name.endsWith( "/" );
  
          this.checkSum = 0;
          this.devMajor = 0;
          this.devMinor = 0;
          this.name = new StringBuffer( name );
          this.mode = isDir ? 040755 : 0100644;
          this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
          this.userId = 0;
          this.groupId = 0;
          this.size = 0;
          this.checkSum = 0;
          this.modTime = ( new Date() ).getTime() / 1000;
          this.linkName = new StringBuffer( "" );
          this.userName = new StringBuffer( "" );
          this.groupName = new StringBuffer( "" );
          this.devMajor = 0;
          this.devMinor = 0;
  
      }
  
      /**
       * Construct an entry with a name an a link flag.
       *
       * @param name Description of Parameter
       * @param linkFlag Description of Parameter
       */
      public TarEntry( String name, byte linkFlag )
      {
          this( name );
          this.linkFlag = linkFlag;
      }
  
      /**
       * Construct an entry for a file. File is set to file, and the header is
       * constructed from information from the file.
       *
       * @param file The file that the entry represents.
       */
      public TarEntry( File file )
      {
          this();
  
          this.file = file;
  
          String name = file.getPath();
          String osname = System.getProperty( "os.name" );
  
          if( osname != null )
          {
  
              // Strip off drive letters!
              // REVIEW Would a better check be "(File.separator == '\')"?
              String win32Prefix = "Windows";
              String prefix = osname.substring( 0, win32Prefix.length() );
  
              if( prefix.equalsIgnoreCase( win32Prefix ) )
              {
                  if( name.length() > 2 )
                  {
                      char ch1 = name.charAt( 0 );
                      char ch2 = name.charAt( 1 );
  
                      if( ch2 == ':'
                          && ( ( ch1 >= 'a' && ch1 <= 'z' )
                          || ( ch1 >= 'A' && ch1 <= 'Z' ) ) )
                      {
                          name = name.substring( 2 );
                      }
                  }
              }
              else if( osname.toLowerCase().indexOf( "netware" ) > -1 )
              {
                  int colon = name.indexOf( ':' );
                  if( colon != -1 )
                  {
                      name = name.substring( colon + 1 );
                  }
              }
          }
  
          name = name.replace( File.separatorChar, '/' );
  
          // No absolute pathnames
          // Windows (and Posix?) paths can start with "\\NetworkDrive\",
          // so we loop on starting /'s.
          while( name.startsWith( "/" ) )
          {
              name = name.substring( 1 );
          }
  
          this.linkName = new StringBuffer( "" );
          this.name = new StringBuffer( name );
  
          if( file.isDirectory() )
          {
              this.mode = 040755;
              this.linkFlag = LF_DIR;
  
              if( this.name.charAt( this.name.length() - 1 ) != '/' )
              {
                  this.name.append( "/" );
              }
          }
          else
          {
              this.mode = 0100644;
              this.linkFlag = LF_NORMAL;
          }
  
          this.size = file.length();
          this.modTime = file.lastModified() / 1000;
          this.checkSum = 0;
          this.devMajor = 0;
          this.devMinor = 0;
      }
  
      /**
       * Construct an entry from an archive's header bytes. File is set to null.
       *
       * @param headerBuf The header bytes from a tar archive entry.
       */
      public TarEntry( byte[] headerBuf )
      {
          this();
          this.parseTarHeader( headerBuf );
      }
  
      /**
       * The entry's file reference
       */
  
      /**
       * Construct an empty entry and prepares the header values.
       */
      private TarEntry()
      {
          this.magic = new StringBuffer( TMAGIC );
          this.name = new StringBuffer();
          this.linkName = new StringBuffer();
  
          String user = System.getProperty( "user.name", "" );
  
          if( user.length() > 31 )
          {
              user = user.substring( 0, 31 );
          }
  
          this.userId = 0;
          this.groupId = 0;
          this.userName = new StringBuffer( user );
          this.groupName = new StringBuffer( "" );
          this.file = null;
      }
  
      /**
       * Set this entry's group id.
       *
       * @param groupId This entry's new group id.
       */
      public void setGroupId( int groupId )
      {
          this.groupId = groupId;
      }
  
      /**
       * Set this entry's group name.
       *
       * @param groupName This entry's new group name.
       */
      public void setGroupName( String groupName )
      {
          this.groupName = new StringBuffer( groupName );
      }
  
      /**
       * Convenience method to set this entry's group and user ids.
       *
       * @param userId This entry's new user id.
       * @param groupId This entry's new group id.
       */
      public void setIds( int userId, int groupId )
      {
          this.setUserId( userId );
          this.setGroupId( groupId );
      }
  
      /**
       * Set this entry's modification time. The parameter passed to this method
       * is in "Java time".
       *
       * @param time This entry's new modification time.
       */
      public void setModTime( long time )
      {
          this.modTime = time / 1000;
      }
  
      /**
       * Set this entry's modification time.
       *
       * @param time This entry's new modification time.
       */
      public void setModTime( Date time )
      {
          this.modTime = time.getTime() / 1000;
      }
  
      /**
       * Set the mode for this entry
       *
       * @param mode The new Mode value
       */
      public void setMode( int mode )
      {
          this.mode = mode;
      }
  
      /**
       * Set this entry's name.
       *
       * @param name This entry's new name.
       */
      public void setName( String name )
      {
          this.name = new StringBuffer( name );
      }
  
      /**
       * Convenience method to set this entry's group and user names.
       *
       * @param userName This entry's new user name.
       * @param groupName This entry's new group name.
       */
      public void setNames( String userName, String groupName )
      {
          this.setUserName( userName );
          this.setGroupName( groupName );
      }
  
      /**
       * Set this entry's file size.
       *
       * @param size This entry's new file size.
       */
      public void setSize( long size )
      {
          this.size = size;
      }
  
      /**
       * Set this entry's user id.
       *
       * @param userId This entry's new user id.
       */
      public void setUserId( int userId )
      {
          this.userId = userId;
      }
  
      /**
       * Set this entry's user name.
       *
       * @param userName This entry's new user name.
       */
      public void setUserName( String userName )
      {
          this.userName = new StringBuffer( userName );
      }
  
      /**
       * If this entry represents a file, and the file is a directory, return an
       * array of TarEntries for this entry's children.
       *
       * @return An array of TarEntry's for this entry's children.
       */
      public TarEntry[] getDirectoryEntries()
      {
          if( this.file == null || !this.file.isDirectory() )
          {
              return new TarEntry[ 0 ];
          }
  
          String[] list = this.file.list();
          TarEntry[] result = new TarEntry[ list.length ];
  
          for( int i = 0; i < list.length; ++i )
          {
              result[ i ] = new TarEntry( new File( this.file, list[ i ] ) );
          }
  
          return result;
      }
  
      /**
       * Get this entry's file.
       *
       * @return This entry's file.
       */
      public File getFile()
      {
          return this.file;
      }
  
      /**
       * Get this entry's group id.
       *
       * @return This entry's group id.
       */
      public int getGroupId()
      {
          return this.groupId;
      }
  
      /**
       * Get this entry's group name.
       *
       * @return This entry's group name.
       */
      public String getGroupName()
      {
          return this.groupName.toString();
      }
  
      /**
       * Set this entry's modification time.
       *
       * @return The ModTime value
       */
      public Date getModTime()
      {
          return new Date( this.modTime * 1000 );
      }
  
      /**
       * Get this entry's mode.
       *
       * @return This entry's mode.
       */
      public int getMode()
      {
          return this.mode;
      }
  
      /**
       * Get this entry's name.
       *
       * @return This entry's name.
       */
      public String getName()
      {
          return this.name.toString();
      }
  
      /**
       * Get this entry's file size.
       *
       * @return This entry's file size.
       */
      public long getSize()
      {
          return this.size;
      }
  
      /**
       * Get this entry's user id.
       *
       * @return This entry's user id.
       */
      public int getUserId()
      {
          return this.userId;
      }
  
      /**
       * Get this entry's user name.
       *
       * @return This entry's user name.
       */
      public String getUserName()
      {
          return this.userName.toString();
      }
  
      /**
       * Determine if the given entry is a descendant of this entry. Descendancy
       * is determined by the name of the descendant starting with this entry's
       * name.
       *
       * @param desc Entry to be checked as a descendent of this.
       * @return True if entry is a descendant of this.
       */
      public boolean isDescendent( TarEntry desc )
      {
          return desc.getName().startsWith( this.getName() );
      }
  
      /**
       * Return whether or not this entry represents a directory.
       *
       * @return True if this entry is a directory.
       */
      public boolean isDirectory()
      {
          if( this.file != null )
          {
              return this.file.isDirectory();
          }
  
          if( this.linkFlag == LF_DIR )
          {
              return true;
          }
  
          if( this.getName().endsWith( "/" ) )
          {
              return true;
          }
  
          return false;
      }
  
      /**
       * Indicate if this entry is a GNU long name block
       *
       * @return true if this is a long name extension provided by GNU tar
       */
      public boolean isGNULongNameEntry()
      {
          return linkFlag == LF_GNUTYPE_LONGNAME &&
              name.toString().equals( GNU_LONGLINK );
      }
  
      /**
       * Determine if the two entries are equal. Equality is determined by the
       * header names being equal.
       *
       * @param it Description of Parameter
       * @return it Entry to be checked for equality.
       * @return True if the entries are equal.
       */
      public boolean equals( TarEntry it )
      {
          return this.getName().equals( it.getName() );
      }
  
      /**
       * Parse an entry's header information from a header buffer.
       *
       * @param header The tar entry header buffer to get information from.
       */
      public void parseTarHeader( byte[] header )
      {
          int offset = 0;
  
          this.name = TarUtils.parseName( header, offset, NAMELEN );
          offset += NAMELEN;
          this.mode = (int)TarUtils.parseOctal( header, offset, MODELEN );
          offset += MODELEN;
          this.userId = (int)TarUtils.parseOctal( header, offset, UIDLEN );
          offset += UIDLEN;
          this.groupId = (int)TarUtils.parseOctal( header, offset, GIDLEN );
          offset += GIDLEN;
          this.size = TarUtils.parseOctal( header, offset, SIZELEN );
          offset += SIZELEN;
          this.modTime = TarUtils.parseOctal( header, offset, MODTIMELEN );
          offset += MODTIMELEN;
          this.checkSum = (int)TarUtils.parseOctal( header, offset, CHKSUMLEN );
          offset += CHKSUMLEN;
          this.linkFlag = header[ offset++ ];
          this.linkName = TarUtils.parseName( header, offset, NAMELEN );
          offset += NAMELEN;
          this.magic = TarUtils.parseName( header, offset, MAGICLEN );
          offset += MAGICLEN;
          this.userName = TarUtils.parseName( header, offset, UNAMELEN );
          offset += UNAMELEN;
          this.groupName = TarUtils.parseName( header, offset, GNAMELEN );
          offset += GNAMELEN;
          this.devMajor = (int)TarUtils.parseOctal( header, offset, DEVLEN );
          offset += DEVLEN;
          this.devMinor = (int)TarUtils.parseOctal( header, offset, DEVLEN );
      }
  
      /**
       * Write an entry's header information to a header buffer.
       *
       * @param outbuf The tar entry header buffer to fill in.
       */
      public void writeEntryHeader( byte[] outbuf )
      {
          int offset = 0;
  
          offset = TarUtils.getNameBytes( this.name, outbuf, offset, NAMELEN );
          offset = TarUtils.getOctalBytes( this.mode, outbuf, offset, MODELEN );
          offset = TarUtils.getOctalBytes( this.userId, outbuf, offset, UIDLEN );
          offset = TarUtils.getOctalBytes( this.groupId, outbuf, offset, GIDLEN );
          offset = TarUtils.getLongOctalBytes( this.size, outbuf, offset, SIZELEN );
          offset = TarUtils.getLongOctalBytes( this.modTime, outbuf, offset, MODTIMELEN );
  
          int csOffset = offset;
  
          for( int c = 0; c < CHKSUMLEN; ++c )
          {
              outbuf[ offset++ ] = (byte)' ';
          }
  
          outbuf[ offset++ ] = this.linkFlag;
          offset = TarUtils.getNameBytes( this.linkName, outbuf, offset, NAMELEN );
          offset = TarUtils.getNameBytes( this.magic, outbuf, offset, MAGICLEN );
          offset = TarUtils.getNameBytes( this.userName, outbuf, offset, UNAMELEN );
          offset = TarUtils.getNameBytes( this.groupName, outbuf, offset, GNAMELEN );
          offset = TarUtils.getOctalBytes( this.devMajor, outbuf, offset, DEVLEN );
          offset = TarUtils.getOctalBytes( this.devMinor, outbuf, offset, DEVLEN );
  
          while( offset < outbuf.length )
          {
              outbuf[ offset++ ] = 0;
          }
  
          long checkSum = TarUtils.computeCheckSum( outbuf );
  
          TarUtils.getCheckSumOctalBytes( checkSum, outbuf, csOffset, CHKSUMLEN );
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/tar/TarInputStream.java
  
  Index: TarInputStream.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.tar;
  
  import java.io.FilterInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  
  /**
   * The TarInputStream reads a UNIX tar archive as an InputStream. methods are
   * provided to position at each successive entry in the archive, and the read
   * each entry as a normal input stream using read().
   *
   * @author Timothy Gerard Endres <a href="mailto:time@ice.com">time@ice.com</a>
   * @author Stefano Mazzocchi <a href="mailto:stefano@apache.org">
   *      stefano@apache.org</a>
   */
  public class TarInputStream extends FilterInputStream
  {
      protected TarBuffer buffer;
      protected TarEntry currEntry;
  
      protected boolean debug;
      protected int entryOffset;
      protected int entrySize;
      protected boolean hasHitEOF;
      protected byte[] oneBuf;
      protected byte[] readBuf;
  
      public TarInputStream( InputStream is )
      {
          this( is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE );
      }
  
      public TarInputStream( InputStream is, int blockSize )
      {
          this( is, blockSize, TarBuffer.DEFAULT_RCDSIZE );
      }
  
      public TarInputStream( InputStream is, int blockSize, int recordSize )
      {
          super( is );
  
          this.buffer = new TarBuffer( is, blockSize, recordSize );
          this.readBuf = null;
          this.oneBuf = new byte[ 1 ];
          this.debug = false;
          this.hasHitEOF = false;
      }
  
      /**
       * Sets the debugging flag.
       *
       * @param debug The new Debug value
       */
      public void setDebug( boolean debug )
      {
          this.debug = debug;
          this.buffer.setDebug( debug );
      }
  
      /**
       * Get the next entry in this tar archive. This will skip over any remaining
       * data in the current entry, if there is one, and place the input stream at
       * the header of the next entry, and read the header and instantiate a new
       * TarEntry from the header bytes and return that entry. If there are no
       * more entries in the archive, null will be returned to indicate that the
       * end of the archive has been reached.
       *
       * @return The next TarEntry in the archive, or null.
       * @exception IOException Description of Exception
       */
      public TarEntry getNextEntry()
          throws IOException
      {
          if( this.hasHitEOF )
          {
              return null;
          }
  
          if( this.currEntry != null )
          {
              int numToSkip = this.entrySize - this.entryOffset;
  
              if( this.debug )
              {
                  System.err.println( "TarInputStream: SKIP currENTRY '"
                                      + this.currEntry.getName() + "' SZ "
                                      + this.entrySize + " OFF "
                                      + this.entryOffset + "  skipping "
                                      + numToSkip + " bytes" );
              }
  
              if( numToSkip > 0 )
              {
                  this.skip( numToSkip );
              }
  
              this.readBuf = null;
          }
  
          byte[] headerBuf = this.buffer.readRecord();
  
          if( headerBuf == null )
          {
              if( this.debug )
              {
                  System.err.println( "READ NULL RECORD" );
              }
              this.hasHitEOF = true;
          }
          else if( this.buffer.isEOFRecord( headerBuf ) )
          {
              if( this.debug )
              {
                  System.err.println( "READ EOF RECORD" );
              }
              this.hasHitEOF = true;
          }
  
          if( this.hasHitEOF )
          {
              this.currEntry = null;
          }
          else
          {
              this.currEntry = new TarEntry( headerBuf );
  
              if( !( headerBuf[ 257 ] == 'u' && headerBuf[ 258 ] == 's'
                  && headerBuf[ 259 ] == 't' && headerBuf[ 260 ] == 'a'
                  && headerBuf[ 261 ] == 'r' ) )
              {
                  this.entrySize = 0;
                  this.entryOffset = 0;
                  this.currEntry = null;
  
                  throw new IOException( "bad header in block "
                                         + this.buffer.getCurrentBlockNum()
                                         + " record "
                                         + this.buffer.getCurrentRecordNum()
                                         + ", " +
                                         "header magic is not 'ustar', but '"
                                         + headerBuf[ 257 ]
                                         + headerBuf[ 258 ]
                                         + headerBuf[ 259 ]
                                         + headerBuf[ 260 ]
                                         + headerBuf[ 261 ]
                                         + "', or (dec) "
                                         + ( (int)headerBuf[ 257 ] )
                                         + ", "
                                         + ( (int)headerBuf[ 258 ] )
                                         + ", "
                                         + ( (int)headerBuf[ 259 ] )
                                         + ", "
                                         + ( (int)headerBuf[ 260 ] )
                                         + ", "
                                         + ( (int)headerBuf[ 261 ] ) );
              }
  
              if( this.debug )
              {
                  System.err.println( "TarInputStream: SET CURRENTRY '"
                                      + this.currEntry.getName()
                                      + "' size = "
                                      + this.currEntry.getSize() );
              }
  
              this.entryOffset = 0;
  
              // REVIEW How do we resolve this discrepancy?!
              this.entrySize = (int)this.currEntry.getSize();
          }
  
          if( this.currEntry != null && this.currEntry.isGNULongNameEntry() )
          {
              // read in the name
              StringBuffer longName = new StringBuffer();
              byte[] buffer = new byte[ 256 ];
              int length = 0;
              while( ( length = read( buffer ) ) >= 0 )
              {
                  longName.append( new String( buffer, 0, length ) );
              }
              getNextEntry();
              this.currEntry.setName( longName.toString() );
          }
  
          return this.currEntry;
      }
  
      /**
       * Get the record size being used by this stream's TarBuffer.
       *
       * @return The TarBuffer record size.
       */
      public int getRecordSize()
      {
          return this.buffer.getRecordSize();
      }
  
      /**
       * Get the available data that can be read from the current entry in the
       * archive. This does not indicate how much data is left in the entire
       * archive, only in the current entry. This value is determined from the
       * entry's size header field and the amount of data already read from the
       * current entry.
       *
       * @return The number of available bytes for the current entry.
       * @exception IOException Description of Exception
       */
      public int available()
          throws IOException
      {
          return this.entrySize - this.entryOffset;
      }
  
      /**
       * Closes this stream. Calls the TarBuffer's close() method.
       *
       * @exception IOException Description of Exception
       */
      public void close()
          throws IOException
      {
          this.buffer.close();
      }
  
      /**
       * Copies the contents of the current tar archive entry directly into an
       * output stream.
       *
       * @param out The OutputStream into which to write the entry's data.
       * @exception IOException Description of Exception
       */
      public void copyEntryContents( OutputStream out )
          throws IOException
      {
          byte[] buf = new byte[ 32 * 1024 ];
  
          while( true )
          {
              int numRead = this.read( buf, 0, buf.length );
  
              if( numRead == -1 )
              {
                  break;
              }
  
              out.write( buf, 0, numRead );
          }
      }
  
      /**
       * Since we do not support marking just yet, we do nothing.
       *
       * @param markLimit The limit to mark.
       */
      public void mark( int markLimit )
      {
      }
  
      /**
       * Since we do not support marking just yet, we return false.
       *
       * @return False.
       */
      public boolean markSupported()
      {
          return false;
      }
  
      /**
       * Reads a byte from the current tar archive entry. This method simply calls
       * read( byte[], int, int ).
       *
       * @return The byte read, or -1 at EOF.
       * @exception IOException Description of Exception
       */
      public int read()
          throws IOException
      {
          int num = this.read( this.oneBuf, 0, 1 );
  
          if( num == -1 )
          {
              return num;
          }
          else
          {
              return (int)this.oneBuf[ 0 ];
          }
      }
  
      /**
       * Reads bytes from the current tar archive entry. This method simply calls
       * read( byte[], int, int ).
       *
       * @param buf The buffer into which to place bytes read.
       * @return The number of bytes read, or -1 at EOF.
       * @exception IOException Description of Exception
       */
      public int read( byte[] buf )
          throws IOException
      {
          return this.read( buf, 0, buf.length );
      }
  
      /**
       * Reads bytes from the current tar archive entry. This method is aware of
       * the boundaries of the current entry in the archive and will deal with
       * them as if they were this stream's start and EOF.
       *
       * @param buf The buffer into which to place bytes read.
       * @param offset The offset at which to place bytes read.
       * @param numToRead The number of bytes to read.
       * @return The number of bytes read, or -1 at EOF.
       * @exception IOException Description of Exception
       */
      public int read( byte[] buf, int offset, int numToRead )
          throws IOException
      {
          int totalRead = 0;
  
          if( this.entryOffset >= this.entrySize )
          {
              return -1;
          }
  
          if( ( numToRead + this.entryOffset ) > this.entrySize )
          {
              numToRead = ( this.entrySize - this.entryOffset );
          }
  
          if( this.readBuf != null )
          {
              int sz = ( numToRead > this.readBuf.length ) ? this.readBuf.length
                  : numToRead;
  
              System.arraycopy( this.readBuf, 0, buf, offset, sz );
  
              if( sz >= this.readBuf.length )
              {
                  this.readBuf = null;
              }
              else
              {
                  int newLen = this.readBuf.length - sz;
                  byte[] newBuf = new byte[ newLen ];
  
                  System.arraycopy( this.readBuf, sz, newBuf, 0, newLen );
  
                  this.readBuf = newBuf;
              }
  
              totalRead += sz;
              numToRead -= sz;
              offset += sz;
          }
  
          while( numToRead > 0 )
          {
              byte[] rec = this.buffer.readRecord();
  
              if( rec == null )
              {
                  // Unexpected EOF!
                  throw new IOException( "unexpected EOF with " + numToRead
                                         + " bytes unread" );
              }
  
              int sz = numToRead;
              int recLen = rec.length;
  
              if( recLen > sz )
              {
                  System.arraycopy( rec, 0, buf, offset, sz );
  
                  this.readBuf = new byte[ recLen - sz ];
  
                  System.arraycopy( rec, sz, this.readBuf, 0, recLen - sz );
              }
              else
              {
                  sz = recLen;
  
                  System.arraycopy( rec, 0, buf, offset, recLen );
              }
  
              totalRead += sz;
              numToRead -= sz;
              offset += sz;
          }
  
          this.entryOffset += totalRead;
  
          return totalRead;
      }
  
      /**
       * Since we do not support marking just yet, we do nothing.
       */
      public void reset()
      {
      }
  
      /**
       * Skip bytes in the input buffer. This skips bytes in the current entry's
       * data, not the entire archive, and will stop at the end of the current
       * entry's data if the number to skip extends beyond that point.
       *
       * @param numToSkip The number of bytes to skip.
       * @exception IOException Description of Exception
       */
      public void skip( int numToSkip )
          throws IOException
      {
  
          // REVIEW
          // This is horribly inefficient, but it ensures that we
          // properly skip over bytes via the TarBuffer...
          //
          byte[] skipBuf = new byte[ 8 * 1024 ];
  
          for( int num = numToSkip; num > 0; )
          {
              int numRead = this.read( skipBuf, 0,
                                       ( num > skipBuf.length ? skipBuf.length
                                         : num ) );
  
              if( numRead == -1 )
              {
                  break;
              }
  
              num -= numRead;
          }
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/tar/TarOutputStream.java
  
  Index: TarOutputStream.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.tar;
  
  import java.io.FilterOutputStream;
  import java.io.IOException;
  import java.io.OutputStream;
  
  /**
   * The TarOutputStream writes a UNIX tar archive as an OutputStream. Methods are
   * provided to put entries, and then write their contents by writing to this
   * stream using write().
   *
   * @author Timothy Gerard Endres <a href="mailto:time@ice.com">time@ice.com</a>
   */
  public class TarOutputStream extends FilterOutputStream
  {
      public final static int LONGFILE_ERROR = 0;
      public final static int LONGFILE_TRUNCATE = 1;
      public final static int LONGFILE_GNU = 2;
      protected int longFileMode = LONGFILE_ERROR;
      protected byte[] assemBuf;
      protected int assemLen;
      protected TarBuffer buffer;
      protected int currBytes;
      protected int currSize;
  
      protected boolean debug;
      protected byte[] oneBuf;
      protected byte[] recordBuf;
  
      public TarOutputStream( OutputStream os )
      {
          this( os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE );
      }
  
      public TarOutputStream( OutputStream os, int blockSize )
      {
          this( os, blockSize, TarBuffer.DEFAULT_RCDSIZE );
      }
  
      public TarOutputStream( OutputStream os, int blockSize, int recordSize )
      {
          super( os );
  
          this.buffer = new TarBuffer( os, blockSize, recordSize );
          this.debug = false;
          this.assemLen = 0;
          this.assemBuf = new byte[ recordSize ];
          this.recordBuf = new byte[ recordSize ];
          this.oneBuf = new byte[ 1 ];
      }
  
      /**
       * Sets the debugging flag in this stream's TarBuffer.
       *
       * @param debug The new BufferDebug value
       */
      public void setBufferDebug( boolean debug )
      {
          this.buffer.setDebug( debug );
      }
  
      /**
       * Sets the debugging flag.
       *
       * @param debugF True to turn on debugging.
       */
      public void setDebug( boolean debugF )
      {
          this.debug = debugF;
      }
  
      public void setLongFileMode( int longFileMode )
      {
          this.longFileMode = longFileMode;
      }
  
      /**
       * Get the record size being used by this stream's TarBuffer.
       *
       * @return The TarBuffer record size.
       */
      public int getRecordSize()
      {
          return this.buffer.getRecordSize();
      }
  
      /**
       * Ends the TAR archive and closes the underlying OutputStream. This means
       * that finish() is called followed by calling the TarBuffer's close().
       *
       * @exception IOException Description of Exception
       */
      public void close()
          throws IOException
      {
          this.finish();
          this.buffer.close();
      }
  
      /**
       * Close an entry. This method MUST be called for all file entries that
       * contain data. The reason is that we must buffer data written to the
       * stream in order to satisfy the buffer's record based writes. Thus, there
       * may be data fragments still being assembled that must be written to the
       * output stream before this entry is closed and the next entry written.
       *
       * @exception IOException Description of Exception
       */
      public void closeEntry()
          throws IOException
      {
          if( this.assemLen > 0 )
          {
              for( int i = this.assemLen; i < this.assemBuf.length; ++i )
              {
                  this.assemBuf[ i ] = 0;
              }
  
              this.buffer.writeRecord( this.assemBuf );
  
              this.currBytes += this.assemLen;
              this.assemLen = 0;
          }
  
          if( this.currBytes < this.currSize )
          {
              throw new IOException( "entry closed at '" + this.currBytes
                                     + "' before the '" + this.currSize
                                     + "' bytes specified in the header were written" );
          }
      }
  
      /**
       * Ends the TAR archive without closing the underlying OutputStream. The
       * result is that the EOF record of nulls is written.
       *
       * @exception IOException Description of Exception
       */
      public void finish()
          throws IOException
      {
          this.writeEOFRecord();
      }
  
      /**
       * Put an entry on the output stream. This writes the entry's header record
       * and positions the output stream for writing the contents of the entry.
       * Once this method is called, the stream is ready for calls to write() to
       * write the entry's contents. Once the contents are written, closeEntry()
       * <B>MUST</B> be called to ensure that all buffered data is completely
       * written to the output stream.
       *
       * @param entry The TarEntry to be written to the archive.
       * @exception IOException Description of Exception
       */
      public void putNextEntry( TarEntry entry )
          throws IOException
      {
          if( entry.getName().length() >= TarConstants.NAMELEN )
          {
  
              if( longFileMode == LONGFILE_GNU )
              {
                  // create a TarEntry for the LongLink, the contents
                  // of which are the entry's name
                  TarEntry longLinkEntry = new TarEntry( TarConstants.GNU_LONGLINK,
                                                         TarConstants.LF_GNUTYPE_LONGNAME );
  
                  longLinkEntry.setSize( entry.getName().length() + 1 );
                  putNextEntry( longLinkEntry );
                  write( entry.getName().getBytes() );
                  write( 0 );
                  closeEntry();
              }
              else if( longFileMode != LONGFILE_TRUNCATE )
              {
                  throw new RuntimeException( "file name '" + entry.getName()
                                              + "' is too long ( > "
                                              + TarConstants.NAMELEN + " bytes)" );
              }
          }
  
          entry.writeEntryHeader( this.recordBuf );
          this.buffer.writeRecord( this.recordBuf );
  
          this.currBytes = 0;
  
          if( entry.isDirectory() )
          {
              this.currSize = 0;
          }
          else
          {
              this.currSize = (int)entry.getSize();
          }
      }
  
      /**
       * Writes a byte to the current tar archive entry. This method simply calls
       * read( byte[], int, int ).
       *
       * @param b The byte written.
       * @exception IOException Description of Exception
       */
      public void write( int b )
          throws IOException
      {
          this.oneBuf[ 0 ] = (byte)b;
  
          this.write( this.oneBuf, 0, 1 );
      }
  
      /**
       * Writes bytes to the current tar archive entry. This method simply calls
       * write( byte[], int, int ).
       *
       * @param wBuf The buffer to write to the archive.
       * @exception IOException Description of Exception
       */
      public void write( byte[] wBuf )
          throws IOException
      {
          this.write( wBuf, 0, wBuf.length );
      }
  
      /**
       * Writes bytes to the current tar archive entry. This method is aware of
       * the current entry and will throw an exception if you attempt to write
       * bytes past the length specified for the current entry. The method is also
       * (painfully) aware of the record buffering required by TarBuffer, and
       * manages buffers that are not a multiple of recordsize in length,
       * including assembling records from small buffers.
       *
       * @param wBuf The buffer to write to the archive.
       * @param wOffset The offset in the buffer from which to get bytes.
       * @param numToWrite The number of bytes to write.
       * @exception IOException Description of Exception
       */
      public void write( byte[] wBuf, int wOffset, int numToWrite )
          throws IOException
      {
          if( ( this.currBytes + numToWrite ) > this.currSize )
          {
              throw new IOException( "request to write '" + numToWrite
                                     + "' bytes exceeds size in header of '"
                                     + this.currSize + "' bytes" );
              //
              // We have to deal with assembly!!!
              // The programmer can be writing little 32 byte chunks for all
              // we know, and we must assemble complete records for writing.
              // REVIEW Maybe this should be in TarBuffer? Could that help to
              // eliminate some of the buffer copying.
              //
          }
  
          if( this.assemLen > 0 )
          {
              if( ( this.assemLen + numToWrite ) >= this.recordBuf.length )
              {
                  int aLen = this.recordBuf.length - this.assemLen;
  
                  System.arraycopy( this.assemBuf, 0, this.recordBuf, 0,
                                    this.assemLen );
                  System.arraycopy( wBuf, wOffset, this.recordBuf,
                                    this.assemLen, aLen );
                  this.buffer.writeRecord( this.recordBuf );
  
                  this.currBytes += this.recordBuf.length;
                  wOffset += aLen;
                  numToWrite -= aLen;
                  this.assemLen = 0;
              }
              else
              {
                  System.arraycopy( wBuf, wOffset, this.assemBuf, this.assemLen,
                                    numToWrite );
  
                  wOffset += numToWrite;
                  this.assemLen += numToWrite;
                  numToWrite -= numToWrite;
              }
          }
  
          //
          // When we get here we have EITHER:
          // o An empty "assemble" buffer.
          // o No bytes to write (numToWrite == 0)
          //
          while( numToWrite > 0 )
          {
              if( numToWrite < this.recordBuf.length )
              {
                  System.arraycopy( wBuf, wOffset, this.assemBuf, this.assemLen,
                                    numToWrite );
  
                  this.assemLen += numToWrite;
  
                  break;
              }
  
              this.buffer.writeRecord( wBuf, wOffset );
  
              int num = this.recordBuf.length;
  
              this.currBytes += num;
              numToWrite -= num;
              wOffset += num;
          }
      }
  
      /**
       * Write an EOF (end of archive) record to the tar archive. An EOF record
       * consists of a record of all zeros.
       *
       * @exception IOException Description of Exception
       */
      private void writeEOFRecord()
          throws IOException
      {
          for( int i = 0; i < this.recordBuf.length; ++i )
          {
              this.recordBuf[ i ] = 0;
          }
  
          this.buffer.writeRecord( this.recordBuf );
      }
  }
  
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/tar/TarUtils.java
  
  Index: TarUtils.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.tar;
  
  /**
   * This class provides static utility methods to work with byte streams.
   *
   * @author Timothy Gerard Endres <a href="mailto:time@ice.com">time@ice.com</a>
   * @author Stefano Mazzocchi <a href="mailto:stefano@apache.org">
   *      stefano@apache.org</a>
   */
  public class TarUtils
  {
      /**
       * Parse the checksum octal integer from a header buffer.
       *
       * @param offset The offset into the buffer from which to parse.
       * @param length The number of header bytes to parse.
       * @param value Description of Parameter
       * @param buf Description of Parameter
       * @return The integer value of the entry's checksum.
       */
      public static int getCheckSumOctalBytes( long value, byte[] buf, int offset, int length )
      {
          getOctalBytes( value, buf, offset, length );
  
          buf[ offset + length - 1 ] = (byte)' ';
          buf[ offset + length - 2 ] = 0;
  
          return offset + length;
      }
  
      /**
       * Parse an octal long integer from a header buffer.
       *
       * @param offset The offset into the buffer from which to parse.
       * @param length The number of header bytes to parse.
       * @param value Description of Parameter
       * @param buf Description of Parameter
       * @return The long value of the octal bytes.
       */
      public static int getLongOctalBytes( long value, byte[] buf, int offset, int length )
      {
          byte[] temp = new byte[ length + 1 ];
  
          getOctalBytes( value, temp, 0, length + 1 );
          System.arraycopy( temp, 0, buf, offset, length );
  
          return offset + length;
      }
  
      /**
       * Determine the number of bytes in an entry name.
       *
       * @param offset The offset into the buffer from which to parse.
       * @param length The number of header bytes to parse.
       * @param name Description of Parameter
       * @param buf Description of Parameter
       * @return The number of bytes in a header's entry name.
       */
      public static int getNameBytes( StringBuffer name, byte[] buf, int offset, int length )
      {
          int i;
  
          for( i = 0; i < length && i < name.length(); ++i )
          {
              buf[ offset + i ] = (byte)name.charAt( i );
          }
  
          for( ; i < length; ++i )
          {
              buf[ offset + i ] = 0;
          }
  
          return offset + length;
      }
  
      /**
       * Parse an octal integer from a header buffer.
       *
       * @param offset The offset into the buffer from which to parse.
       * @param length The number of header bytes to parse.
       * @param value Description of Parameter
       * @param buf Description of Parameter
       * @return The integer value of the octal bytes.
       */
      public static int getOctalBytes( long value, byte[] buf, int offset, int length )
      {
          byte[] result = new byte[ length ];
          int idx = length - 1;
  
          buf[ offset + idx ] = 0;
          --idx;
          buf[ offset + idx ] = (byte)' ';
          --idx;
  
          if( value == 0 )
          {
              buf[ offset + idx ] = (byte)'0';
              --idx;
          }
          else
          {
              for( long val = value; idx >= 0 && val > 0; --idx )
              {
                  buf[ offset + idx ] = (byte)( (byte)'0' + (byte)( val & 7 ) );
                  val = val >> 3;
              }
          }
  
          for( ; idx >= 0; --idx )
          {
              buf[ offset + idx ] = (byte)' ';
          }
  
          return offset + length;
      }
  
      /**
       * Compute the checksum of a tar entry header.
       *
       * @param buf The tar entry's header buffer.
       * @return The computed checksum.
       */
      public static long computeCheckSum( byte[] buf )
      {
          long sum = 0;
  
          for( int i = 0; i < buf.length; ++i )
          {
              sum += 255 & buf[ i ];
          }
  
          return sum;
      }
  
      /**
       * Parse an entry name from a header buffer.
       *
       * @param header The header buffer from which to parse.
       * @param offset The offset into the buffer from which to parse.
       * @param length The number of header bytes to parse.
       * @return The header's entry name.
       */
      public static StringBuffer parseName( byte[] header, int offset, int length )
      {
          StringBuffer result = new StringBuffer( length );
          int end = offset + length;
  
          for( int i = offset; i < end; ++i )
          {
              if( header[ i ] == 0 )
              {
                  break;
              }
  
              result.append( (char)header[ i ] );
          }
  
          return result;
      }
  
      /**
       * Parse an octal string from a header buffer. This is used for the file
       * permission mode value.
       *
       * @param header The header buffer from which to parse.
       * @param offset The offset into the buffer from which to parse.
       * @param length The number of header bytes to parse.
       * @return The long value of the octal string.
       */
      public static long parseOctal( byte[] header, int offset, int length )
      {
          long result = 0;
          boolean stillPadding = true;
          int end = offset + length;
  
          for( int i = offset; i < end; ++i )
          {
              if( header[ i ] == 0 )
              {
                  break;
              }
  
              if( header[ i ] == (byte)' ' || header[ i ] == '0' )
              {
                  if( stillPadding )
                  {
                      continue;
                  }
  
                  if( header[ i ] == (byte)' ' )
                  {
                      break;
                  }
              }
  
              stillPadding = false;
              result = ( result << 3 ) + ( header[ i ] - '0' );
          }
  
          return result;
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/AsiExtraField.java
  
  Index: AsiExtraField.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.zip;
  
  import java.util.zip.CRC32;
  import java.util.zip.ZipException;
  
  /**
   * Adds Unix file permission and UID/GID fields as well as symbolic link
   * handling. <p>
   *
   * This class uses the ASi extra field in the format: <pre>
   *         Value         Size            Description
   *         -----         ----            -----------
   * (Unix3) 0x756e        Short           tag for this extra block type
   *         TSize         Short           total data size for this block
   *         CRC           Long            CRC-32 of the remaining data
   *         Mode          Short           file permissions
   *         SizDev        Long            symlink'd size OR major/minor dev num
   *         UID           Short           user ID
   *         GID           Short           group ID
   *         (var.)        variable        symbolic link filename
   * </pre> taken from appnote.iz (Info-ZIP note, 981119) found at <a
   * href="ftp://ftp.uu.net/pub/archiving/zip/doc/">
   * ftp://ftp.uu.net/pub/archiving/zip/doc/</a> </p> <p>
   *
   * Short is two bytes and Long is four bytes in big endian byte and word order,
   * device numbers are currently not supported.</p>
   *
   * @author <a href="stefan.bodewig@epost.de">Stefan Bodewig</a>
   * @version $Revision: 1.1 $
   */
  public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable
  {
  
      private final static ZipShort HEADER_ID = new ZipShort( 0x756E );
  
      /**
       * Standard Unix stat(2) file mode.
       *
       * @since 1.1
       */
      private int mode = 0;
      /**
       * User ID.
       *
       * @since 1.1
       */
      private int uid = 0;
      /**
       * Group ID.
       *
       * @since 1.1
       */
      private int gid = 0;
      /**
       * File this entry points to, if it is a symbolic link. <p>
       *
       * empty string - if entry is not a symbolic link.</p>
       *
       * @since 1.1
       */
      private String link = "";
      /**
       * Is this an entry for a directory?
       *
       * @since 1.1
       */
      private boolean dirFlag = false;
  
      /**
       * Instance used to calculate checksums.
       *
       * @since 1.1
       */
      private CRC32 crc = new CRC32();
  
      public AsiExtraField()
      {
      }
  
      /**
       * Indicate whether this entry is a directory.
       *
       * @param dirFlag The new Directory value
       * @since 1.1
       */
      public void setDirectory( boolean dirFlag )
      {
          this.dirFlag = dirFlag;
          mode = getMode( mode );
      }
  
      /**
       * Set the group id.
       *
       * @param gid The new GroupId value
       * @since 1.1
       */
      public void setGroupId( int gid )
      {
          this.gid = gid;
      }
  
      /**
       * Indicate that this entry is a symbolic link to the given filename.
       *
       * @param name Name of the file this entry links to, empty String if it is
       *      not a symbolic link.
       * @since 1.1
       */
      public void setLinkedFile( String name )
      {
          link = name;
          mode = getMode( mode );
      }
  
      /**
       * File mode of this file.
       *
       * @param mode The new Mode value
       * @since 1.1
       */
      public void setMode( int mode )
      {
          this.mode = getMode( mode );
      }
  
      /**
       * Set the user id.
       *
       * @param uid The new UserId value
       * @since 1.1
       */
      public void setUserId( int uid )
      {
          this.uid = uid;
      }
  
      /**
       * Delegate to local file data.
       *
       * @return The CentralDirectoryData value
       * @since 1.1
       */
      public byte[] getCentralDirectoryData()
      {
          return getLocalFileDataData();
      }
  
      /**
       * Delegate to local file data.
       *
       * @return The CentralDirectoryLength value
       * @since 1.1
       */
      public ZipShort getCentralDirectoryLength()
      {
          return getLocalFileDataLength();
      }
  
      /**
       * Get the group id.
       *
       * @return The GroupId value
       * @since 1.1
       */
      public int getGroupId()
      {
          return gid;
      }
  
      /**
       * The Header-ID.
       *
       * @return The HeaderId value
       * @since 1.1
       */
      public ZipShort getHeaderId()
      {
          return HEADER_ID;
      }
  
      /**
       * Name of linked file
       *
       * @return name of the file this entry links to if it is a symbolic link,
       *      the empty string otherwise.
       * @since 1.1
       */
      public String getLinkedFile()
      {
          return link;
      }
  
      /**
       * The actual data to put into local file data - without Header-ID or length
       * specifier.
       *
       * @return The LocalFileDataData value
       * @since 1.1
       */
      public byte[] getLocalFileDataData()
      {
          // CRC will be added later
          byte[] data = new byte[ getLocalFileDataLength().getValue() - 4 ];
          System.arraycopy( ( new ZipShort( getMode() ) ).getBytes(), 0, data, 0, 2 );
  
          byte[] linkArray = getLinkedFile().getBytes();
          System.arraycopy( ( new ZipLong( linkArray.length ) ).getBytes(),
                            0, data, 2, 4 );
  
          System.arraycopy( ( new ZipShort( getUserId() ) ).getBytes(),
                            0, data, 6, 2 );
          System.arraycopy( ( new ZipShort( getGroupId() ) ).getBytes(),
                            0, data, 8, 2 );
  
          System.arraycopy( linkArray, 0, data, 10, linkArray.length );
  
          crc.reset();
          crc.update( data );
          long checksum = crc.getValue();
  
          byte[] result = new byte[ data.length + 4 ];
          System.arraycopy( ( new ZipLong( checksum ) ).getBytes(), 0, result, 0, 4 );
          System.arraycopy( data, 0, result, 4, data.length );
          return result;
      }
  
      /**
       * Length of the extra field in the local file data - without Header-ID or
       * length specifier.
       *
       * @return The LocalFileDataLength value
       * @since 1.1
       */
      public ZipShort getLocalFileDataLength()
      {
          return new ZipShort( 4// CRC
                               + 2// Mode
                               + 4// SizDev
                               + 2// UID
                               + 2// GID
                               + getLinkedFile().getBytes().length );
      }
  
      /**
       * File mode of this file.
       *
       * @return The Mode value
       * @since 1.1
       */
      public int getMode()
      {
          return mode;
      }
  
      /**
       * Get the user id.
       *
       * @return The UserId value
       * @since 1.1
       */
      public int getUserId()
      {
          return uid;
      }
  
      /**
       * Is this entry a directory?
       *
       * @return The Directory value
       * @since 1.1
       */
      public boolean isDirectory()
      {
          return dirFlag && !isLink();
      }
  
      /**
       * Is this entry a symbolic link?
       *
       * @return The Link value
       * @since 1.1
       */
      public boolean isLink()
      {
          return getLinkedFile().length() != 0;
      }
  
      /**
       * Populate data from this array as if it was in local file data.
       *
       * @param data Description of Parameter
       * @param offset Description of Parameter
       * @param length Description of Parameter
       * @exception ZipException Description of Exception
       * @since 1.1
       */
      public void parseFromLocalFileData( byte[] data, int offset, int length )
          throws ZipException
      {
  
          long givenChecksum = ( new ZipLong( data, offset ) ).getValue();
          byte[] tmp = new byte[ length - 4 ];
          System.arraycopy( data, offset + 4, tmp, 0, length - 4 );
          crc.reset();
          crc.update( tmp );
          long realChecksum = crc.getValue();
          if( givenChecksum != realChecksum )
          {
              throw new ZipException( "bad CRC checksum "
                                      + Long.toHexString( givenChecksum )
                                      + " instead of "
                                      + Long.toHexString( realChecksum ) );
          }
  
          int newMode = ( new ZipShort( tmp, 0 ) ).getValue();
          byte[] linkArray = new byte[ (int)( new ZipLong( tmp, 2 ) ).getValue() ];
          uid = ( new ZipShort( tmp, 6 ) ).getValue();
          gid = ( new ZipShort( tmp, 8 ) ).getValue();
  
          if( linkArray.length == 0 )
          {
              link = "";
          }
          else
          {
              System.arraycopy( tmp, 10, linkArray, 0, linkArray.length );
              link = new String( linkArray );
          }
          setDirectory( ( newMode & DIR_FLAG ) != 0 );
          setMode( newMode );
      }
  
      /**
       * Get the file mode for given permissions with the correct file type.
       *
       * @param mode Description of Parameter
       * @return The Mode value
       * @since 1.1
       */
      protected int getMode( int mode )
      {
          int type = FILE_FLAG;
          if( isLink() )
          {
              type = LINK_FLAG;
          }
          else if( isDirectory() )
          {
              type = DIR_FLAG;
          }
          return type | ( mode & PERM_MASK );
      }
  
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/ExtraFieldUtils.java
  
  Index: ExtraFieldUtils.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.zip;
  
  import java.util.ArrayList;
  import java.util.Hashtable;
  import java.util.zip.ZipException;
  
  /**
   * ZipExtraField related methods
   *
   * @author <a href="stefan.bodewig@epost.de">Stefan Bodewig</a>
   * @version $Revision: 1.1 $
   */
  public class ExtraFieldUtils
  {
  
      /**
       * Static registry of known extra fields.
       *
       * @since 1.1
       */
      private static Hashtable implementations;
  
      static
      {
          implementations = new Hashtable();
          register( AsiExtraField.class );
      }
  
      /**
       * Create an instance of the approriate ExtraField, falls back to {@link
       * UnrecognizedExtraField UnrecognizedExtraField}.
       *
       * @param headerId Description of Parameter
       * @return Description of the Returned Value
       * @exception InstantiationException Description of Exception
       * @exception IllegalAccessException Description of Exception
       * @since 1.1
       */
      public static ZipExtraField createExtraField( ZipShort headerId )
          throws InstantiationException, IllegalAccessException
      {
          Class c = (Class)implementations.get( headerId );
          if( c != null )
          {
              return (ZipExtraField)c.newInstance();
          }
          UnrecognizedExtraField u = new UnrecognizedExtraField();
          u.setHeaderId( headerId );
          return u;
      }
  
      /**
       * Merges the central directory fields of the given ZipExtraFields.
       *
       * @param data Description of Parameter
       * @return Description of the Returned Value
       * @since 1.1
       */
      public static byte[] mergeCentralDirectoryData( ZipExtraField[] data )
      {
          int sum = 4 * data.length;
          for( int i = 0; i < data.length; i++ )
          {
              sum += data[ i ].getCentralDirectoryLength().getValue();
          }
          byte[] result = new byte[ sum ];
          int start = 0;
          for( int i = 0; i < data.length; i++ )
          {
              System.arraycopy( data[ i ].getHeaderId().getBytes(),
                                0, result, start, 2 );
              System.arraycopy( data[ i ].getCentralDirectoryLength().getBytes(),
                                0, result, start + 2, 2 );
              byte[] local = data[ i ].getCentralDirectoryData();
              System.arraycopy( local, 0, result, start + 4, local.length );
              start += ( local.length + 4 );
          }
          return result;
      }
  
      /**
       * Merges the local file data fields of the given ZipExtraFields.
       *
       * @param data Description of Parameter
       * @return Description of the Returned Value
       * @since 1.1
       */
      public static byte[] mergeLocalFileDataData( ZipExtraField[] data )
      {
          int sum = 4 * data.length;
          for( int i = 0; i < data.length; i++ )
          {
              sum += data[ i ].getLocalFileDataLength().getValue();
          }
          byte[] result = new byte[ sum ];
          int start = 0;
          for( int i = 0; i < data.length; i++ )
          {
              System.arraycopy( data[ i ].getHeaderId().getBytes(),
                                0, result, start, 2 );
              System.arraycopy( data[ i ].getLocalFileDataLength().getBytes(),
                                0, result, start + 2, 2 );
              byte[] local = data[ i ].getLocalFileDataData();
              System.arraycopy( local, 0, result, start + 4, local.length );
              start += ( local.length + 4 );
          }
          return result;
      }
  
      /**
       * Split the array into ExtraFields and populate them with the give data.
       *
       * @param data Description of Parameter
       * @return Description of the Returned Value
       * @exception ZipException Description of Exception
       * @since 1.1
       */
      public static ZipExtraField[] parse( byte[] data )
          throws ZipException
      {
          ArrayList v = new ArrayList();
          int start = 0;
          while( start <= data.length - 4 )
          {
              ZipShort headerId = new ZipShort( data, start );
              int length = ( new ZipShort( data, start + 2 ) ).getValue();
              if( start + 4 + length > data.length )
              {
                  throw new ZipException( "data starting at " + start + " is in unknown format" );
              }
              try
              {
                  ZipExtraField ze = createExtraField( headerId );
                  ze.parseFromLocalFileData( data, start + 4, length );
                  v.add( ze );
              }
              catch( InstantiationException ie )
              {
                  throw new ZipException( ie.getMessage() );
              }
              catch( IllegalAccessException iae )
              {
                  throw new ZipException( iae.getMessage() );
              }
              start += ( length + 4 );
          }
          if( start != data.length )
          {// array not exhausted
              throw new ZipException( "data starting at " + start + " is in unknown format" );
          }
  
          final ZipExtraField[] result = new ZipExtraField[ v.size() ];
          return (ZipExtraField[])v.toArray( result );
      }
  
      /**
       * Register a ZipExtraField implementation. <p>
       *
       * The given class must have a no-arg constructor and implement the {@link
       * ZipExtraField ZipExtraField interface}.</p>
       *
       * @param c Description of Parameter
       * @since 1.1
       */
      public static void register( Class c )
      {
          try
          {
              ZipExtraField ze = (ZipExtraField)c.newInstance();
              implementations.put( ze.getHeaderId(), c );
          }
          catch( ClassCastException cc )
          {
              throw new RuntimeException( c +
                                          " doesn\'t implement ZipExtraField" );
          }
          catch( InstantiationException ie )
          {
              throw new RuntimeException( c + " is not a concrete class" );
          }
          catch( IllegalAccessException ie )
          {
              throw new RuntimeException( c +
                                          "\'s no-arg constructor is not public" );
          }
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/UnixStat.java
  
  Index: UnixStat.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.zip;
  
  /**
   * Constants from stat.h on Unix systems.
   *
   * @author <a href="stefan.bodewig@epost.de">Stefan Bodewig</a>
   * @version $Revision: 1.1 $
   */
  public interface UnixStat
  {
      /**
       * Bits used for permissions (and sticky bit)
       *
       * @since 1.1
       */
      int PERM_MASK = 07777;
      /**
       * Indicates symbolic links.
       *
       * @since 1.1
       */
      int LINK_FLAG = 0120000;
      /**
       * Indicates plain files.
       *
       * @since 1.1
       */
      int FILE_FLAG = 0100000;
      /**
       * Indicates directories.
       *
       * @since 1.1
       */
      int DIR_FLAG = 040000;
  
      // ----------------------------------------------------------
      // somewhat arbitrary choices that are quite common for shared
      // installations
      // -----------------------------------------------------------
  
      /**
       * Default permissions for symbolic links.
       *
       * @since 1.1
       */
      int DEFAULT_LINK_PERM = 0777;
      /**
       * Default permissions for directories.
       *
       * @since 1.1
       */
      int DEFAULT_DIR_PERM = 0755;
      /**
       * Default permissions for plain files.
       *
       * @since 1.1
       */
      int DEFAULT_FILE_PERM = 0644;
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/UnrecognizedExtraField.java
  
  Index: UnrecognizedExtraField.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.zip;
  
  /**
   * Simple placeholder for all those extra fields we don't want to deal with. <p>
   *
   * Assumes local file data and central directory entries are identical - unless
   * told the opposite.</p>
   *
   * @author <a href="stefan.bodewig@epost.de">Stefan Bodewig</a>
   * @version $Revision: 1.1 $
   */
  public class UnrecognizedExtraField
      implements ZipExtraField
  {
      /**
       * Extra field data in central directory - without Header-ID or length
       * specifier.
       *
       * @since 1.1
       */
      private byte[] centralData;
  
      /**
       * The Header-ID.
       *
       * @since 1.1
       */
      private ZipShort headerId;
  
      /**
       * Extra field data in local file data - without Header-ID or length
       * specifier.
       *
       * @since 1.1
       */
      private byte[] localData;
  
      public void setCentralDirectoryData( byte[] data )
      {
          centralData = data;
      }
  
      public void setHeaderId( ZipShort headerId )
      {
          this.headerId = headerId;
      }
  
      public void setLocalFileDataData( byte[] data )
      {
          localData = data;
      }
  
      public byte[] getCentralDirectoryData()
      {
          if( centralData != null )
          {
              return centralData;
          }
          return getLocalFileDataData();
      }
  
      public ZipShort getCentralDirectoryLength()
      {
          if( centralData != null )
          {
              return new ZipShort( centralData.length );
          }
          return getLocalFileDataLength();
      }
  
      public ZipShort getHeaderId()
      {
          return headerId;
      }
  
      public byte[] getLocalFileDataData()
      {
          return localData;
      }
  
      public ZipShort getLocalFileDataLength()
      {
          return new ZipShort( localData.length );
      }
  
      public void parseFromLocalFileData( byte[] data, int offset, int length )
      {
          byte[] tmp = new byte[ length ];
          System.arraycopy( data, offset, tmp, 0, length );
          setLocalFileDataData( tmp );
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/ZipEntry.java
  
  Index: ZipEntry.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.zip;
  
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.util.ArrayList;
  import java.util.zip.ZipException;
  
  /**
   * Extension that adds better handling of extra fields and provides access to
   * the internal and external file attributes.
   *
   * @author <a href="stefan.bodewig@epost.de">Stefan Bodewig</a>
   * @version $Revision: 1.1 $
   */
  public class ZipEntry
      extends java.util.zip.ZipEntry
  {
      /**
       * Helper for JDK 1.1
       *
       * @since 1.2
       */
      private static Method setCompressedSizeMethod = null;
      /**
       * Helper for JDK 1.1
       *
       * @since 1.2
       */
      private static Object lockReflection = new Object();
      /**
       * Helper for JDK 1.1
       *
       * @since 1.2
       */
      private static boolean triedToGetMethod = false;
  
      private int internalAttributes = 0;
      private long externalAttributes = 0;
      private ArrayList extraFields = new ArrayList();
  
      /**
       * Helper for JDK 1.1 <-> 1.2 incompatibility.
       *
       * @since 1.2
       */
      private Long compressedSize = null;
  
      /**
       * Creates a new zip entry with the specified name.
       *
       * @param name Description of Parameter
       * @since 1.1
       */
      public ZipEntry( String name )
      {
          super( name );
      }
  
      /**
       * Creates a new zip entry with fields taken from the specified zip entry.
       *
       * @param entry Description of Parameter
       * @exception ZipException Description of Exception
       * @since 1.1
       */
      public ZipEntry( java.util.zip.ZipEntry entry )
          throws ZipException
      {
          /*
           * REVISIT: call super(entry) instead of this stuff in Ant2,
           * "copy constructor" has not been available in JDK 1.1
           */
          super( entry.getName() );
  
          setComment( entry.getComment() );
          setMethod( entry.getMethod() );
          setTime( entry.getTime() );
  
          long size = entry.getSize();
          if( size > 0 )
          {
              setSize( size );
          }
          long cSize = entry.getCompressedSize();
          if( cSize > 0 )
          {
              setComprSize( cSize );
          }
          long crc = entry.getCrc();
          if( crc > 0 )
          {
              setCrc( crc );
          }
  
          byte[] extra = entry.getExtra();
          if( extra != null )
          {
              setExtraFields( ExtraFieldUtils.parse( extra ) );
          }
          else
          {
              // initializes extra data to an empty byte array
              setExtra();
          }
      }
  
      /**
       * Creates a new zip entry with fields taken from the specified zip entry.
       *
       * @param entry Description of Parameter
       * @exception ZipException Description of Exception
       * @since 1.1
       */
      public ZipEntry( ZipEntry entry )
          throws ZipException
      {
          this( (java.util.zip.ZipEntry)entry );
          setInternalAttributes( entry.getInternalAttributes() );
          setExternalAttributes( entry.getExternalAttributes() );
          setExtraFields( entry.getExtraFields() );
      }
  
      /**
       * Try to get a handle to the setCompressedSize method.
       *
       * @since 1.2
       */
      private static void checkSCS()
      {
          if( !triedToGetMethod )
          {
              synchronized( lockReflection )
              {
                  triedToGetMethod = true;
                  try
                  {
                      setCompressedSizeMethod =
                          java.util.zip.ZipEntry.class.getMethod( "setCompressedSize",
                                                                  new Class[]{Long.TYPE} );
                  }
                  catch( NoSuchMethodException nse )
                  {
                  }
              }
          }
      }
  
      /**
       * Are we running JDK 1.2 or higher?
       *
       * @return Description of the Returned Value
       * @since 1.2
       */
      private static boolean haveSetCompressedSize()
      {
          checkSCS();
          return setCompressedSizeMethod != null;
      }
  
      /**
       * Invoke setCompressedSize via reflection.
       *
       * @param ze Description of Parameter
       * @param size Description of Parameter
       * @since 1.2
       */
      private static void performSetCompressedSize( ZipEntry ze, long size )
      {
          Long[] s = {new Long( size )};
          try
          {
              setCompressedSizeMethod.invoke( ze, s );
          }
          catch( InvocationTargetException ite )
          {
              Throwable nested = ite.getTargetException();
              throw new RuntimeException( "Exception setting the compressed size "
                                          + "of " + ze + ": "
                                          + nested.getMessage() );
          }
          catch( Throwable other )
          {
              throw new RuntimeException( "Exception setting the compressed size "
                                          + "of " + ze + ": "
                                          + other.getMessage() );
          }
      }
  
      /**
       * Make this class work in JDK 1.1 like a 1.2 class. <p>
       *
       * This either stores the size for later usage or invokes setCompressedSize
       * via reflection.</p>
       *
       * @param size The new ComprSize value
       * @since 1.2
       */
      public void setComprSize( long size )
      {
          if( haveSetCompressedSize() )
          {
              performSetCompressedSize( this, size );
          }
          else
          {
              compressedSize = new Long( size );
          }
      }
  
      /**
       * Sets the external file attributes.
       *
       * @param value The new ExternalAttributes value
       * @since 1.1
       */
      public void setExternalAttributes( long value )
      {
          externalAttributes = value;
      }
  
      /**
       * Throws an Exception if extra data cannot be parsed into extra fields.
       *
       * @param extra The new Extra value
       * @exception RuntimeException Description of Exception
       * @since 1.1
       */
      public void setExtra( byte[] extra )
          throws RuntimeException
      {
          try
          {
              setExtraFields( ExtraFieldUtils.parse( extra ) );
          }
          catch( Exception e )
          {
              throw new RuntimeException( e.getMessage() );
          }
      }
  
      /**
       * Replaces all currently attached extra fields with the new array.
       *
       * @param fields The new ExtraFields value
       * @since 1.1
       */
      public void setExtraFields( ZipExtraField[] fields )
      {
          extraFields.clear();
          for( int i = 0; i < fields.length; i++ )
          {
              extraFields.add( fields[ i ] );
          }
          setExtra();
      }
  
      /**
       * Sets the internal file attributes.
       *
       * @param value The new InternalAttributes value
       * @since 1.1
       */
      public void setInternalAttributes( int value )
      {
          internalAttributes = value;
      }
  
      /**
       * Retrieves the extra data for the central directory.
       *
       * @return The CentralDirectoryExtra value
       * @since 1.1
       */
      public byte[] getCentralDirectoryExtra()
      {
          return ExtraFieldUtils.mergeCentralDirectoryData( getExtraFields() );
      }
  
      /**
       * Override to make this class work in JDK 1.1 like a 1.2 class.
       *
       * @return The CompressedSize value
       * @since 1.2
       */
      public long getCompressedSize()
      {
          if( compressedSize != null )
          {
              // has been set explicitly and we are running in a 1.1 VM
              return compressedSize.longValue();
          }
          return super.getCompressedSize();
      }
  
      /**
       * Retrieves the external file attributes.
       *
       * @return The ExternalAttributes value
       * @since 1.1
       */
      public long getExternalAttributes()
      {
          return externalAttributes;
      }
  
      /**
       * Retrieves extra fields.
       *
       * @return The ExtraFields value
       * @since 1.1
       */
      public ZipExtraField[] getExtraFields()
      {
          final ZipExtraField[] result = new ZipExtraField[ extraFields.size() ];
          return (ZipExtraField[])extraFields.toArray( result );
      }
  
      /**
       * Retrieves the internal file attributes.
       *
       * @return The InternalAttributes value
       * @since 1.1
       */
      public int getInternalAttributes()
      {
          return internalAttributes;
      }
  
      /**
       * Retrieves the extra data for the local file data.
       *
       * @return The LocalFileDataExtra value
       * @since 1.1
       */
      public byte[] getLocalFileDataExtra()
      {
          byte[] extra = getExtra();
          return extra != null ? extra : new byte[ 0 ];
      }
  
      /**
       * Adds an extra fields - replacing an already present extra field of the
       * same type.
       *
       * @param ze The feature to be added to the ExtraField attribute
       * @since 1.1
       */
      public void addExtraField( ZipExtraField ze )
      {
          ZipShort type = ze.getHeaderId();
          boolean done = false;
          for( int i = 0; !done && i < extraFields.size(); i++ )
          {
              if( ( (ZipExtraField)extraFields.get( i ) ).getHeaderId().equals( type ) )
              {
                  extraFields.set( i, ze );
                  done = true;
              }
          }
          if( !done )
          {
              extraFields.add( ze );
          }
          setExtra();
      }
  
      /**
       * Overwrite clone
       *
       * @return Description of the Returned Value
       * @since 1.1
       */
      public Object clone()
      {
          ZipEntry e = null;
          try
          {
              e = new ZipEntry( (java.util.zip.ZipEntry)super.clone() );
          }
          catch( Exception ex )
          {
              // impossible as extra data is in correct format
              ex.printStackTrace();
          }
          e.setInternalAttributes( getInternalAttributes() );
          e.setExternalAttributes( getExternalAttributes() );
          e.setExtraFields( getExtraFields() );
          return e;
      }
  
      /**
       * Remove an extra fields.
       *
       * @param type Description of Parameter
       * @since 1.1
       */
      public void removeExtraField( ZipShort type )
      {
          boolean done = false;
          for( int i = 0; !done && i < extraFields.size(); i++ )
          {
              if( ( (ZipExtraField)extraFields.get( i ) ).getHeaderId().equals( type ) )
              {
                  extraFields.remove( i );
                  done = true;
              }
          }
          if( !done )
          {
              throw new java.util.NoSuchElementException();
          }
          setExtra();
      }
  
      /**
       * Unfortunately {@link java.util.zip.ZipOutputStream
       * java.util.zip.ZipOutputStream} seems to access the extra data directly,
       * so overriding getExtra doesn't help - we need to modify super's data
       * directly.
       *
       * @since 1.1
       */
      protected void setExtra()
      {
          super.setExtra( ExtraFieldUtils.mergeLocalFileDataData( getExtraFields() ) );
      }
  
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/ZipExtraField.java
  
  Index: ZipExtraField.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.zip;
  
  import java.util.zip.ZipException;
  
  /**
   * General format of extra field data. <p>
   *
   * Extra fields usually appear twice per file, once in the local file data and
   * once in the central directory. Usually they are the same, but they don't have
   * to be. {@link java.util.zip.ZipOutputStream java.util.zip.ZipOutputStream}
   * will only use the local file data in both places.</p>
   *
   * @author <a href="stefan.bodewig@epost.de">Stefan Bodewig</a>
   * @version $Revision: 1.1 $
   */
  public interface ZipExtraField
  {
  
      /**
       * The Header-ID.
       *
       * @return The HeaderId value
       * @since 1.1
       */
      ZipShort getHeaderId();
  
      /**
       * Length of the extra field in the local file data - without Header-ID or
       * length specifier.
       *
       * @return The LocalFileDataLength value
       * @since 1.1
       */
      ZipShort getLocalFileDataLength();
  
      /**
       * Length of the extra field in the central directory - without Header-ID or
       * length specifier.
       *
       * @return The CentralDirectoryLength value
       * @since 1.1
       */
      ZipShort getCentralDirectoryLength();
  
      /**
       * The actual data to put into local file data - without Header-ID or length
       * specifier.
       *
       * @return The LocalFileDataData value
       * @since 1.1
       */
      byte[] getLocalFileDataData();
  
      /**
       * The actual data to put central directory - without Header-ID or length
       * specifier.
       *
       * @return The CentralDirectoryData value
       * @since 1.1
       */
      byte[] getCentralDirectoryData();
  
      /**
       * Populate data from this array as if it was in local file data.
       *
       * @param data Description of Parameter
       * @param offset Description of Parameter
       * @param length Description of Parameter
       * @exception ZipException Description of Exception
       * @since 1.1
       */
      void parseFromLocalFileData( byte[] data, int offset, int length )
          throws ZipException;
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/ZipLong.java
  
  Index: ZipLong.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.zip;
  
  /**
   * Utility class that represents a four byte integer with conversion rules for
   * the big endian byte order of ZIP files.
   *
   * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
   * @version $Revision: 1.1 $
   */
  public class ZipLong implements Cloneable
  {
  
      private long value;
  
      /**
       * Create instance from a number.
       *
       * @param value Description of Parameter
       * @since 1.1
       */
      public ZipLong( long value )
      {
          this.value = value;
      }
  
      /**
       * Create instance from bytes.
       *
       * @param bytes Description of Parameter
       * @since 1.1
       */
      public ZipLong( byte[] bytes )
      {
          this( bytes, 0 );
      }
  
      /**
       * Create instance from the four bytes starting at offset.
       *
       * @param bytes Description of Parameter
       * @param offset Description of Parameter
       * @since 1.1
       */
      public ZipLong( byte[] bytes, int offset )
      {
          value = ( bytes[ offset + 3 ] << 24 ) & 0xFF000000l;
          value += ( bytes[ offset + 2 ] << 16 ) & 0xFF0000;
          value += ( bytes[ offset + 1 ] << 8 ) & 0xFF00;
          value += ( bytes[ offset ] & 0xFF );
      }
  
      /**
       * Get value as two bytes in big endian byte order.
       *
       * @return The Bytes value
       * @since 1.1
       */
      public byte[] getBytes()
      {
          byte[] result = new byte[ 4 ];
          result[ 0 ] = (byte)( ( value & 0xFF ) );
          result[ 1 ] = (byte)( ( value & 0xFF00 ) >> 8 );
          result[ 2 ] = (byte)( ( value & 0xFF0000 ) >> 16 );
          result[ 3 ] = (byte)( ( value & 0xFF000000l ) >> 24 );
          return result;
      }
  
      /**
       * Get value as Java int.
       *
       * @return The Value value
       * @since 1.1
       */
      public long getValue()
      {
          return value;
      }
  
      /**
       * Override to make two instances with same value equal.
       *
       * @param o Description of Parameter
       * @return Description of the Returned Value
       * @since 1.1
       */
      public boolean equals( Object o )
      {
          if( o == null || !( o instanceof ZipLong ) )
          {
              return false;
          }
          return value == ( (ZipLong)o ).getValue();
      }
  
      /**
       * Override to make two instances with same value equal.
       *
       * @return Description of the Returned Value
       * @since 1.1
       */
      public int hashCode()
      {
          return (int)value;
      }
  
  }// ZipLong
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/ZipOutputStream.java
  
  Index: ZipOutputStream.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.zip;
  
  import java.io.IOException;
  import java.io.OutputStream;
  import java.io.UnsupportedEncodingException;
  import java.util.ArrayList;
  import java.util.Date;
  import java.util.Hashtable;
  import java.util.zip.CRC32;
  import java.util.zip.Deflater;
  import java.util.zip.DeflaterOutputStream;
  import java.util.zip.ZipException;
  
  /**
   * Reimplementation of {@link java.util.zip.ZipOutputStream
   * java.util.zip.ZipOutputStream} that does handle the extended functionality of
   * this package, especially internal/external file attributes and extra fields
   * with different layouts for local file data and central directory entries. <p>
   *
   * This implementation will use a Data Descriptor to store size and CRC
   * information for DEFLATED entries, this means, you don't need to calculate
   * them yourself. Unfortunately this is not possible for the STORED method, here
   * setting the CRC and uncompressed size information is required before {@link
   * #putNextEntry putNextEntry} will be called.</p>
   *
   * @author <a href="stefan.bodewig@epost.de">Stefan Bodewig</a>
   * @version $Revision: 1.1 $
   */
  public class ZipOutputStream extends DeflaterOutputStream
  {
  
      /**
       * Helper, a 0 as ZipShort.
       *
       * @since 1.1
       */
      private final static byte[] ZERO = {0, 0};
  
      /**
       * Helper, a 0 as ZipLong.
       *
       * @since 1.1
       */
      private final static byte[] LZERO = {0, 0, 0, 0};
  
      /**
       * Compression method for deflated entries.
       *
       * @since 1.1
       */
      public final static int DEFLATED = ZipEntry.DEFLATED;
  
      /**
       * Compression method for deflated entries.
       *
       * @since 1.1
       */
      public final static int STORED = ZipEntry.STORED;
  
      /*
       * Various ZIP constants
       */
      /**
       * local file header signature
       *
       * @since 1.1
       */
      protected final static ZipLong LFH_SIG = new ZipLong( 0X04034B50L );
      /**
       * data descriptor signature
       *
       * @since 1.1
       */
      protected final static ZipLong DD_SIG = new ZipLong( 0X08074B50L );
      /**
       * central file header signature
       *
       * @since 1.1
       */
      protected final static ZipLong CFH_SIG = new ZipLong( 0X02014B50L );
      /**
       * end of central dir signature
       *
       * @since 1.1
       */
      protected final static ZipLong EOCD_SIG = new ZipLong( 0X06054B50L );
  
      /**
       * Smallest date/time ZIP can handle.
       *
       * @since 1.1
       */
      private final static ZipLong DOS_TIME_MIN = new ZipLong( 0x00002100L );
  
      /**
       * The file comment.
       *
       * @since 1.1
       */
      private String comment = "";
  
      /**
       * Compression level for next entry.
       *
       * @since 1.1
       */
      private int level = Deflater.DEFAULT_COMPRESSION;
  
      /**
       * Default compression method for next entry.
       *
       * @since 1.1
       */
      private int method = DEFLATED;
  
      /**
       * List of ZipEntries written so far.
       *
       * @since 1.1
       */
      private ArrayList entries = new ArrayList();
  
      /**
       * CRC instance to avoid parsing DEFLATED data twice.
       *
       * @since 1.1
       */
      private CRC32 crc = new CRC32();
  
      /**
       * Count the bytes written to out.
       *
       * @since 1.1
       */
      private long written = 0;
  
      /**
       * Data for current entry started here.
       *
       * @since 1.1
       */
      private long dataStart = 0;
  
      /**
       * Start of central directory.
       *
       * @since 1.1
       */
      private ZipLong cdOffset = new ZipLong( 0 );
  
      /**
       * Length of central directory.
       *
       * @since 1.1
       */
      private ZipLong cdLength = new ZipLong( 0 );
  
      /**
       * Holds the offsets of the LFH starts for each entry
       *
       * @since 1.1
       */
      private Hashtable offsets = new Hashtable();
  
      /**
       * The encoding to use for filenames and the file comment. <p>
       *
       * For a list of possible values see <a
       * href="http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html">
       * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html
       * </a>. Defaults to the platform's default character encoding.</p>
       *
       * @since 1.3
       */
      private String encoding = null;
  
      /**
       * Current entry.
       *
       * @since 1.1
       */
      private ZipEntry entry;
  
      /**
       * Creates a new ZIP OutputStream filtering the underlying stream.
       *
       * @param out Description of Parameter
       * @since 1.1
       */
      public ZipOutputStream( OutputStream out )
      {
          super( out, new Deflater( Deflater.DEFAULT_COMPRESSION, true ) );
      }
  
      /**
       * Convert a Date object to a DOS date/time field. <p>
       *
       * Stolen from InfoZip's <code>fileio.c</code></p>
       *
       * @param time Description of Parameter
       * @return Description of the Returned Value
       * @since 1.1
       */
      protected static ZipLong toDosTime( Date time )
      {
          int year = time.getYear() + 1900;
          int month = time.getMonth() + 1;
          if( year < 1980 )
          {
              return DOS_TIME_MIN;
          }
          long value = ( ( year - 1980 ) << 25 )
              | ( month << 21 )
              | ( time.getDate() << 16 )
              | ( time.getHours() << 11 )
              | ( time.getMinutes() << 5 )
              | ( time.getSeconds() >> 1 );
  
          byte[] result = new byte[ 4 ];
          result[ 0 ] = (byte)( ( value & 0xFF ) );
          result[ 1 ] = (byte)( ( value & 0xFF00 ) >> 8 );
          result[ 2 ] = (byte)( ( value & 0xFF0000 ) >> 16 );
          result[ 3 ] = (byte)( ( value & 0xFF000000l ) >> 24 );
          return new ZipLong( result );
      }
  
      /**
       * Set the file comment.
       *
       * @param comment The new Comment value
       * @since 1.1
       */
      public void setComment( String comment )
      {
          this.comment = comment;
      }
  
      /**
       * The encoding to use for filenames and the file comment. <p>
       *
       * For a list of possible values see <a
       * href="http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html">
       * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html
       * </a>. Defaults to the platform's default character encoding.</p>
       *
       * @param encoding The new Encoding value
       * @since 1.3
       */
      public void setEncoding( String encoding )
      {
          this.encoding = encoding;
      }
  
      /**
       * Sets the compression level for subsequent entries. <p>
       *
       * Default is Deflater.DEFAULT_COMPRESSION.</p>
       *
       * @param level The new Level value
       * @since 1.1
       */
      public void setLevel( int level )
      {
          this.level = level;
      }
  
      /**
       * Sets the default compression method for subsequent entries. <p>
       *
       * Default is DEFLATED.</p>
       *
       * @param method The new Method value
       * @since 1.1
       */
      public void setMethod( int method )
      {
          this.method = method;
      }
  
      /**
       * The encoding to use for filenames and the file comment.
       *
       * @return null if using the platform's default character encoding.
       * @since 1.3
       */
      public String getEncoding()
      {
          return encoding;
      }
  
      /**
       * Writes all necessary data for this entry.
       *
       * @exception IOException Description of Exception
       * @since 1.1
       */
      public void closeEntry()
          throws IOException
      {
          if( entry == null )
          {
              return;
          }
  
          long realCrc = crc.getValue();
          crc.reset();
  
          if( entry.getMethod() == DEFLATED )
          {
              def.finish();
              while( !def.finished() )
              {
                  deflate();
              }
  
              entry.setSize( def.getTotalIn() );
              entry.setComprSize( def.getTotalOut() );
              entry.setCrc( realCrc );
  
              def.reset();
  
              written += entry.getCompressedSize();
          }
          else
          {
              if( entry.getCrc() != realCrc )
              {
                  throw new ZipException( "bad CRC checksum for entry "
                                          + entry.getName() + ": "
                                          + Long.toHexString( entry.getCrc() )
                                          + " instead of "
                                          + Long.toHexString( realCrc ) );
              }
  
              if( entry.getSize() != written - dataStart )
              {
                  throw new ZipException( "bad size for entry "
                                          + entry.getName() + ": "
                                          + entry.getSize()
                                          + " instead of "
                                          + ( written - dataStart ) );
              }
  
          }
  
          writeDataDescriptor( entry );
          entry = null;
      }
  
      /*
       * Found out by experiment, that DeflaterOutputStream.close()
       * will call finish() - so we don't need to override close
       * ourselves.
       */
      /**
       * Finishs writing the contents and closes this as well as the underlying
       * stream.
       *
       * @exception IOException Description of Exception
       * @since 1.1
       */
      public void finish()
          throws IOException
      {
          closeEntry();
          cdOffset = new ZipLong( written );
          for( int i = 0; i < entries.size(); i++ )
          {
              writeCentralFileHeader( (ZipEntry)entries.get( i ) );
          }
          cdLength = new ZipLong( written - cdOffset.getValue() );
          writeCentralDirectoryEnd();
          offsets.clear();
          entries.clear();
      }
  
      /**
       * Begin writing next entry.
       *
       * @param ze Description of Parameter
       * @exception IOException Description of Exception
       * @since 1.1
       */
      public void putNextEntry( ZipEntry ze )
          throws IOException
      {
          closeEntry();
  
          entry = ze;
          entries.add( entry );
  
          if( entry.getMethod() == -1 )
          {// not specified
              entry.setMethod( method );
          }
  
          if( entry.getTime() == -1 )
          {// not specified
              entry.setTime( System.currentTimeMillis() );
          }
  
          if( entry.getMethod() == STORED )
          {
              if( entry.getSize() == -1 )
              {
                  throw new ZipException( "uncompressed size is required for STORED method" );
              }
              if( entry.getCrc() == -1 )
              {
                  throw new ZipException( "crc checksum is required for STORED method" );
              }
              entry.setComprSize( entry.getSize() );
          }
          else
          {
              def.setLevel( level );
          }
          writeLocalFileHeader( entry );
      }
  
      /**
       * Writes bytes to ZIP entry. <p>
       *
       * Override is necessary to support STORED entries, as well as calculationg
       * CRC automatically for DEFLATED entries.</p>
       *
       * @param b Description of Parameter
       * @param offset Description of Parameter
       * @param length Description of Parameter
       * @exception IOException Description of Exception
       */
      public void write( byte[] b, int offset, int length )
          throws IOException
      {
          if( entry.getMethod() == DEFLATED )
          {
              super.write( b, offset, length );
          }
          else
          {
              out.write( b, offset, length );
              written += length;
          }
          crc.update( b, offset, length );
      }
  
      /**
       * Retrieve the bytes for the given String in the encoding set for this
       * Stream.
       *
       * @param name Description of Parameter
       * @return The Bytes value
       * @exception ZipException Description of Exception
       * @since 1.3
       */
      protected byte[] getBytes( String name )
          throws ZipException
      {
          if( encoding == null )
          {
              return name.getBytes();
          }
          else
          {
              try
              {
                  return name.getBytes( encoding );
              }
              catch( UnsupportedEncodingException uee )
              {
                  throw new ZipException( uee.getMessage() );
              }
          }
      }
  
      /**
       * Writes the &quot;End of central dir record&quot;
       *
       * @exception IOException Description of Exception
       * @since 1.1
       */
      protected void writeCentralDirectoryEnd()
          throws IOException
      {
          out.write( EOCD_SIG.getBytes() );
  
          // disk numbers
          out.write( ZERO );
          out.write( ZERO );
  
          // number of entries
          byte[] num = ( new ZipShort( entries.size() ) ).getBytes();
          out.write( num );
          out.write( num );
  
          // length and location of CD
          out.write( cdLength.getBytes() );
          out.write( cdOffset.getBytes() );
  
          // ZIP file comment
          byte[] data = getBytes( comment );
          out.write( ( new ZipShort( data.length ) ).getBytes() );
          out.write( data );
      }
  
      /**
       * Writes the central file header entry
       *
       * @param ze Description of Parameter
       * @exception IOException Description of Exception
       * @since 1.1
       */
      protected void writeCentralFileHeader( ZipEntry ze )
          throws IOException
      {
          out.write( CFH_SIG.getBytes() );
          written += 4;
  
          // version made by
          out.write( ( new ZipShort( 20 ) ).getBytes() );
          written += 2;
  
          // version needed to extract
          // general purpose bit flag
          if( ze.getMethod() == DEFLATED )
          {
              // requires version 2 as we are going to store length info
              // in the data descriptor
              out.write( ( new ZipShort( 20 ) ).getBytes() );
  
              // bit3 set to signal, we use a data descriptor
              out.write( ( new ZipShort( 8 ) ).getBytes() );
          }
          else
          {
              out.write( ( new ZipShort( 10 ) ).getBytes() );
              out.write( ZERO );
          }
          written += 4;
  
          // compression method
          out.write( ( new ZipShort( ze.getMethod() ) ).getBytes() );
          written += 2;
  
          // last mod. time and date
          out.write( toDosTime( new Date( ze.getTime() ) ).getBytes() );
          written += 4;
  
          // CRC
          // compressed length
          // uncompressed length
          out.write( ( new ZipLong( ze.getCrc() ) ).getBytes() );
          out.write( ( new ZipLong( ze.getCompressedSize() ) ).getBytes() );
          out.write( ( new ZipLong( ze.getSize() ) ).getBytes() );
          written += 12;
  
          // file name length
          byte[] name = getBytes( ze.getName() );
          out.write( ( new ZipShort( name.length ) ).getBytes() );
          written += 2;
  
          // extra field length
          byte[] extra = ze.getCentralDirectoryExtra();
          out.write( ( new ZipShort( extra.length ) ).getBytes() );
          written += 2;
  
          // file comment length
          String comm = ze.getComment();
          if( comm == null )
          {
              comm = "";
          }
          byte[] comment = getBytes( comm );
          out.write( ( new ZipShort( comment.length ) ).getBytes() );
          written += 2;
  
          // disk number start
          out.write( ZERO );
          written += 2;
  
          // internal file attributes
          out.write( ( new ZipShort( ze.getInternalAttributes() ) ).getBytes() );
          written += 2;
  
          // external file attributes
          out.write( ( new ZipLong( ze.getExternalAttributes() ) ).getBytes() );
          written += 4;
  
          // relative offset of LFH
          out.write( ( (ZipLong)offsets.get( ze ) ).getBytes() );
          written += 4;
  
          // file name
          out.write( name );
          written += name.length;
  
          // extra field
          out.write( extra );
          written += extra.length;
  
          // file comment
          out.write( comment );
          written += comment.length;
      }
  
      /**
       * Writes the data descriptor entry
       *
       * @param ze Description of Parameter
       * @exception IOException Description of Exception
       * @since 1.1
       */
      protected void writeDataDescriptor( ZipEntry ze )
          throws IOException
      {
          if( ze.getMethod() != DEFLATED )
          {
              return;
          }
          out.write( DD_SIG.getBytes() );
          out.write( ( new ZipLong( entry.getCrc() ) ).getBytes() );
          out.write( ( new ZipLong( entry.getCompressedSize() ) ).getBytes() );
          out.write( ( new ZipLong( entry.getSize() ) ).getBytes() );
          written += 16;
      }
  
      /**
       * Writes the local file header entry
       *
       * @param ze Description of Parameter
       * @exception IOException Description of Exception
       * @since 1.1
       */
      protected void writeLocalFileHeader( ZipEntry ze )
          throws IOException
      {
          offsets.put( ze, new ZipLong( written ) );
  
          out.write( LFH_SIG.getBytes() );
          written += 4;
  
          // version needed to extract
          // general purpose bit flag
          if( ze.getMethod() == DEFLATED )
          {
              // requires version 2 as we are going to store length info
              // in the data descriptor
              out.write( ( new ZipShort( 20 ) ).getBytes() );
  
              // bit3 set to signal, we use a data descriptor
              out.write( ( new ZipShort( 8 ) ).getBytes() );
          }
          else
          {
              out.write( ( new ZipShort( 10 ) ).getBytes() );
              out.write( ZERO );
          }
          written += 4;
  
          // compression method
          out.write( ( new ZipShort( ze.getMethod() ) ).getBytes() );
          written += 2;
  
          // last mod. time and date
          out.write( toDosTime( new Date( ze.getTime() ) ).getBytes() );
          written += 4;
  
          // CRC
          // compressed length
          // uncompressed length
          if( ze.getMethod() == DEFLATED )
          {
              out.write( LZERO );
              out.write( LZERO );
              out.write( LZERO );
          }
          else
          {
              out.write( ( new ZipLong( ze.getCrc() ) ).getBytes() );
              out.write( ( new ZipLong( ze.getSize() ) ).getBytes() );
              out.write( ( new ZipLong( ze.getSize() ) ).getBytes() );
          }
          written += 12;
  
          // file name length
          byte[] name = getBytes( ze.getName() );
          out.write( ( new ZipShort( name.length ) ).getBytes() );
          written += 2;
  
          // extra field length
          byte[] extra = ze.getLocalFileDataExtra();
          out.write( ( new ZipShort( extra.length ) ).getBytes() );
          written += 2;
  
          // file name
          out.write( name );
          written += name.length;
  
          // extra field
          out.write( extra );
          written += extra.length;
  
          dataStart = written;
      }
  
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/zip/ZipShort.java
  
  Index: ZipShort.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.aut.zip;
  
  /**
   * Utility class that represents a two byte integer with conversion rules for
   * the big endian byte order of ZIP files.
   *
   * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
   * @version $Revision: 1.1 $
   */
  public class ZipShort implements Cloneable
  {
  
      private int value;
  
      /**
       * Create instance from a number.
       *
       * @param value Description of Parameter
       * @since 1.1
       */
      public ZipShort( int value )
      {
          this.value = value;
      }
  
      /**
       * Create instance from bytes.
       *
       * @param bytes Description of Parameter
       * @since 1.1
       */
      public ZipShort( byte[] bytes )
      {
          this( bytes, 0 );
      }
  
      /**
       * Create instance from the two bytes starting at offset.
       *
       * @param bytes Description of Parameter
       * @param offset Description of Parameter
       * @since 1.1
       */
      public ZipShort( byte[] bytes, int offset )
      {
          value = ( bytes[ offset + 1 ] << 8 ) & 0xFF00;
          value += ( bytes[ offset ] & 0xFF );
      }
  
      /**
       * Get value as two bytes in big endian byte order.
       *
       * @return The Bytes value
       * @since 1.1
       */
      public byte[] getBytes()
      {
          byte[] result = new byte[ 2 ];
          result[ 0 ] = (byte)( value & 0xFF );
          result[ 1 ] = (byte)( ( value & 0xFF00 ) >> 8 );
          return result;
      }
  
      /**
       * Get value as Java int.
       *
       * @return The Value value
       * @since 1.1
       */
      public int getValue()
      {
          return value;
      }
  
      /**
       * Override to make two instances with same value equal.
       *
       * @param o Description of Parameter
       * @return Description of the Returned Value
       * @since 1.1
       */
      public boolean equals( Object o )
      {
          if( o == null || !( o instanceof ZipShort ) )
          {
              return false;
          }
          return value == ( (ZipShort)o ).getValue();
      }
  
      /**
       * Override to make two instances with same value equal.
       *
       * @return Description of the Returned Value
       * @since 1.1
       */
      public int hashCode()
      {
          return value;
      }
  
  }// ZipShort
  
  
  

--
To unsubscribe, e-mail:   <mailto:ant-dev-unsubscribe@jakarta.apache.org>
For additional commands, e-mail: <mailto:ant-dev-help@jakarta.apache.org>


Mime
View raw message