ZipFile.java 21.5 KB
Newer Older
Tom Tromey committed
1
/* ZipFile.java --
2
   Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006
Tom Tromey committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
   Free Software Foundation, Inc.

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */


package java.util.zip;

import gnu.java.util.EmptyEnumeration;

import java.io.EOFException;
import java.io.File;
46
import java.io.FileNotFoundException;
Tom Tromey committed
47 48 49
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
Tom Tromey committed
50
import java.io.UnsupportedEncodingException;
51 52 53
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
Tom Tromey committed
54 55
import java.util.Enumeration;
import java.util.Iterator;
56
import java.util.LinkedHashMap;
Tom Tromey committed
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81

/**
 * This class represents a Zip archive.  You can ask for the contained
 * entries, or get an input stream for a file entry.  The entry is
 * automatically decompressed.
 *
 * This class is thread safe:  You can open input streams for arbitrary
 * entries in different threads.
 *
 * @author Jochen Hoenicke
 * @author Artur Biesiadowski
 */
public class ZipFile implements ZipConstants
{

  /**
   * Mode flag to open a zip file for reading.
   */
  public static final int OPEN_READ = 0x1;

  /**
   * Mode flag to delete a zip file after reading.
   */
  public static final int OPEN_DELETE = 0x4;

82 83 84 85 86
  /**
   * This field isn't defined in the JDK's ZipConstants, but should be.
   */
  static final int ENDNRD =  4;

Tom Tromey committed
87 88 89 90 91 92 93
  // Name of this zip file.
  private final String name;

  // File from which zip entries are read.
  private final RandomAccessFile raf;

  // The entries of this zip file when initialized and not yet closed.
94
  private LinkedHashMap<String, ZipEntry> entries;
Tom Tromey committed
95 96 97

  private boolean closed = false;

98 99 100 101 102 103 104 105 106 107 108

  /**
   * Helper function to open RandomAccessFile and throw the proper
   * ZipException in case opening the file fails.
   *
   * @param name the file name, or null if file is provided
   *
   * @param file the file, or null if name is provided
   *
   * @return the newly open RandomAccessFile, never null
   */
109 110
  private RandomAccessFile openFile(String name,
                                    File file)
111
    throws ZipException, IOException
112 113
  {
    try
114
      {
115
        return
116 117 118 119 120
          (name != null)
          ? new RandomAccessFile(name, "r")
          : new RandomAccessFile(file, "r");
      }
    catch (FileNotFoundException f)
121
      {
122 123 124 125 126 127 128
        ZipException ze = new ZipException(f.getMessage());
        ze.initCause(f);
        throw ze;
      }
  }


Tom Tromey committed
129 130 131 132
  /**
   * Opens a Zip file with the given name for reading.
   * @exception IOException if a i/o error occured.
   * @exception ZipException if the file doesn't contain a valid zip
133
   * archive.
Tom Tromey committed
134 135 136
   */
  public ZipFile(String name) throws ZipException, IOException
  {
137
    this.raf = openFile(name,null);
Tom Tromey committed
138 139 140 141 142 143 144 145
    this.name = name;
    checkZipFile();
  }

  /**
   * Opens a Zip file reading the given File.
   * @exception IOException if a i/o error occured.
   * @exception ZipException if the file doesn't contain a valid zip
146
   * archive.
Tom Tromey committed
147 148 149
   */
  public ZipFile(File file) throws ZipException, IOException
  {
150
    this.raf = openFile(null,file);
Tom Tromey committed
151 152 153 154 155 156 157 158 159 160
    this.name = file.getPath();
    checkZipFile();
  }

