Source for java.util.zip.ZipFile

   1: /* ZipFile.java --
   2:    Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006
   3:    Free Software Foundation, Inc.
   4: 
   5: This file is part of GNU Classpath.
   6: 
   7: GNU Classpath is free software; you can redistribute it and/or modify
   8: it under the terms of the GNU General Public License as published by
   9: the Free Software Foundation; either version 2, or (at your option)
  10: any later version.
  11: 
  12: GNU Classpath is distributed in the hope that it will be useful, but
  13: WITHOUT ANY WARRANTY; without even the implied warranty of
  14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15: General Public License for more details.
  16: 
  17: You should have received a copy of the GNU General Public License
  18: along with GNU Classpath; see the file COPYING.  If not, write to the
  19: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  20: 02110-1301 USA.
  21: 
  22: Linking this library statically or dynamically with other modules is
  23: making a combined work based on this library.  Thus, the terms and
  24: conditions of the GNU General Public License cover the whole
  25: combination.
  26: 
  27: As a special exception, the copyright holders of this library give you
  28: permission to link this library with independent modules to produce an
  29: executable, regardless of the license terms of these independent
  30: modules, and to copy and distribute the resulting executable under
  31: terms of your choice, provided that you also meet, for each linked
  32: independent module, the terms and conditions of the license of that
  33: module.  An independent module is a module which is not derived from
  34: or based on this library.  If you modify this library, you may extend
  35: this exception to your version of the library, but you are not
  36: obligated to do so.  If you do not wish to do so, delete this
  37: exception statement from your version. */
  38: 
  39: 
  40: package java.util.zip;
  41: 
  42: import gnu.java.util.EmptyEnumeration;
  43: 
  44: import java.io.EOFException;
  45: import java.io.File;
  46: import java.io.FileNotFoundException;
  47: import java.io.IOException;
  48: import java.io.InputStream;
  49: import java.io.RandomAccessFile;
  50: import java.io.UnsupportedEncodingException;
  51: import java.nio.ByteBuffer;
  52: import java.nio.charset.Charset;
  53: import java.nio.charset.CharsetDecoder;
  54: import java.util.Enumeration;
  55: import java.util.Iterator;
  56: import java.util.LinkedHashMap;
  57: 
  58: /**
  59:  * This class represents a Zip archive.  You can ask for the contained
  60:  * entries, or get an input stream for a file entry.  The entry is
  61:  * automatically decompressed.
  62:  *
  63:  * This class is thread safe:  You can open input streams for arbitrary
  64:  * entries in different threads.
  65:  *
  66:  * @author Jochen Hoenicke
  67:  * @author Artur Biesiadowski
  68:  */
  69: public class ZipFile implements ZipConstants
  70: {
  71: 
  72:   /**
  73:    * Mode flag to open a zip file for reading.
  74:    */
  75:   public static final int OPEN_READ = 0x1;
  76: 
  77:   /**
  78:    * Mode flag to delete a zip file after reading.
  79:    */
  80:   public static final int OPEN_DELETE = 0x4;
  81: 
  82:   /**
  83:    * This field isn't defined in the JDK's ZipConstants, but should be.
  84:    */
  85:   static final int ENDNRD =  4;
  86: 
  87:   // Name of this zip file.
  88:   private final String name;
  89: 
  90:   // File from which zip entries are read.
  91:   private final RandomAccessFile raf;
  92: 
  93:   // The entries of this zip file when initialized and not yet closed.
  94:   private LinkedHashMap<String, ZipEntry> entries;
  95: 
  96:   private boolean closed = false;
  97: 
  98: 
  99:   /**
 100:    * Helper function to open RandomAccessFile and throw the proper
 101:    * ZipException in case opening the file fails.
 102:    *
 103:    * @param name the file name, or null if file is provided
 104:    *
 105:    * @param file the file, or null if name is provided
 106:    *
 107:    * @return the newly open RandomAccessFile, never null
 108:    */
 109:   private RandomAccessFile openFile(String name, 
 110:                                     File file) 
 111:     throws ZipException, IOException
 112:   {                                       
 113:     try 
 114:       {
 115:         return 
 116:           (name != null)
 117:           ? new RandomAccessFile(name, "r")
 118:           : new RandomAccessFile(file, "r");
 119:       }
 120:     catch (FileNotFoundException f)
 121:       { 
 122:         ZipException ze = new ZipException(f.getMessage());
 123:         ze.initCause(f);
 124:         throw ze;
 125:       }
 126:   }
 127: 
 128: 
 129:   /**
 130:    * Opens a Zip file with the given name for reading.
 131:    * @exception IOException if a i/o error occured.
 132:    * @exception ZipException if the file doesn't contain a valid zip
 133:    * archive.  
 134:    */
 135:   public ZipFile(String name) throws ZipException, IOException
 136:   {
 137:     this.raf = openFile(name,null);
 138:     this.name = name;
 139:     checkZipFile();
 140:   }
 141: 
 142:   /**
 143:    * Opens a Zip file reading the given File.
 144:    * @exception IOException if a i/o error occured.
 145:    * @exception ZipException if the file doesn't contain a valid zip
 146:    * archive.  
 147:    */
 148:   public ZipFile(File file) throws ZipException, IOException
 149:   {
 150:     this.raf = openFile(null,file);
 151:     this.name = file.getPath();
 152:     checkZipFile();
 153:   }
 154: 
 155:   /**
 156:    * Opens a Zip file reading the given File in the given mode.
 157:    *
 158:    * If the OPEN_DELETE mode is specified, the zip file will be deleted at
 159:    * some time moment after it is opened. It will be deleted before the zip
 160:    * file is closed or the Virtual Machine exits.
 161:    * 
 162:    * The contents of the zip file will be accessible until it is closed.
 163:    *
 164:    * @since JDK1.3
 165:    * @param mode Must be one of OPEN_READ or OPEN_READ | OPEN_DELETE
 166:    *
 167:    * @exception IOException if a i/o error occured.
 168:    * @exception ZipException if the file doesn't contain a valid zip
 169:    * archive.  
 170:    */
 171:   public ZipFile(File file, int mode) throws ZipException, IOException
 172:   {
 173:     if (mode != OPEN_READ && mode != (OPEN_READ | OPEN_DELETE))
 174:       throw new IllegalArgumentException("invalid mode");
 175:     if ((mode & OPEN_DELETE) != 0)
 176:       file.deleteOnExit();
 177:     this.raf = openFile(null,file);
 178:     this.name = file.getPath();
 179:     checkZipFile();
 180:   }
 181: 
 182:   private void checkZipFile() throws ZipException
 183:   {
 184:     boolean valid = false;
 185: 
 186:     try 
 187:       {
 188:         byte[] buf = new byte[4];
 189:         raf.readFully(buf);
 190:         int sig = buf[0] & 0xFF
 191:                 | ((buf[1] & 0xFF) << 8)
 192:                 | ((buf[2] & 0xFF) << 16)
 193:                 | ((buf[3] & 0xFF) << 24);
 194:         valid = sig == LOCSIG;
 195:       }
 196:     catch (IOException _)
 197:       {
 198:       } 
 199: 
 200:     if (!valid)
 201:       {
 202:         try
 203:           {
 204:         raf.close();
 205:           }
 206:         catch (IOException _)
 207:           {
 208:           }
 209:     throw new ZipException("Not a valid zip file");
 210:       }
 211:   }
 212: 
 213:   /**
 214:    * Checks if file is closed and throws an exception.
 215:    */
 216:   private void checkClosed()
 217:   {
 218:     if (closed)
 219:       throw new IllegalStateException("ZipFile has closed: " + name);
 220:   }
 221: 
 222:   /**
 223:    * Read the central directory of a zip file and fill the entries
 224:    * array.  This is called exactly once when first needed. It is called
 225:    * while holding the lock on <code>raf</code>.
 226:    *
 227:    * @exception IOException if a i/o error occured.
 228:    * @exception ZipException if the central directory is malformed 
 229:    */
 230:   private void readEntries() throws ZipException, IOException
 231:   {
 232:     /* Search for the End Of Central Directory.  When a zip comment is 
 233:      * present the directory may start earlier.
 234:      * Note that a comment has a maximum length of 64K, so that is the
 235:      * maximum we search backwards.
 236:      */
 237:     PartialInputStream inp = new PartialInputStream(raf, 4096);
 238:     long pos = raf.length() - ENDHDR;
 239:     long top = Math.max(0, pos - 65536);
 240:     do
 241:       {
 242:     if (pos < top)
 243:       throw new ZipException
 244:         ("central directory not found, probably not a zip file: " + name);
 245:     inp.seek(pos--);
 246:       }
 247:     while (inp.readLeInt() != ENDSIG);
 248:     
 249:     if (inp.skip(ENDTOT - ENDNRD) != ENDTOT - ENDNRD)
 250:       throw new EOFException(name);
 251:     int count = inp.readLeShort();
 252:     if (inp.skip(ENDOFF - ENDSIZ) != ENDOFF - ENDSIZ)
 253:       throw new EOFException(name);
 254:     int centralOffset = inp.readLeInt();
 255: 
 256:     entries = new LinkedHashMap<String, ZipEntry> (count+count/2);
 257:     inp.seek(centralOffset);
 258:     
 259:     for (int i = 0; i < count; i++)
 260:       {
 261:     if (inp.readLeInt() != CENSIG)
 262:       throw new ZipException("Wrong Central Directory signature: " + name);
 263: 
 264:         inp.skip(6);
 265:     int method = inp.readLeShort();
 266:     int dostime = inp.readLeInt();
 267:     int crc = inp.readLeInt();
 268:     int csize = inp.readLeInt();
 269:     int size = inp.readLeInt();
 270:     int nameLen = inp.readLeShort();
 271:     int extraLen = inp.readLeShort();
 272:     int commentLen = inp.readLeShort();
 273:         inp.skip(8);
 274:     int offset = inp.readLeInt();
 275:     String name = inp.readString(nameLen);
 276: 
 277:     ZipEntry entry = new ZipEntry(name);
 278:     entry.setMethod(method);
 279:     entry.setCrc(crc & 0xffffffffL);
 280:     entry.setSize(size & 0xffffffffL);
 281:     entry.setCompressedSize(csize & 0xffffffffL);
 282:     entry.setDOSTime(dostime);
 283:     if (extraLen > 0)
 284:       {
 285:         byte[] extra = new byte[extraLen];
 286:         inp.readFully(extra);
 287:         entry.setExtra(extra);
 288:       }
 289:     if (commentLen > 0)
 290:       {
 291:             entry.setComment(inp.readString(commentLen));
 292:       }
 293:     entry.offset = offset;
 294:     entries.put(name, entry);
 295:       }
 296:   }
 297: 
 298:   /**
 299:    * Closes the ZipFile.  This also closes all input streams given by
 300:    * this class.  After this is called, no further method should be
 301:    * called.
 302:    * 
 303:    * @exception IOException if a i/o error occured.
 304:    */
 305:   public void close() throws IOException
 306:   {
 307:     RandomAccessFile raf = this.raf;
 308:     if (raf == null)
 309:       return;
 310: 
 311:     synchronized (raf)
 312:       {
 313:     closed = true;
 314:     entries = null;
 315:     raf.close();
 316:       }
 317:   }
 318: 
 319:   /**
 320:    * Calls the <code>close()</code> method when this ZipFile has not yet
 321:    * been explicitly closed.
 322:    */
 323:   protected void finalize() throws IOException
 324:   {
 325:     if (!closed && raf != null) close();
 326:   }
 327: 
 328:   /**
 329:    * Returns an enumeration of all Zip entries in this Zip file.
 330:    *
 331:    * @exception IllegalStateException when the ZipFile has already been closed
 332:    */
 333:   public Enumeration<? extends ZipEntry> entries()
 334:   {
 335:     checkClosed();
 336:     
 337:     try
 338:       {
 339:     return new ZipEntryEnumeration(getEntries().values().iterator());
 340:       }
 341:     catch (IOException ioe)
 342:       {
 343:     return EmptyEnumeration.getInstance();
 344:       }
 345:   }
 346: 
 347:   /**
 348:    * Checks that the ZipFile is still open and reads entries when necessary.
 349:    *
 350:    * @exception IllegalStateException when the ZipFile has already been closed.
 351:    * @exception IOException when the entries could not be read.
 352:    */
 353:   private LinkedHashMap<String, ZipEntry> getEntries() throws IOException
 354:   {
 355:     synchronized(raf)
 356:       {
 357:     checkClosed();
 358: 
 359:     if (entries == null)
 360:       readEntries();
 361: 
 362:     return entries;
 363:       }
 364:   }
 365: 
 366:   /**
 367:    * Searches for a zip entry in this archive with the given name.
 368:    *
 369:    * @param name the name. May contain directory components separated by
 370:    * slashes ('/').
 371:    * @return the zip entry, or null if no entry with that name exists.
 372:    *
 373:    * @exception IllegalStateException when the ZipFile has already been closed
 374:    */
 375:   public ZipEntry getEntry(String name)
 376:   {
 377:     checkClosed();
 378: 
 379:     try
 380:       {
 381:     LinkedHashMap<String, ZipEntry> entries = getEntries();
 382:     ZipEntry entry = entries.get(name);
 383:         // If we didn't find it, maybe it's a directory.
 384:         if (entry == null && !name.endsWith("/"))
 385:       entry = entries.get(name + '/');
 386:     return entry != null ? new ZipEntry(entry, name) : null;
 387:       }
 388:     catch (IOException ioe)
 389:       {
 390:     return null;
 391:       }
 392:   }
 393: 
 394:   /**
 395:    * Creates an input stream reading the given zip entry as
 396:    * uncompressed data.  Normally zip entry should be an entry
 397:    * returned by getEntry() or entries().
 398:    *
 399:    * This implementation returns null if the requested entry does not
 400:    * exist.  This decision is not obviously correct, however, it does
 401:    * appear to mirror Sun's implementation, and it is consistant with
 402:    * their javadoc.  On the other hand, the old JCL book, 2nd Edition,
 403:    * claims that this should return a "non-null ZIP entry".  We have
 404:    * chosen for now ignore the old book, as modern versions of Ant (an
 405:    * important application) depend on this behaviour.  See discussion
 406:    * in this thread:
 407:    * http://gcc.gnu.org/ml/java-patches/2004-q2/msg00602.html
 408:    *
 409:    * @param entry the entry to create an InputStream for.
 410:    * @return the input stream, or null if the requested entry does not exist.
 411:    *
 412:    * @exception IllegalStateException when the ZipFile has already been closed
 413:    * @exception IOException if a i/o error occured.
 414:    * @exception ZipException if the Zip archive is malformed.  
 415:    */
 416:   public InputStream getInputStream(ZipEntry entry) throws IOException
 417:   {
 418:     checkClosed();
 419: 
 420:     LinkedHashMap<String, ZipEntry> entries = getEntries();
 421:     String name = entry.getName();
 422:     ZipEntry zipEntry = entries.get(name);
 423:     if (zipEntry == null)
 424:       return null;
 425: 
 426:     PartialInputStream inp = new PartialInputStream(raf, 1024);
 427:     inp.seek(zipEntry.offset);
 428: 
 429:     if (inp.readLeInt() != LOCSIG)
 430:       throw new ZipException("Wrong Local header signature: " + name);
 431: 
 432:     inp.skip(4);
 433: 
 434:     if (zipEntry.getMethod() != inp.readLeShort())
 435:       throw new ZipException("Compression method mismatch: " + name);
 436: 
 437:     inp.skip(16);
 438: 
 439:     int nameLen = inp.readLeShort();
 440:     int extraLen = inp.readLeShort();
 441:     inp.skip(nameLen + extraLen);
 442: 
 443:     inp.setLength(zipEntry.getCompressedSize());
 444: 
 445:     int method = zipEntry.getMethod();
 446:     switch (method)
 447:       {
 448:       case ZipOutputStream.STORED:
 449:     return inp;
 450:       case ZipOutputStream.DEFLATED:
 451:         inp.addDummyByte();
 452:         final Inflater inf = new Inflater(true);
 453:         final int sz = (int) entry.getSize();
 454:         return new InflaterInputStream(inp, inf)
 455:         {
 456:           public int available() throws IOException
 457:           {
 458:             if (sz == -1)
 459:               return super.available();
 460:             if (super.available() != 0)
 461:               return sz - inf.getTotalOut();
 462:             return 0;
 463:           }
 464:         };
 465:       default:
 466:     throw new ZipException("Unknown compression method " + method);
 467:       }
 468:   }
 469:   
 470:   /**
 471:    * Returns the (path) name of this zip file.
 472:    */
 473:   public String getName()
 474:   {
 475:     return name;
 476:   }
 477: 
 478:   /**
 479:    * Returns the number of entries in this zip file.
 480:    *
 481:    * @exception IllegalStateException when the ZipFile has already been closed
 482:    */
 483:   public int size()
 484:   {
 485:     checkClosed();
 486:     
 487:     try
 488:       {
 489:     return getEntries().size();
 490:       }
 491:     catch (IOException ioe)
 492:       {
 493:     return 0;
 494:       }
 495:   }
 496:   
 497:   private static class ZipEntryEnumeration implements Enumeration<ZipEntry>
 498:   {
 499:     private final Iterator<ZipEntry> elements;
 500: 
 501:     public ZipEntryEnumeration(Iterator<ZipEntry> elements)
 502:     {
 503:       this.elements = elements;
 504:     }
 505: 
 506:     public boolean hasMoreElements()
 507:     {
 508:       return elements.hasNext();
 509:     }
 510: 
 511:     public ZipEntry nextElement()
 512:     {
 513:       /* We return a clone, just to be safe that the user doesn't
 514:        * change the entry.  
 515:        */
 516:       return (ZipEntry) (elements.next().clone());
 517:     }
 518:   }
 519: 
 520:   private static final class PartialInputStream extends InputStream
 521:   {
 522:     /**
 523:      * The UTF-8 charset use for decoding the filenames.
 524:      */
 525:     private static final Charset UTF8CHARSET = Charset.forName("UTF-8");
 526: 
 527:     /**
 528:      * The actual UTF-8 decoder. Created on demand. 
 529:      */
 530:     private CharsetDecoder utf8Decoder;
 531: 
 532:     private final RandomAccessFile raf;
 533:     private final byte[] buffer;
 534:     private long bufferOffset;
 535:     private int pos;
 536:     private long end;
 537:     // We may need to supply an extra dummy byte to our reader.
 538:     // See Inflater.  We use a count here to simplify the logic
 539:     // elsewhere in this class.  Note that we ignore the dummy
 540:     // byte in methods where we know it is not needed.
 541:     private int dummyByteCount;
 542: 
 543:     public PartialInputStream(RandomAccessFile raf, int bufferSize)
 544:       throws IOException
 545:     {
 546:       this.raf = raf;
 547:       buffer = new byte[bufferSize];
 548:       bufferOffset = -buffer.length;
 549:       pos = buffer.length;
 550:       end = raf.length();
 551:     }
 552: 
 553:     void setLength(long length)
 554:     {
 555:       end = bufferOffset + pos + length;
 556:     }
 557: 
 558:     private void fillBuffer() throws IOException
 559:     {
 560:       synchronized (raf)
 561:         {
 562:           long len = end - bufferOffset;
 563:           if (len == 0 && dummyByteCount > 0)
 564:             {
 565:               buffer[0] = 0;
 566:               dummyByteCount = 0;
 567:             }
 568:           else
 569:             {
 570:               raf.seek(bufferOffset);
 571:               raf.readFully(buffer, 0, (int) Math.min(buffer.length, len));
 572:             }
 573:         }
 574:     }
 575:     
 576:     public int available()
 577:     {
 578:       long amount = end - (bufferOffset + pos);
 579:       if (amount > Integer.MAX_VALUE)
 580:     return Integer.MAX_VALUE;
 581:       return (int) amount;
 582:     }
 583:     
 584:     public int read() throws IOException
 585:     {
 586:       if (bufferOffset + pos >= end + dummyByteCount)
 587:     return -1;
 588:       if (pos == buffer.length)
 589:         {
 590:           bufferOffset += buffer.length;
 591:           pos = 0;
 592:           fillBuffer();
 593:         }
 594:       
 595:       return buffer[pos++] & 0xFF;
 596:     }
 597: 
 598:     public int read(byte[] b, int off, int len) throws IOException
 599:     {
 600:       if (len > end + dummyByteCount - (bufferOffset + pos))
 601:     {
 602:       len = (int) (end + dummyByteCount - (bufferOffset + pos));
 603:       if (len == 0)
 604:         return -1;
 605:     }
 606: 
 607:       int totalBytesRead = Math.min(buffer.length - pos, len);
 608:       System.arraycopy(buffer, pos, b, off, totalBytesRead);
 609:       pos += totalBytesRead;
 610:       off += totalBytesRead;
 611:       len -= totalBytesRead;
 612: 
 613:       while (len > 0)
 614:         {
 615:           bufferOffset += buffer.length;
 616:           pos = 0;
 617:           fillBuffer();
 618:           int remain = Math.min(buffer.length, len);
 619:           System.arraycopy(buffer, pos, b, off, remain);
 620:           pos += remain;
 621:           off += remain;
 622:           len -= remain;
 623:           totalBytesRead += remain;
 624:         }
 625:       
 626:       return totalBytesRead;
 627:     }
 628: 
 629:     public long skip(long amount) throws IOException
 630:     {
 631:       if (amount < 0)
 632:     return 0;
 633:       if (amount > end - (bufferOffset + pos))
 634:     amount = end - (bufferOffset + pos);
 635:       seek(bufferOffset + pos + amount);
 636:       return amount;
 637:     }
 638: 
 639:     void seek(long newpos) throws IOException
 640:     {
 641:       long offset = newpos - bufferOffset;
 642:       if (offset >= 0 && offset <= buffer.length)
 643:         {
 644:           pos = (int) offset;
 645:         }
 646:       else
 647:         {
 648:           bufferOffset = newpos;
 649:           pos = 0;
 650:           fillBuffer();
 651:         }
 652:     }
 653: 
 654:     void readFully(byte[] buf) throws IOException
 655:     {
 656:       if (read(buf, 0, buf.length) != buf.length)
 657:         throw new EOFException();
 658:     }
 659: 
 660:     void readFully(byte[] buf, int off, int len) throws IOException
 661:     {
 662:       if (read(buf, off, len) != len)
 663:         throw new EOFException();
 664:     }
 665: 
 666:     int readLeShort() throws IOException
 667:     {
 668:       int result;
 669:       if(pos + 1 < buffer.length)
 670:         {
 671:           result = ((buffer[pos + 0] & 0xff) | (buffer[pos + 1] & 0xff) << 8);
 672:           pos += 2;
 673:         }
 674:       else
 675:         {
 676:           int b0 = read();
 677:           int b1 = read();
 678:           if (b1 == -1)
 679:             throw new EOFException();
 680:           result = (b0 & 0xff) | (b1 & 0xff) << 8;
 681:         }
 682:       return result;
 683:     }
 684: 
 685:     int readLeInt() throws IOException
 686:     {
 687:       int result;
 688:       if(pos + 3 < buffer.length)
 689:         {
 690:           result = (((buffer[pos + 0] & 0xff) | (buffer[pos + 1] & 0xff) << 8)
 691:                    | ((buffer[pos + 2] & 0xff)
 692:                        | (buffer[pos + 3] & 0xff) << 8) << 16);
 693:           pos += 4;
 694:         }
 695:       else
 696:         {
 697:           int b0 = read();
 698:           int b1 = read();
 699:           int b2 = read();
 700:           int b3 = read();
 701:           if (b3 == -1)
 702:             throw new EOFException();
 703:           result =  (((b0 & 0xff) | (b1 & 0xff) << 8) | ((b2 & 0xff)
 704:                     | (b3 & 0xff) << 8) << 16);
 705:         }
 706:       return result;
 707:     }
 708: 
 709:     /**
 710:      * Decode chars from byte buffer using UTF8 encoding.  This
 711:      * operation is performance-critical since a jar file contains a
 712:      * large number of strings for the name of each file in the
 713:      * archive.  This routine therefore avoids using the expensive
 714:      * utf8Decoder when decoding is straightforward.
 715:      *
 716:      * @param buffer the buffer that contains the encoded character
 717:      *        data
 718:      * @param pos the index in buffer of the first byte of the encoded
 719:      *        data
 720:      * @param length the length of the encoded data in number of
 721:      *        bytes.
 722:      *
 723:      * @return a String that contains the decoded characters.
 724:      */
 725:     private String decodeChars(byte[] buffer, int pos, int length)
 726:       throws IOException
 727:     {
 728:       String result;
 729:       int i=length - 1;
 730:       while ((i >= 0) && (buffer[i] <= 0x7f))
 731:         {
 732:           i--;
 733:         }
 734:       if (i < 0)
 735:         {
 736:           result = new String(buffer, 0, pos, length);
 737:         }
 738:       else
 739:         {
 740:           ByteBuffer bufferBuffer = ByteBuffer.wrap(buffer, pos, length);
 741:           if (utf8Decoder == null)
 742:             utf8Decoder = UTF8CHARSET.newDecoder();
 743:           utf8Decoder.reset();
 744:           char [] characters = utf8Decoder.decode(bufferBuffer).array();
 745:           result = String.valueOf(characters);
 746:         }
 747:       return result;
 748:     }
 749: 
 750:     String readString(int length) throws IOException
 751:     {
 752:       if (length > end - (bufferOffset + pos))
 753:         throw new EOFException();
 754: 
 755:       String result = null;
 756:       try
 757:         {
 758:           if (buffer.length - pos >= length)
 759:             {
 760:               result = decodeChars(buffer, pos, length);
 761:               pos += length;
 762:             }
 763:           else
 764:             {
 765:               byte[] b = new byte[length];
 766:               readFully(b);
 767:               result = decodeChars(b, 0, length);
 768:             }
 769:         }
 770:       catch (UnsupportedEncodingException uee)
 771:         {
 772:           throw new AssertionError(uee);
 773:         }
 774:       return result;
 775:     }
 776: 
 777:     public void addDummyByte()
 778:     {
 779:       dummyByteCount = 1;
 780:     }
 781:   }
 782: }