/* 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 java.nio.charset;

import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.util.WeakHashMap;

/**
 * Used to indicate the result of encoding/decoding. There are four types of
 * results:
 * <ol>
 * <li>UNDERFLOW indicates that all input has been processed but more input is
 * required. It is represented by the unique object
 * <code>CoderResult.UNDERFLOW</code>.
 * <li>OVERFLOW indicates an insufficient output buffer size. It is represented
 * by the unique object <code>CoderResult.OVERFLOW</code>.
 * <li>A malformed-input error indicates that an unrecognizable sequence of
 * input units has been encountered. Get an instance of this type of result by
 * calling <code>CoderResult.malformedForLength(int)</code> with the length of
 * the malformed-input.
 * <li>An unmappable-character error indicates that a sequence of input units
 * can not be mapped to the output charset. Get an instance of this type of
 * result by calling <code>CoderResult.unmappableForLength(int)</code> with
 * the input sequence size indicating the identity of the unmappable character.
 * </ol>
 */
public class CoderResult {

    // indicating underflow error type
    private static final int TYPE_UNDERFLOW = 1;

    // indicating overflow error type
    private static final int TYPE_OVERFLOW = 2;

    // indicating malformed-input error type
    private static final int TYPE_MALFORMED_INPUT = 3;

    // indicating unmappable character error type
    private static final int TYPE_UNMAPPABLE_CHAR = 4;

    /**
     * Result object indicating that there is insufficient data in the
     * encoding/decoding buffer or that additional data is required.
     */
    public static final CoderResult UNDERFLOW = new CoderResult(TYPE_UNDERFLOW,
            0);

    /**
     * Result object used to indicate that the output buffer does not have
     * enough space available to store the result of the encoding/decoding.
     */
    public static final CoderResult OVERFLOW = new CoderResult(TYPE_OVERFLOW, 0);

    /*
     * Stores unique result objects for each malformed-input error of a certain
     * length
     */
    private static WeakHashMap<Integer, CoderResult> _malformedErrors = new WeakHashMap<Integer, CoderResult>();

    /*
     * Stores unique result objects for each unmappable-character error of a
     * certain length
     */
    private static WeakHashMap<Integer, CoderResult> _unmappableErrors = new WeakHashMap<Integer, CoderResult>();

    // the type of this result
    private final int type;

    // the length of the erroneous input
    private final int length;

    /**
     * Constructs a <code>CoderResult</code> object with its text description.
     *
     * @param type
     *            the type of this result
     * @param length
     *            the length of the erroneous input
     */
    private CoderResult(int type, int length) {
        super();
        this.type = type;
        this.length = length;
    }

    /**
     * Gets a <code>CoderResult</code> object indicating a malformed-input
     * error.
     *
     * @param length
     *            the length of the malformed-input.
     * @return a <code>CoderResult</code> object indicating a malformed-input
     *         error.
     * @throws IllegalArgumentException
     *             if <code>length</code> is non-positive.
     */
    public static synchronized CoderResult malformedForLength(int length)
            throws IllegalArgumentException {
        if (length > 0) {
            Integer key = Integer.valueOf(length);
            synchronized (_malformedErrors) {
                CoderResult r = _malformedErrors.get(key);
                if (null == r) {
                    r = new CoderResult(TYPE_MALFORMED_INPUT, length);
                    _malformedErrors.put(key, r);
                }
                return r;
            }
        }
        throw new IllegalArgumentException("Length must be greater than 0; was " + length);
    }

    /**
     * Gets a <code>CoderResult</code> object indicating an unmappable
     * character error.
     *
     * @param length
     *            the length of the input unit sequence denoting the unmappable
     *            character.
     * @return a <code>CoderResult</code> object indicating an unmappable
     *         character error.
     * @throws IllegalArgumentException
     *             if <code>length</code> is non-positive.
     */
    public static synchronized CoderResult unmappableForLength(int length)
            throws IllegalArgumentException {
        if (length > 0) {
            Integer key = Integer.valueOf(length);
            synchronized (_unmappableErrors) {
                CoderResult r = _unmappableErrors.get(key);
                if (null == r) {
                    r = new CoderResult(TYPE_UNMAPPABLE_CHAR, length);
                    _unmappableErrors.put(key, r);
                }
                return r;
            }
        }
        throw new IllegalArgumentException("Length must be greater than 0; was " + length);
    }