  /**
   * Opens a Zip file reading the given File in the given mode.
   *
   * If the OPEN_DELETE mode is specified, the zip file will be deleted at
   * some time moment after it is opened. It will be deleted before the zip
   * file is closed or the Virtual Machine exits.
161
   *
Tom Tromey committed
162 163 164 165 166 167 168
   * The contents of the zip file will be accessible until it is closed.
   *
   * @since JDK1.3
   * @param mode Must be one of OPEN_READ or OPEN_READ | OPEN_DELETE
   *
   * @exception IOException if a i/o error occured.
   * @exception ZipException if the file doesn't contain a valid zip
169
   * archive.
Tom Tromey committed
170 171 172 173 174 175 176
   */
  public ZipFile(File file, int mode) throws ZipException, IOException
  {
    if (mode != OPEN_READ && mode != (OPEN_READ | OPEN_DELETE))
      throw new IllegalArgumentException("invalid mode");
    if ((mode & OPEN_DELETE) != 0)
      file.deleteOnExit();
177
    this.raf = openFile(null,file);
Tom Tromey committed
178 179 180 181
    this.name = file.getPath();
    checkZipFile();
  }

182
  private void checkZipFile() throws ZipException
Tom Tromey committed
183
  {
184
    boolean valid = false;
Tom Tromey committed
185

186
    try
187
      {
188 189 190 191 192 193 194 195 196
        byte[] buf = new byte[4];
        raf.readFully(buf);
        int sig = buf[0] & 0xFF
                | ((buf[1] & 0xFF) << 8)
                | ((buf[2] & 0xFF) << 16)
                | ((buf[3] & 0xFF) << 24);
        valid = sig == LOCSIG;
      }
    catch (IOException _)
197
      {
198
      }
199

200
    if (!valid)
Tom Tromey committed
201
      {
202 203
        try
          {
204
            raf.close();
205 206 207 208
          }
        catch (IOException _)
          {
          }
209
        throw new ZipException("Not a valid zip file");
Tom Tromey committed
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
      }
  }

  /**
   * Checks if file is closed and throws an exception.
   */
  private void checkClosed()
  {
    if (closed)
      throw new IllegalStateException("ZipFile has closed: " + name);
  }

  /**
   * Read the central directory of a zip file and fill the entries
   * array.  This is called exactly once when first needed. It is called
   * while holding the lock on <code>raf</code>.
   *
   * @exception IOException if a i/o error occured.
228
   * @exception ZipException if the central directory is malformed
Tom Tromey committed
229 230 231
   */
  private void readEntries() throws ZipException, IOException
  {
232
    /* Search for the End Of Central Directory.  When a zip comment is
Tom Tromey committed
233
     * present the directory may start earlier.
234 235
     * Note that a comment has a maximum length of 64K, so that is the
     * maximum we search backwards.
Tom Tromey committed
236
     */
237
    PartialInputStream inp = new PartialInputStream(raf, 4096);
Tom Tromey committed
238
    long pos = raf.length() - ENDHDR;
239
    long top = Math.max(0, pos - 65536);
Tom Tromey committed
240 241
    do
      {
242 243 244 245
        if (pos < top)
          throw new ZipException
            ("central directory not found, probably not a zip file: " + name);
        inp.seek(pos--);
Tom Tromey committed
246
      }
247
    while (inp.readLeInt() != ENDSIG);
248

249
    if (inp.skip(ENDTOT - ENDNRD) != ENDTOT - ENDNRD)
Tom Tromey committed
250
      throw new EOFException(name);
251 252
    int count = inp.readLeShort();
    if (inp.skip(ENDOFF - ENDSIZ) != ENDOFF - ENDSIZ)
Tom Tromey committed
253
      throw new EOFException(name);
254
    int centralOffset = inp.readLeInt();
Tom Tromey committed
255

256
    entries = new LinkedHashMap<String, ZipEntry> (count+count/2);
257
    inp.seek(centralOffset);
258

Tom Tromey committed
259 260
    for (int i = 0; i < count; i++)
      {
261 262
        if (inp.readLeInt() != CENSIG)
          throw new ZipException("Wrong Central Directory signature: " + name);
Tom Tromey committed
263

264 265 266 267
        inp.skip(4);
        int flags = inp.readLeShort();
        if ((flags & 1) != 0)
          throw new ZipException("invalid CEN header (encrypted entry)");
268 269 270 271 272 273 274 275
        int method = inp.readLeShort();
        int dostime = inp.readLeInt();
        int crc = inp.readLeInt();
        int csize = inp.readLeInt();
        int size = inp.readLeInt();
        int nameLen = inp.readLeShort();
        int extraLen = inp.readLeShort();
        int commentLen = inp.readLeShort();
276
        inp.skip(8);
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
        int offset = inp.readLeInt();
        String name = inp.readString(nameLen);

        ZipEntry entry = new ZipEntry(name);
        entry.setMethod(method);
        entry.setCrc(crc & 0xffffffffL);
        entry.setSize(size & 0xffffffffL);
        entry.setCompressedSize(csize & 0xffffffffL);
        entry.setDOSTime(dostime);
        if (extraLen > 0)
          {
            byte[] extra = new byte[extraLen];
            inp.readFully(extra);
            entry.setExtra(extra);
          }
        if (commentLen > 0)
          {
294
            entry.setComment(inp.readString(commentLen));
295 296 297
          }
        entry.offset = offset;
        entries.put(name, entry);
Tom Tromey committed
298 299 300 301 302 303 304
      }
  }

