/*
 * 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.
 */

package net.jini.config;

import java.io.IOException;
import java.io.Reader;

/**
 * A Reader that converts Unicode escape sequences, throwing IOException for
 * malformed escapes.
 *
 * 
 * @since 2.0
 */
final class UnicodeEscapesDecodingReader extends Reader {

    /** Marker for when peekc is empty. */
    private static final int NO_CHAR = Integer.MIN_VALUE;

    /** The source of characters to translate. */
    private Reader reader;

    /**
     * A peeked character -- the character after a backslash if it wasn't 'u'
     * -- or NO_CHAR if no peek was done.
     */
    private int peekc = NO_CHAR;

    /** Buffer to hold the Unicode value being parsed. */
    private final char[] code = new char[4];

    /** Creates an instance of this class. */
    UnicodeEscapesDecodingReader(Reader reader) {
	if (reader == null) {
	    throw new NullPointerException("reader is null");
	}
	this.reader = reader;
    }

    public synchronized int read() throws IOException {
	if (reader == null) {
	    throw new IOException("stream is closed");
	}
	return readInternal();
    }

    public synchronized int read(char[] cbuf, int off, int len)
	throws IOException
    {
	if (reader == null) {
	    throw new IOException("stream is closed");
	} else if (off < 0 || off > cbuf.length ||
		   len < 0 || off + len > cbuf.length ||
		   off + len < 0)
	{
	    throw new IndexOutOfBoundsException();
	}
	for (int nchars = 0; nchars < len; nchars++) {
	    int c = readInternal();
	    if (c < 0) {
		return (nchars == 0) ? -1 : nchars;
	    }
	    cbuf[off + nchars] = (char) c;
	}
	return len;
    }

    /** Implements read() without checking if this reader is closed. */
    private int readInternal() throws IOException {
	int c;
	if (peekc == NO_CHAR) {
	    c = reader.read();
	} else {
	    c = peekc;
	    peekc = NO_CHAR;
	    if (c == '\\') {	/* an escaped slash */
		return c;
	    }
	}
	if (c != '\\') {	/* not a Unicode escape */
	    return c;
	}
	c = reader.read();
	if (c != 'u') {		/* not a Unicode escape */
	    peekc = c;
	    return '\\';
	}
	do {
	    c = reader.read();
	} while (c == 'u');
	int nchars = 0;
	if (c >= 0) {
	    code[nchars++] = (char) c;
	    while (nchars < 4) {
		int n = reader.read(code, nchars, 4 - nchars);
		if (n < 0) {
		    break;
		}
		nchars += n;
	    }
	}
	String s = new String(code, 0, nchars);
	if (nchars == 4) {
	    try {
		int i = Integer.parseInt(s, 16);
		if (i >= 0) {
		    return (char) i;
		}
	    } catch (NumberFormatException e) {
	    }
	}
	throw new IOException("illegal Unicode escape: \\u" + s);
    }

    public synchronized void close() throws IOException {
	if (reader != null) {
	    reader.close();
	    reader = null;
	}
    }
}
