/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
/*
 * Contributor(s): Thomas Ball
 */

package org.netbeans.modules.classfile;

import java.io.*;

/**
 * A Java class file constant pool reader.  This class is
 * used rather than java.io.DataInputStream as it is
 * optimized for constant pool reading only.
 *
 * @author Thomas Ball
 */
public final class ConstantPoolReader extends FilterInputStream implements DataInput {

    public ConstantPoolReader(InputStream in) {
	super(in);
    }

    public void readFully(byte[] b) throws IOException {
	readFully(b, 0, b.length);
    }

    public void readFully(byte[] b, int off, int len) throws IOException {
	InputStream input = in;
	int n = 0;
	while (n < len) {
	    int count = input.read(b, off + n, len - n);
	    if (count < 0)
		throw new EOFException();
	    n += count;
	}
    }

    public int skipBytes(int n) throws IOException {
	InputStream input = in;
	int total = 0;
	int cur = 0;

	while ((total<n) && ((cur = (int) input.skip(n-total)) > 0)) {
	    total += cur;
	}

	return total;
    }

    public boolean readBoolean() throws IOException {
	int ch = in.read();
	if (ch < 0)
	    throw new EOFException();
	return (ch != 0);
    }

    public byte readByte() throws IOException {
	int ch = in.read();
	if (ch < 0)
	    throw new EOFException();
	return (byte)(ch);
    }

    public int readUnsignedByte() throws IOException {
	int ch = in.read();
	if (ch < 0)
	    throw new EOFException();
	return ch;
    }

    public short readShort() throws IOException {
	InputStream input = in;
	int ch1 = input.read();
	int ch2 = input.read();
	if ((ch1 | ch2) < 0)
	     throw new EOFException();
	return (short)((ch1 << 8) + ch2);
    }

    public int readUnsignedShort() throws IOException {
	InputStream input = in;
	int ch1 = input.read();
	int ch2 = input.read();
	if ((ch1 | ch2) < 0)
	     throw new EOFException();
	return (ch1 << 8) + ch2;
    }

    public char readChar() throws IOException {
	return (char)readUnsignedShort();
    }

    public int readInt() throws IOException {
	InputStream input = in;
	int ch1 = input.read();
	int ch2 = input.read();
	int ch3 = input.read();
	int ch4 = input.read();
	if ((ch1 | ch2 | ch3 | ch4) < 0)
	     throw new EOFException();
	return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
    }

    public long readLong() throws IOException {
	return ((long)(readInt()) << 32) + (readInt() & 0xFFFFFFFFL);
    }

    public float readFloat() throws IOException {
	return Float.intBitsToFloat(readInt());
    }

    public double readDouble() throws IOException {
	return Double.longBitsToDouble(readLong());
    }

    private char[] lineBuffer;

    public String readLine() throws IOException {
	InputStream in = this.in;
	char buf[] = lineBuffer;

	if (buf == null) {
	    buf = lineBuffer = new char[128];
	}

	int room = buf.length;
	int offset = 0;
	int c;

loop:	while (true) {
	    switch (c = in.read()) {
	      case -1:
	      case '\n':
		break loop;

	      case '\r':
		int c2 = in.read();
		if ((c2 != '\n') && (c2 != -1)) {
		    if (!(in instanceof PushbackInputStream)) {
			in = this.in = new PushbackInputStream(in);
		    }
		    ((PushbackInputStream)in).unread(c2);
		}
		break loop;

	      default:
		if (--room < 0) {
		    buf = new char[offset + 128];
		    room = buf.length - offset - 1;
		    System.arraycopy(lineBuffer, 0, buf, 0, offset);
		    lineBuffer = buf;
		}
		buf[offset++] = (char) c;
		break;
	    }
	}
	if ((c == -1) && (offset == 0)) {
	    return null;
	}
	return String.copyValueOf(buf, 0, offset);
    }

    // NOT threadsafe: for performance
    static char[] str = new char[1024];
    byte[] bytearr = new byte[1024];

    public String readUTF() throws IOException {
        int utflen = readUnsignedShort();
	if (utflen > bytearr.length)
	    bytearr = new byte[utflen];

 	readFully(bytearr, 0, utflen);
	return readUTF(bytearr, utflen);
    }

    byte[] readRawUTF() throws IOException {
        int utflen = readUnsignedShort();
	byte[] buf = new byte[utflen];
	readFully(buf, 0, utflen);
	return buf;
    }

    static synchronized String readUTF(byte[] src, int utflen) {
	int i = 0;
	int strlen = 0;

	if (utflen > str.length)
	    str = new char[utflen];

        while (i < utflen) {
            int b = src[i++] & 0xFF;
            if (b >= 0xE0) {
                b = (b & 0x0F) << 12;
                b |= (src[i++] & 0x3F) << 6;
                b |= (src[i++] & 0x3F);
            } else if (b >= 0xC0) {
                b = (b & 0x1F) << 6;
                b |= (src[i++] & 0x3F);
            }
            str[strlen++] = (char)b;
        }

        // The number of chars produced may be less than utflen
        return new String(str, 0, strlen);
    }
}