    /**
     * Returns true if this result is an underflow condition.
     *
     * @return true if an underflow, otherwise false.
     */
    public boolean isUnderflow() {
        return this.type == TYPE_UNDERFLOW;
    }

    /**
     * Returns true if this result represents a malformed-input error or an
     * unmappable-character error.
     *
     * @return true if this is a malformed-input error or an
     *         unmappable-character error, otherwise false.
     */
    public boolean isError() {
        return this.type == TYPE_MALFORMED_INPUT
                || this.type == TYPE_UNMAPPABLE_CHAR;
    }

    /**
     * Returns true if this result represents a malformed-input error.
     *
     * @return true if this is a malformed-input error, otherwise false.
     */
    public boolean isMalformed() {
        return this.type == TYPE_MALFORMED_INPUT;
    }

    /**
     * Returns true if this result is an overflow condition.
     *
     * @return true if this is an overflow, otherwise false.
     */
    public boolean isOverflow() {
        return this.type == TYPE_OVERFLOW;
    }

    /**
     * Returns true if this result represents an unmappable-character error.
     *
     * @return true if this is an unmappable-character error, otherwise false.
     */
    public boolean isUnmappable() {
        return this.type == TYPE_UNMAPPABLE_CHAR;
    }

    /**
     * Gets the length of the erroneous input. The length is only meaningful to
     * a malformed-input error or an unmappable character error.
     *
     * @return the length, as an integer, of this object's erroneous input.
     * @throws UnsupportedOperationException
     *             if this result is an overflow or underflow.
     */
    public int length() throws UnsupportedOperationException {
        if (this.type == TYPE_MALFORMED_INPUT || this.type == TYPE_UNMAPPABLE_CHAR) {
            return this.length;
        }
        throw new UnsupportedOperationException("length meaningless for " + toString());
    }

    /**
     * Throws an exception corresponding to this coder result.
     *
     * @throws BufferUnderflowException
     *             in case this is an underflow.
     * @throws BufferOverflowException
     *             in case this is an overflow.
     * @throws UnmappableCharacterException
     *             in case this is an unmappable-character error.
     * @throws MalformedInputException
     *             in case this is a malformed-input error.
     * @throws CharacterCodingException
     *             the default exception.
     */
    public void throwException() throws BufferUnderflowException,
            BufferOverflowException, UnmappableCharacterException,
            MalformedInputException, CharacterCodingException {
        switch (this.type) {
            case TYPE_UNDERFLOW:
                throw new BufferUnderflowException();
            case TYPE_OVERFLOW:
                throw new BufferOverflowException();
            case TYPE_UNMAPPABLE_CHAR:
                throw new UnmappableCharacterException(this.length);
            case TYPE_MALFORMED_INPUT:
                throw new MalformedInputException(this.length);
            default:
                throw new CharacterCodingException();
        }
    }

    /**
     * Returns a text description of this result.
     *
     * @return a text description of this result.
     */
    @Override
    public String toString() {
        String dsc = null;
        switch (this.type) {
            case TYPE_UNDERFLOW:
                dsc = "UNDERFLOW error";
                break;
            case TYPE_OVERFLOW:
                dsc = "OVERFLOW error";
                break;
            case TYPE_UNMAPPABLE_CHAR:
                dsc = "Unmappable-character error with erroneous input length "
                        + this.length;
                break;
            case TYPE_MALFORMED_INPUT:
                dsc = "Malformed-input error with erroneous input length "
                        + this.length;
                break;
            default:
                dsc = "";
                break;
        }
        return getClass().getName() + "[" + dsc + "]";
    }
}