  /**
   * Closes the ZipFile.  This also closes all input streams given by
   * this class.  After this is called, no further method should be
   * called.
305
   *
Tom Tromey committed
306 307 308 309
   * @exception IOException if a i/o error occured.
   */
  public void close() throws IOException
  {
Tom Tromey committed
310 311 312 313
    RandomAccessFile raf = this.raf;
    if (raf == null)
      return;

Tom Tromey committed
314 315
    synchronized (raf)
      {
316 317 318
        closed = true;
        entries = null;
        raf.close();
Tom Tromey committed
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
      }
  }

  /**
   * Calls the <code>close()</code> method when this ZipFile has not yet
   * been explicitly closed.
   */
  protected void finalize() throws IOException
  {
    if (!closed && raf != null) close();
  }

  /**
   * Returns an enumeration of all Zip entries in this Zip file.
   *
   * @exception IllegalStateException when the ZipFile has already been closed
   */
336
  public Enumeration<? extends ZipEntry> entries()
Tom Tromey committed
337 338
  {
    checkClosed();
339

Tom Tromey committed
340 341
    try
      {
342
        return new ZipEntryEnumeration(getEntries().values().iterator());
Tom Tromey committed
343 344 345
      }
    catch (IOException ioe)
      {
346
        return new EmptyEnumeration<ZipEntry>();
Tom Tromey committed
347 348 349 350 351 352 353
      }
  }

  /**
   * Checks that the ZipFile is still open and reads entries when necessary.
   *
   * @exception IllegalStateException when the ZipFile has already been closed.
354
   * @exception IOException when the entries could not be read.
Tom Tromey committed
355
   */
356
  private LinkedHashMap<String, ZipEntry> getEntries() throws IOException
Tom Tromey committed
357 358 359
  {
    synchronized(raf)
      {
360
        checkClosed();
Tom Tromey committed
361

362 363
        if (entries == null)
          readEntries();
Tom Tromey committed
364

365
        return entries;
Tom Tromey committed
366 367 368 369 370 371
      }
  }

  /**
   * Searches for a zip entry in this archive with the given name.
   *
372
   * @param name the name. May contain directory components separated by
Tom Tromey committed
373 374 375 376 377 378 379 380 381 382 383
   * slashes ('/').
   * @return the zip entry, or null if no entry with that name exists.
   *
   * @exception IllegalStateException when the ZipFile has already been closed
   */
  public ZipEntry getEntry(String name)
  {
    checkClosed();

    try
      {
384 385
        LinkedHashMap<String, ZipEntry> entries = getEntries();
        ZipEntry entry = entries.get(name);
Tom Tromey committed
386 387
        // If we didn't find it, maybe it's a directory.
        if (entry == null && !name.endsWith("/"))
388 389
          entry = entries.get(name + '/');
        return entry != null ? new ZipEntry(entry, name) : null;
Tom Tromey committed
390 391 392
      }
    catch (IOException ioe)
      {
393
        return null;
Tom Tromey committed
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
      }
  }

