001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2010–2011 ApexIdentity Inc.
015 * Portions Copyright 2011-2014 ForgeRock AS.
016 */
017
018package org.forgerock.openig.io;
019
020import java.io.IOException;
021import java.util.Arrays;
022
023/**
024 * A buffer that uses a byte array for data storage. The byte array starts at a prescribed
025 * initial length, and grows exponentially up to the prescribed limit.
026 * <p>
027 * <strong>Note:</strong> This implementation is not synchronized. If multiple threads access
028 * a buffer concurrently, threads that append to the buffer should synchronize on the instance
029 * of this object.
030 */
031public class MemoryBuffer implements Buffer {
032
033    /** The byte array storing buffer data. */
034    byte[] data; // package scope to give TemporaryBuffer access without intermediate copy
035
036    /** The current length of the buffer. */
037    private final int limit;
038
039    /** Current length of the buffer. */
040    private int length = 0;
041
042    /**
043     * Constructs a new memory buffer.
044     *
045     * @param initial the initial size of the byte array to create.
046     * @param limit the buffer length limit, after which an {@link OverflowException} will be thrown.
047     */
048    public MemoryBuffer(int initial, int limit) {
049        data = new byte[initial];
050        this.limit = limit;
051    }
052
053    @Override
054    public int read(int pos, byte[] b, int off, int len) throws IOException {
055        if (off < 0 || len < 0 || len > b.length - off) {
056            throw new IndexOutOfBoundsException();
057        }
058        notClosed();
059        int n = 0;
060        if (pos < length) {
061            n = Math.min(len, length - pos);
062            System.arraycopy(data, pos, b, off, n);
063        }
064        return n;
065    }
066
067    @Override
068    public void append(byte[] b, int off, int len) throws IOException {
069        if (off < 0 || len < 0 || len > b.length - off) {
070            throw new IndexOutOfBoundsException();
071        }
072        notClosed();
073        int end = this.length + len;
074        if (end > limit) {
075            throw new OverflowException();
076        }
077        if (data.length < end) {
078            // buffer grows exponentially (up to limit)
079            data = Arrays.copyOf(data, Math.max(end, Math.min(limit, data.length << 1)));
080        }
081        System.arraycopy(b, off, data, this.length, len);
082        this.length += len;
083    }
084
085    @Override
086    public int length() {
087        return length;
088    }
089
090    @Override
091    public void close() {
092        data = null;
093    }
094
095    @Override
096    public void finalize() throws Throwable {
097        close();
098        super.finalize();
099    }
100
101    /**
102     * Throws an {@link IOException} if the buffer is closed.
103     */
104    private void notClosed() throws IOException {
105        if (data == null) {
106            throw new IOException("buffer is closed");
107        }
108    }
109}