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(4);
 265:         int flags = inp.readLeShort();
 266:         if ((flags & 1) != 0)
 267:           throw new ZipException("invalid CEN header (encrypted entry)");
 268:         int method = inp.readLeShort();
 269:         int dostime = inp.readLeInt();
 270:         int crc = inp.readLeInt();
 271:         int csize = inp.readLeInt();
 272:         int size = inp.readLeInt();
 273:         int nameLen = inp.readLeShort();
 274:         int extraLen = inp.readLeShort();
 275:         int commentLen = inp.readLeShort();
 276:         inp.skip(8);
 277:         int offset = inp.readLeInt();
 278:         String name = inp.readString(nameLen);
 279: 
 280:         ZipEntry entry = new ZipEntry(name);
 281:         entry.setMethod(method);
 282:         entry.setCrc(crc & 0xffffffffL);
 283:         entry.setSize(size & 0xffffffffL);
 284:         entry.setCompressedSize(csize & 0xffffffffL);
 285:         entry.setDOSTime(dostime);
 286:         if (extraLen > 0)
 287:           {
 288:             byte[] extra = new byte[extraLen];
 289:             inp.readFully(extra);
 290:             entry.setExtra(extra);
 291:           }
 292:         if (commentLen > 0)
 293:           {
 294:             entry.setComment(inp.readString(commentLen));
 295:           }
 296:         entry.offset = offset;
 297:         entries.put(name, entry);
 298:       }
 299:   }
 300: 
 301:   /**
 302:    * Closes the ZipFile.  This also closes all input streams given by
 303:    * this class.  After this is called, no further method should be
 304:    * called.
 305:    *
 306:    * @exception IOException if a i/o error occured.
 307:    */
 308:   public void close() throws IOException
 309:   {
 310:     RandomAccessFile raf = this.raf;
 311:     if (raf == null)
 312:       return;
 313: 
 314:     synchronized (raf)
 315:       {
 316:         closed = true;
 317:         entries = null;
 318:         raf.close();
 319:       }
 320:   }
 321: 
 322:   /**
 323:    * Calls the <code>close()</code> method when this ZipFile has not yet
 324:    * been explicitly closed.
 325:    */
 326:   protected void finalize() throws IOException
 327:   {
 328:     if (!closed && raf != null) close();
 329:   }
 330: 
 331:   /**
 332:    * Returns an enumeration of all Zip entries in this Zip file.
 333:    *
 334:    * @exception IllegalStateException when the ZipFile has already been closed
 335:    */
 336:   public Enumeration<? extends ZipEntry> entries()
 337:   {
 338:     checkClosed();
 339: 
 340:     try
 341:       {
 342:         return new ZipEntryEnumeration(getEntries().values().iterator());
 343:       }
 344:     catch (IOException ioe)
 345:       {
 346:         return new EmptyEnumeration<ZipEntry>();
 347:       }
 348:   }
 349: 
 350:   /**
 351:    * Checks that the ZipFile is still open and reads entries when necessary.
 352:    *
 353:    * @exception IllegalStateException when the ZipFile has already been closed.
 354:    * @exception IOException when the entries could not be read.
 355:    */
 356:   private LinkedHashMap<String, ZipEntry> getEntries() throws IOException
 357:   {
 358:     synchronized(raf)
 359:       {
 360:         checkClosed();
 361: 
 362:         if (entries == null)
 363:           readEntries();
 364: 
 365:         return entries;
 366:       }
 367:   }
 368: 
 369:   /**
 370:    * Searches for a zip entry in this archive with the given name.
 371:    *
 372:    * @param name the name. May contain directory components separated by
 373:    * slashes ('/').
 374:    * @return the zip entry, or null if no entry with that name exists.
 375:    *
 376:    * @exception IllegalStateException when the ZipFile has already been closed
 377:    */
 378:   public ZipEntry getEntry(String name)
 379:   {
 380:     checkClosed();
 381: 
 382:     try
 383:       {
 384:         LinkedHashMap<String, ZipEntry> entries = getEntries();
 385:         ZipEntry entry = entries.get(name);
 386:         // If we didn't find it, maybe it's a directory.
 387:         if (entry == null && !name.endsWith("/"))
 388:           entry = entries.get(name + '/');
 389:         return entry != null ? new ZipEntry(entry, name) : null;
 390:       }
 391:     catch (IOException ioe)
 392:       {
 393:         return null;
 394:       }
 395:   }
 396: 
 397:   /**
 398:    * Creates an input stream reading the given zip entry as
 399:    * uncompressed data.  Normally zip entry should be an entry
 400:    * returned by getEntry() or entries().
 401:    *
 402:    * This implementation returns null if the requested entry does not
 403:    * exist.  This decision is not obviously correct, however, it does
 404:    * appear to mirror Sun's implementation, and it is consistant with
 405:    * their javadoc.  On the other hand, the old JCL book, 2nd Edition,
 406:    * claims that this should return a "non-null ZIP entry".  We have
 407:    * chosen for now ignore the old book, as modern versions of Ant (an
 408:    * important application) depend on this behaviour.  See discussion
 409:    * in this thread:
 410:    * http://gcc.gnu.org/ml/java-patches/2004-q2/msg00602.html
 411:    *
 412:    * @param entry the entry to create an InputStream for.
 413:    * @return the input stream, or null if the requested entry does not exist.
 414:    *
 415:    * @exception IllegalStateException when the ZipFile has already been closed
 416:    * @exception IOException if a i/o error occured.
 417:    * @exception ZipException if the Zip archive is malformed.
 418:    */
 419:   public InputStream getInputStream(ZipEntry entry) throws IOException
 420:   {
 421:     checkClosed();
 422: 
 423:     LinkedHashMap<String, ZipEntry> entries = getEntries();
 424:     String name = entry.getName();
 425:     ZipEntry zipEntry = entries.get(name);
 426:     if (zipEntry == null)
 427:       return null;
 428: 
 429:     PartialInputStream inp = new PartialInputStream(raf, 1024);
 430:     inp.seek(zipEntry.offset);
 431: 
 432:     if (inp.readLeInt() != LOCSIG)
 433:       throw new ZipException("Wrong Local header signature: " + name);
 434: 
 435:     inp.skip(4);
 436: 
 437:     if (zipEntry.getMethod() != inp.readLeShort())
 438:       throw new ZipException("Compression method mismatch: " + name);
 439: 
 440:     inp.skip(16);
 441: 
 442:     int nameLen = inp.readLeShort();
 443:     int extraLen = inp.readLeShort();
 444:     inp.skip(nameLen + extraLen);
 445: 
 446:     inp.setLength(zipEntry.getCompressedSize());
 447: 
 448:     int method = zipEntry.getMethod();
 449:     switch (method)
 450:       {
 451:       case ZipOutputStream.STORED:
 452:         return inp;
 453:       case ZipOutputStream.DEFLATED:
 454:         inp.addDummyByte();
 455:         final Inflater inf = new Inflater(true);
 456:         final int sz = (int) entry.getSize();
 457:         return new InflaterInputStream(inp, inf)
 458:         {
 459:           public int available() throws IOException
 460:           {
 461:             if (sz == -1)
 462:               return super.available();
 463:             if (super.available() != 0)
 464:               return sz - inf.getTotalOut();
 465:             return 0;
 466:           }
 467:         };
 468:       default:
 469:         throw new ZipException("Unknown compression method " + method);
 470:       }
 471:   }
 472: 
 473:   /**
 474:    * Returns the (path) name of this zip file.
 475:    */
 476:   public String getName()
 477:   {
 478:     return name;
 479:   }
 480: 
 481:   /**
 482:    * Returns the number of entries in this zip file.
 483:    *
 484:    * @exception IllegalStateException when the ZipFile has already been closed
 485:    */
 486:   public int size()
 487:   {
 488:     checkClosed();
 489: 
 490:     try
 491:       {
 492:         return getEntries().size();
 493:       }
 494:     catch (IOException ioe)
 495:       {
 496:         return 0;
 497:       }
 498:   }
 499: 
 500:   private static class ZipEntryEnumeration implements Enumeration<ZipEntry>
 501:   {
 502:     private final Iterator<ZipEntry> elements;
 503: 
 504:     public ZipEntryEnumeration(Iterator<ZipEntry> elements)
 505:     {
 506:       this.elements = elements;
 507:     }
 508: 
 509:     public boolean hasMoreElements()
 510:     {
 511:       return elements.hasNext();
 512:     }
 513: 
 514:     public ZipEntry nextElement()
 515:     {
 516:       /* We return a clone, just to be safe that the user doesn't
 517:        * change the entry.
 518:        */
 519:       return (ZipEntry) (elements.next().clone());
 520:     }
 521:   }
 522: 
 523:   private static final class PartialInputStream extends InputStream
 524:   {
 525:     /**
 526:      * The UTF-8 charset use for decoding the filenames.
 527:      */
 528:     private static final Charset UTF8CHARSET = Charset.forName("UTF-8");
 529: 
 530:     /**
 531:      * The actual UTF-8 decoder. Created on demand.
 532:      */
 533:     private CharsetDecoder utf8Decoder;
 534: 
 535:     private final RandomAccessFile raf;
 536:     private final byte[] buffer;
 537:     private long bufferOffset;
 538:     private int pos;
 539:     private long end;
 540:     // We may need to supply an extra dummy byte to our reader.
 541:     // See Inflater.  We use a count here to simplify the logic
 542:     // elsewhere in this class.  Note that we ignore the dummy
 543:     // byte in methods where we know it is not needed.
 544:     private int dummyByteCount;
 545: 
 546:     public PartialInputStream(RandomAccessFile raf, int bufferSize)
 547:       throws IOException
 548:     {
 549:       this.raf = raf;
 550:       buffer = new byte[bufferSize];
 551:       bufferOffset = -buffer.length;
 552:       pos = buffer.length;
 553:       end = raf.length();
 554:     }
 555: 
 556:     void setLength(long length)
 557:     {
 558:       end = bufferOffset + pos + length;
 559:     }
 560: 
 561:     private void fillBuffer() throws IOException
 562:     {
 563:       synchronized (raf)
 564:         {
 565:           long len = end - bufferOffset;
 566:           if (len == 0 && dummyByteCount > 0)
 567:             {
 568:               buffer[0] = 0;
 569:               dummyByteCount = 0;
 570:             }
 571:           else
 572:             {
 573:               raf.seek(bufferOffset);
 574:               raf.readFully(buffer, 0, (int) Math.min(buffer.length, len));
 575:             }
 576:         }
 577:     }
 578: 
 579:     public int available()
 580:     {
 581:       long amount = end - (bufferOffset + pos);
 582:       if (amount > Integer.MAX_VALUE)
 583:         return Integer.MAX_VALUE;
 584:       return (int) amount;
 585:     }
 586: 
 587:     public int read() throws IOException
 588:     {
 589:       if (bufferOffset + pos >= end + dummyByteCount)
 590:         return -1;
 591:       if (pos == buffer.length)
 592:         {
 593:           bufferOffset += buffer.length;
 594:           pos = 0;
 595:           fillBuffer();
 596:         }
 597: 
 598:       return buffer[pos++] & 0xFF;
 599:     }
 600: 
 601:     public int read(byte[] b, int off, int len) throws IOException
 602:     {
 603:       if (len > end + dummyByteCount - (bufferOffset + pos))
 604:         {
 605:           len = (int) (end + dummyByteCount - (bufferOffset + pos));
 606:           if (len == 0)
 607:             return -1;
 608:         }
 609: 
 610:       int totalBytesRead = Math.min(buffer.length - pos, len);
 611:       System.arraycopy(buffer, pos, b, off, totalBytesRead);
 612:       pos += totalBytesRead;
 613:       off += totalBytesRead;
 614:       len -= totalBytesRead;
 615: 
 616:       while (len > 0)
 617:         {
 618:           bufferOffset += buffer.length;
 619:           pos = 0;
 620:           fillBuffer();
 621:           int remain = Math.min(buffer.length, len);
 622:           System.arraycopy(buffer, pos, b, off, remain);
 623:           pos += remain;
 624:           off += remain;
 625:           len -= remain;
 626:           totalBytesRead += remain;
 627:         }
 628: 
 629:       return totalBytesRead;
 630:     }
 631: 
 632:     public long skip(long amount) throws IOException
 633:     {
 634:       if (amount < 0)
 635:         return 0;
 636:       if (amount > end - (bufferOffset + pos))
 637:         amount = end - (bufferOffset + pos);
 638:       seek(bufferOffset + pos + amount);
 639:       return amount;
 640:     }
 641: 
 642:     void seek(long newpos) throws IOException
 643:     {
 644:       long offset = newpos - bufferOffset;
 645:       if (offset >= 0 && offset <= buffer.length)
 646:         {
 647:           pos = (int) offset;
 648:         }
 649:       else
 650:         {
 651:           bufferOffset = newpos;
 652:           pos = 0;
 653:           fillBuffer();
 654:         }
 655:     }
 656: 
 657:     void readFully(byte[] buf) throws IOException
 658:     {
 659:       if (read(buf, 0, buf.length) != buf.length)
 660:         throw new EOFException();
 661:     }
 662: 
 663:     void readFully(byte[] buf, int off, int len) throws IOException
 664:     {
 665:       if (read(buf, off, len) != len)
 666:         throw new EOFException();
 667:     }
 668: 
 669:     int readLeShort() throws IOException
 670:     {
 671:       int result;
 672:       if(pos + 1 < buffer.length)
 673:         {
 674:           result = ((buffer[pos + 0] & 0xff) | (buffer[pos + 1] & 0xff) << 8);
 675:           pos += 2;
 676:         }
 677:       else
 678:         {
 679:           int b0 = read();
 680:           int b1 = read();
 681:           if (b1 == -1)
 682:             throw new EOFException();
 683:           result = (b0 & 0xff) | (b1 & 0xff) << 8;
 684:         }
 685:       return result;
 686:     }
 687: 
 688:     int readLeInt() throws IOException
 689:     {
 690:       int result;
 691:       if(pos + 3 < buffer.length)
 692:         {
 693:           result = (((buffer[pos + 0] & 0xff) | (buffer[pos + 1] & 0xff) << 8)
 694:                    | ((buffer[pos + 2] & 0xff)
 695:                        | (buffer[pos + 3] & 0xff) << 8) << 16);
 696:           pos += 4;
 697:         }
 698:       else
 699:         {
 700:           int b0 = read();
 701:           int b1 = read();
 702:           int b2 = read();
 703:           int b3 = read();
 704:           if (b3 == -1)
 705:             throw new EOFException();
 706:           result =  (((b0 & 0xff) | (b1 & 0xff) << 8) | ((b2 & 0xff)
 707:                     | (b3 & 0xff) << 8) << 16);
 708:         }
 709:       return result;
 710:     }
 711: 
 712:     /**
 713:      * Decode chars from byte buffer using UTF8 encoding.  This
 714:      * operation is performance-critical since a jar file contains a
 715:      * large number of strings for the name of each file in the
 716:      * archive.  This routine therefore avoids using the expensive
 717:      * utf8Decoder when decoding is straightforward.
 718:      *
 719:      * @param buffer the buffer that contains the encoded character
 720:      *        data
 721:      * @param pos the index in buffer of the first byte of the encoded
 722:      *        data
 723:      * @param length the length of the encoded data in number of
 724:      *        bytes.
 725:      *
 726:      * @return a String that contains the decoded characters.
 727:      */
 728:     private String decodeChars(byte[] buffer, int pos, int length)
 729:       throws IOException
 730:     {
 731:       String result;
 732:       int i=length - 1;
 733:       while ((i >= 0) && (buffer[i] <= 0x7f))
 734:         {
 735:           i--;
 736:         }
 737:       if (i < 0)
 738:         {
 739:           result = new String(buffer, 0, pos, length);
 740:         }
 741:       else
 742:         {
 743:           ByteBuffer bufferBuffer = ByteBuffer.wrap(buffer, pos, length);
 744:           if (utf8Decoder == null)
 745:             utf8Decoder = UTF8CHARSET.newDecoder();
 746:           utf8Decoder.reset();
 747:           char [] characters = utf8Decoder.decode(bufferBuffer).array();
 748:           result = String.valueOf(characters);
 749:         }
 750:       return result;
 751:     }
 752: 
 753:     String readString(int length) throws IOException
 754:     {
 755:       if (length > end - (bufferOffset + pos))
 756:         throw new EOFException();
 757: 
 758:       String result = null;
 759:       try
 760:         {
 761:           if (buffer.length - pos >= length)
 762:             {
 763:               result = decodeChars(buffer, pos, length);
 764:               pos += length;
 765:             }
 766:           else
 767:             {
 768:               byte[] b = new byte[length];
 769:               readFully(b);
 770:               result = decodeChars(b, 0, length);
 771:             }
 772:         }
 773:       catch (UnsupportedEncodingException uee)
 774:         {
 775:           throw new AssertionError(uee);
 776:         }
 777:       return result;
 778:     }
 779: 
 780:     public void addDummyByte()
 781:     {
 782:       dummyByteCount = 1;
 783:     }
 784:   }
 785: }