  /**
   * Creates an input stream reading the given zip entry as
   * uncompressed data.  Normally zip entry should be an entry
   * returned by getEntry() or entries().
   *
   * This implementation returns null if the requested entry does not
   * exist.  This decision is not obviously correct, however, it does
   * appear to mirror Sun's implementation, and it is consistant with
   * their javadoc.  On the other hand, the old JCL book, 2nd Edition,
   * claims that this should return a "non-null ZIP entry".  We have
   * chosen for now ignore the old book, as modern versions of Ant (an
   * important application) depend on this behaviour.  See discussion
   * in this thread:
   * http://gcc.gnu.org/ml/java-patches/2004-q2/msg00602.html
   *
   * @param entry the entry to create an InputStream for.
   * @return the input stream, or null if the requested entry does not exist.
   *
   * @exception IllegalStateException when the ZipFile has already been closed
   * @exception IOException if a i/o error occured.
417
   * @exception ZipException if the Zip archive is malformed.
Tom Tromey committed
418 419 420 421 422
   */
  public InputStream getInputStream(ZipEntry entry) throws IOException
  {
    checkClosed();

423
    LinkedHashMap<String, ZipEntry> entries = getEntries();
Tom Tromey committed
424
    String name = entry.getName();
425
    ZipEntry zipEntry = entries.get(name);
Tom Tromey committed
426 427 428
    if (zipEntry == null)
      return null;

429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
    PartialInputStream inp = new PartialInputStream(raf, 1024);
    inp.seek(zipEntry.offset);

    if (inp.readLeInt() != LOCSIG)
      throw new ZipException("Wrong Local header signature: " + name);

    inp.skip(4);

    if (zipEntry.getMethod() != inp.readLeShort())
      throw new ZipException("Compression method mismatch: " + name);

    inp.skip(16);

    int nameLen = inp.readLeShort();
    int extraLen = inp.readLeShort();
    inp.skip(nameLen + extraLen);

    inp.setLength(zipEntry.getCompressedSize());

Tom Tromey committed
448 449 450 451
    int method = zipEntry.getMethod();
    switch (method)
      {
      case ZipOutputStream.STORED:
452
        return inp;
Tom Tromey committed
453
      case ZipOutputStream.DEFLATED:
454
        inp.addDummyByte();
455 456 457 458 459 460 461 462 463 464 465 466 467
        final Inflater inf = new Inflater(true);
        final int sz = (int) entry.getSize();
        return new InflaterInputStream(inp, inf)
        {
          public int available() throws IOException
          {
            if (sz == -1)
              return super.available();
            if (super.available() != 0)
              return sz - inf.getTotalOut();
            return 0;
          }
        };
Tom Tromey committed
468
      default:
469
        throw new ZipException("Unknown compression method " + method);
Tom Tromey committed
470 471
      }
  }
472

Tom Tromey committed
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
  /**
   * Returns the (path) name of this zip file.
   */
  public String getName()
  {
    return name;
  }

  /**
   * Returns the number of entries in this zip file.
   *
   * @exception IllegalStateException when the ZipFile has already been closed
   */
  public int size()
  {
    checkClosed();
489

Tom Tromey committed
490 491
    try
      {
492
        return getEntries().size();
Tom Tromey committed
493 494 495
      }
    catch (IOException ioe)
      {
496
        return 0;
Tom Tromey committed
497 498
      }
  }
499

500
  private static class ZipEntryEnumeration implements Enumeration<ZipEntry>
Tom Tromey committed
501
  {
502
    private final Iterator<ZipEntry> elements;
Tom Tromey committed
503

504
    public ZipEntryEnumeration(Iterator<ZipEntry> elements)
Tom Tromey committed
505 506 507 508 509 510 511 512 513
    {
      this.elements = elements;
    }

    public boolean hasMoreElements()
    {
      return elements.hasNext();
    }

514
    public ZipEntry nextElement()
Tom Tromey committed
515 516
    {
      /* We return a clone, just to be safe that the user doesn't
517
       * change the entry.
Tom Tromey committed
518
       */
519
      return (ZipEntry) (elements.next().clone());
Tom Tromey committed
520 521 522
    }
  }

523
  private static final class PartialInputStream extends InputStream
Tom Tromey committed
524
  {
525 526 527 528 529 530
    /**
     * The UTF-8 charset use for decoding the filenames.
     */
    private static final Charset UTF8CHARSET = Charset.forName("UTF-8");

    /**
531
     * The actual UTF-8 decoder. Created on demand.
532 533 534
     */
    private CharsetDecoder utf8Decoder;

Tom Tromey committed
535
    private final RandomAccessFile raf;
536 537 538 539
    private final byte[] buffer;
    private long bufferOffset;
    private int pos;
    private long end;
540 541 542 543 544
    // We may need to supply an extra dummy byte to our reader.
    // See Inflater.  We use a count here to simplify the logic
    // elsewhere in this class.  Note that we ignore the dummy
    // byte in methods where we know it is not needed.
    private int dummyByteCount;
Tom Tromey committed
545

546 547
    public PartialInputStream(RandomAccessFile raf, int bufferSize)
      throws IOException
Tom Tromey committed
548 549
    {
      this.raf = raf;
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564
      buffer = new byte[bufferSize];
      bufferOffset = -buffer.length;
      pos = buffer.length;
      end = raf.length();
    }

    void setLength(long length)
    {
      end = bufferOffset + pos + length;
    }

    private void fillBuffer() throws IOException
    {
      synchronized (raf)
        {
565 566 567 568 569 570 571 572 573 574 575
          long len = end - bufferOffset;
          if (len == 0 && dummyByteCount > 0)
            {
              buffer[0] = 0;
              dummyByteCount = 0;
            }
          else
            {
              raf.seek(bufferOffset);
              raf.readFully(buffer, 0, (int) Math.min(buffer.length, len));
            }
576
        }
Tom Tromey committed
577
    }
578

Tom Tromey committed
579 580
    public int available()
    {
581
      long amount = end - (bufferOffset + pos);
Tom Tromey committed
582
      if (amount > Integer.MAX_VALUE)
583
        return Integer.MAX_VALUE;
Tom Tromey committed
584 585
      return (int) amount;
    }
586

Tom Tromey committed
587 588
    public int read() throws IOException
    {
589
      if (bufferOffset + pos >= end + dummyByteCount)
590
        return -1;
591 592 593 594 595 596
      if (pos == buffer.length)
        {
          bufferOffset += buffer.length;
          pos = 0;
          fillBuffer();
        }
597

598
      return buffer[pos++] & 0xFF;
Tom Tromey committed
599 600 601 602
    }

    public int read(byte[] b, int off, int len) throws IOException
    {
603
      if (len > end + dummyByteCount - (bufferOffset + pos))
604 605 606 607 608
        {
          len = (int) (end + dummyByteCount - (bufferOffset + pos));
          if (len == 0)
            return -1;
        }
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627

      int totalBytesRead = Math.min(buffer.length - pos, len);
      System.arraycopy(buffer, pos, b, off, totalBytesRead);
      pos += totalBytesRead;
      off += totalBytesRead;
      len -= totalBytesRead;

      while (len > 0)
        {
          bufferOffset += buffer.length;
          pos = 0;
          fillBuffer();
          int remain = Math.min(buffer.length, len);
          System.arraycopy(buffer, pos, b, off, remain);
          pos += remain;
          off += remain;
          len -= remain;
          totalBytesRead += remain;
        }
628

629
      return totalBytesRead;
Tom Tromey committed
630 631
    }

632
    public long skip(long amount) throws IOException
Tom Tromey committed
633 634
    {
      if (amount < 0)
635
        return 0;
636
      if (amount > end - (bufferOffset + pos))
637
        amount = end - (bufferOffset + pos);
638
      seek(bufferOffset + pos + amount);
Tom Tromey committed
639 640
      return amount;
    }
641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670

    void seek(long newpos) throws IOException
    {
      long offset = newpos - bufferOffset;
      if (offset >= 0 && offset <= buffer.length)
        {
          pos = (int) offset;
        }
      else
        {
          bufferOffset = newpos;
          pos = 0;
          fillBuffer();
        }
    }

    void readFully(byte[] buf) throws IOException
    {
      if (read(buf, 0, buf.length) != buf.length)
        throw new EOFException();
    }

    void readFully(byte[] buf, int off, int len) throws IOException
    {
      if (read(buf, off, len) != len)
        throw new EOFException();
    }

    int readLeShort() throws IOException
    {
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685
      int result;
      if(pos + 1 < buffer.length)
        {
          result = ((buffer[pos + 0] & 0xff) | (buffer[pos + 1] & 0xff) << 8);
          pos += 2;
        }
      else
        {
          int b0 = read();
          int b1 = read();
          if (b1 == -1)
            throw new EOFException();
          result = (b0 & 0xff) | (b1 & 0xff) << 8;
        }
      return result;
686 687 688 689
    }

    int readLeInt() throws IOException
    {
690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750
      int result;
      if(pos + 3 < buffer.length)
        {
          result = (((buffer[pos + 0] & 0xff) | (buffer[pos + 1] & 0xff) << 8)
                   | ((buffer[pos + 2] & 0xff)
                       | (buffer[pos + 3] & 0xff) << 8) << 16);
          pos += 4;
        }
      else
        {
          int b0 = read();
          int b1 = read();
          int b2 = read();
          int b3 = read();
          if (b3 == -1)
            throw new EOFException();
          result =  (((b0 & 0xff) | (b1 & 0xff) << 8) | ((b2 & 0xff)
                    | (b3 & 0xff) << 8) << 16);
        }
      return result;
    }

    /**
     * Decode chars from byte buffer using UTF8 encoding.  This
     * operation is performance-critical since a jar file contains a
     * large number of strings for the name of each file in the
     * archive.  This routine therefore avoids using the expensive
     * utf8Decoder when decoding is straightforward.
     *
     * @param buffer the buffer that contains the encoded character
     *        data
     * @param pos the index in buffer of the first byte of the encoded
     *        data
     * @param length the length of the encoded data in number of
     *        bytes.
     *
     * @return a String that contains the decoded characters.
     */
    private String decodeChars(byte[] buffer, int pos, int length)
      throws IOException
    {
      String result;
      int i=length - 1;
      while ((i >= 0) && (buffer[i] <= 0x7f))
        {
          i--;
        }
      if (i < 0)
        {
          result = new String(buffer, 0, pos, length);
        }
      else
        {
          ByteBuffer bufferBuffer = ByteBuffer.wrap(buffer, pos, length);
          if (utf8Decoder == null)
            utf8Decoder = UTF8CHARSET.newDecoder();
          utf8Decoder.reset();
          char [] characters = utf8Decoder.decode(bufferBuffer).array();
          result = String.valueOf(characters);
        }
      return result;
751 752 753 754 755 756 757
    }

    String readString(int length) throws IOException
    {
      if (length > end - (bufferOffset + pos))
        throw new EOFException();

758
      String result = null;
759 760 761 762
      try
        {
          if (buffer.length - pos >= length)
            {
763
              result = decodeChars(buffer, pos, length);
764 765 766 767 768 769
              pos += length;
            }
          else
            {
              byte[] b = new byte[length];
              readFully(b);
770
              result = decodeChars(b, 0, length);
771 772 773 774 775 776
            }
        }
      catch (UnsupportedEncodingException uee)
        {
          throw new AssertionError(uee);
        }
777
      return result;
778
    }
779 780 781 782 783

    public void addDummyByte()
    {
      dummyByteCount = 1;
    }
Tom Tromey committed
784 785
  }
}