/*
 * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.javafx.sg.prism;

import java.lang.ref.WeakReference;
import java.nio.BufferOverflowException;
import java.util.Arrays;

A growable buffer that can contain both byte-encoded primitive values and a list of Objects stored for communication between a writer that fills it with data and a reader that empties the data behind the writer. Both buffers (the byte-encoded array and the Object array) grow as needed with no hard limits and the two are kept separately so it is up to the reader and writer to read the two streams in a predetermined synchronicity of the two streams. The methods on a given GrowableDataBuffer object are not synchronized or thread-safe and writing to or reading from the object from more than one thread at a time is unsupported. In particular, multiple writer threads and/or multiple reader threads will definitely cause problems. The static getBuffer() factory methods and the static returnBuffer() method are all synchronized so that they can be called from any thread at any time, but any given buffer should only be returned to the pool once.
/** * A growable buffer that can contain both byte-encoded primitive values * and a list of Objects stored for communication between a writer that fills * it with data and a reader that empties the data behind the writer. * * Both buffers (the byte-encoded array and the Object array) grow as needed * with no hard limits and the two are kept separately so it is up to the * reader and writer to read the two streams in a predetermined synchronicity * of the two streams. * * The methods on a given GrowableDataBuffer object are not synchronized or * thread-safe and writing to or reading from the object from more than one * thread at a time is unsupported. In particular, multiple writer threads * and/or multiple reader threads will definitely cause problems. * * The static getBuffer() factory methods and the static returnBuffer() method * are all synchronized so that they can be called from any thread at any * time, but any given buffer should only be returned to the pool once. */
public class GrowableDataBuffer { static final int VAL_GROW_QUANTUM = 1024; static final int MAX_VAL_GROW = 1024 * 1024; static final int MIN_OBJ_GROW = 32; static class WeakLink { WeakReference<GrowableDataBuffer> bufref; WeakLink next; } static WeakLink buflist = new WeakLink(); // Dummy "head" link object
Retrieve a buffer with an initial byte-encoding capacity of at least minsize bytes. The initial capacity of the object buffer will be the default size.
Params:
  • minsize – the minimum initial size of the byte-encoding buffer
Returns:a GrowableDataBuffer object of the requested size
/** * Retrieve a buffer with an initial byte-encoding capacity of at least * {@code minsize} bytes. * The initial capacity of the object buffer will be the default size. * * @param minsize the minimum initial size of the byte-encoding buffer * @return a {@code GrowableDataBuffer} object of the requested size */
public static GrowableDataBuffer getBuffer(int minsize) { return getBuffer(minsize, MIN_OBJ_GROW); }
Retrieve a buffer with an initial byte-encoding capacity of at least minvals bytes and an initial object buffer capacity of at least minobjs Objects.
Params:
  • minvals – the minimum initial size of the byte-encoding buffer
  • minobjs – the minimum initial size of the Object buffer
Returns:a GrowableDataBuffer object of the requested sizes
/** * Retrieve a buffer with an initial byte-encoding capacity of at least * {@code minvals} bytes and an initial object buffer capacity of at * least {@code minobjs} Objects. * * @param minvals the minimum initial size of the byte-encoding buffer * @param minobjs the minimum initial size of the Object buffer * @return a {@code GrowableDataBuffer} object of the requested sizes */
public synchronized static GrowableDataBuffer getBuffer(int minvals, int minobjs) { WeakLink prev = buflist; WeakLink cur = buflist.next; while (cur != null) { GrowableDataBuffer curgdb = cur.bufref.get(); WeakLink next = cur.next; if (curgdb == null) { prev.next = cur = next; continue; } if (curgdb.valueCapacity() >= minvals && curgdb.objectCapacity() >= minobjs) { prev.next = next; return curgdb; } prev = cur; cur = next; } return new GrowableDataBuffer(minvals, minobjs); }
Return the indicated GrowableDataBuffer object to the pool for reuse. A given GrowableDataBuffer object should only be returned to the pool once per retrieval from the getBuffer() methods.
Params:
  • gdb – the GrowableDataBuffer object to be reused.
/** * Return the indicated {@code GrowableDataBuffer} object to the pool * for reuse. * A given {@code GrowableDataBuffer} object should only be returned to * the pool once per retrieval from the {@code getBuffer()} methods. * * @param gdb the {@code GrowableDataBuffer} object to be reused. */
public synchronized static void returnBuffer(GrowableDataBuffer retgdb) { int retvlen = retgdb.valueCapacity(); int retolen = retgdb.objectCapacity(); retgdb.reset(); WeakLink prev = buflist; WeakLink cur = buflist.next; while (cur != null) { GrowableDataBuffer curgdb = cur.bufref.get(); WeakLink next = cur.next; if (curgdb == null) { prev.next = cur = next; continue; } int curvlen = curgdb.valueCapacity(); int curolen = curgdb.objectCapacity(); if (curvlen > retvlen || (curvlen == retvlen && curolen >= retolen)) { break; } prev = cur; cur = next; } WeakLink retlink = new WeakLink(); retlink.bufref = new WeakReference<>(retgdb); prev.next = retlink; retlink.next = cur; } byte vals[]; int writevalpos; // next vals location to write encoded values int readvalpos; // next vals location to read encoded values int savevalpos; // saved valpos for reading data multiple times Object objs[]; int writeobjpos; // next objs location to write data objects int readobjpos; // next objs location to read data objects int saveobjpos; // saved objpos for reading objects multiple times private GrowableDataBuffer(int initvalsize, int initobjsize) { vals = new byte[initvalsize]; objs = new Object[initobjsize]; }
The location of the next byte to be read from the encoded value buffer. This must always be less than or equal to the writeValuePosition().
Returns:the byte position of the next byte data to be read.
/** * The location of the next byte to be read from the encoded value * buffer. * This must always be less than or equal to the * {@code writeValuePosition()}. * * @return the byte position of the next byte data to be read. */
public int readValuePosition() { return readvalpos; }
The location of the next byte to be written to the encoded value buffer.
Returns:the byte position of the next byte data to be written.
/** * The location of the next byte to be written to the encoded value * buffer. * * @return the byte position of the next byte data to be written. */
public int writeValuePosition() { return writevalpos; }
The location of the next object to be read from the object buffer. This must always be less than or equal to the writeObjectPosition().
Returns:the position of the next object to be read.
/** * The location of the next object to be read from the object buffer. * This must always be less than or equal to the * {@code writeObjectPosition()}. * * @return the position of the next object to be read. */
public int readObjectPosition() { return readobjpos; }
The location of the next object to be written to the object buffer.
Returns:the position of the next object to be written.
/** * The location of the next object to be written to the object buffer. * * @return the position of the next object to be written. */
public int writeObjectPosition() { return writeobjpos; }
The capacity, in bytes, of the byte-encoding buffer.
Returns:the capacity of the byte-encoding buffer
/** * The capacity, in bytes, of the byte-encoding buffer. * * @return the capacity of the byte-encoding buffer */
public int valueCapacity() { return vals.length; }
The capacity, in objects, of the Object buffer.
Returns:the capacity of the Object buffer
/** * The capacity, in objects, of the {@code Object} buffer. * * @return the capacity of the {@code Object} buffer */
public int objectCapacity() { return objs.length; }
Save aside the current read positions of both the byte-encoding buffer and the Object buffer for a later restore() operation.
/** * Save aside the current read positions of both the byte-encoding * buffer and the {@code Object} buffer for a later {@code restore()} * operation. */
public void save() { savevalpos = readvalpos; saveobjpos = readobjpos; }
Restore the read positions of both the byte-encoding buffer and the Object buffer to their last saved positions.
/** * Restore the read positions of both the byte-encoding buffer and * the {@code Object} buffer to their last saved positions. */
public void restore() { readvalpos = savevalpos; readobjpos = saveobjpos; }
Indicates whether or not there are values in the byte-encoding buffer waiting to be read.
Returns:true iff there are data values to be read
/** * Indicates whether or not there are values in the byte-encoding * buffer waiting to be read. * * @return true iff there are data values to be read */
public boolean hasValues() { return (readvalpos < writevalpos); }
Indicates whether or not there are objects in the object buffer waiting to be read.
Returns:true iff there are objects to be read
/** * Indicates whether or not there are objects in the object * buffer waiting to be read. * * @return true iff there are objects to be read */
public boolean hasObjects() { return (readobjpos < writeobjpos); }
Indicates whether the byte-encoding buffer is completely empty. Note that this is different from whether or not there is unread data in the byte-encoding buffer. A buffer which has been written and then later fully emptied by reading is not considered "empty".
Returns:true iff there is no data at all stored in the byte buffer
/** * Indicates whether the byte-encoding buffer is completely empty. * Note that this is different from whether or not there is unread * data in the byte-encoding buffer. A buffer which has been written * and then later fully emptied by reading is not considered "empty". * * @return true iff there is no data at all stored in the byte buffer */
public boolean isEmpty() { return (writevalpos == 0); }
Clears out all data and resets all positions to the start of the buffers so that a new sequence of writing, then reading of data and objects can begin. Note that the Object array is cleared to nulls here and those objects will finally become collectable by the garbage collector.
/** * Clears out all data and resets all positions to the start of the * buffers so that a new sequence of writing, then reading of data * and objects can begin. * Note that the {@code Object} array is cleared to nulls here and * those objects will finally become collectable by the garbage collector. */
public void reset() { readvalpos = savevalpos = writevalpos = 0; readobjpos = saveobjpos = 0; if (writeobjpos > 0) { Arrays.fill(objs, 0, writeobjpos, null); writeobjpos = 0; } }
Appends the contents of both the byte and Object buffers in the indicated GrowableDataBuffer to this object. The data in the other indicated GrowableDataBuffer object is not disturbed in any way.
Params:
  • gdb – the GrowableDataBuffer to append to this object
/** * Appends the contents of both the byte and {@code Object} buffers in * the indicated {@code GrowableDataBuffer} to this object. * The data in the other indicated {@code GrowableDataBuffer} object * is not disturbed in any way. * * @param gdb the {@code GrowableDataBuffer} to append to this object */
public void append(GrowableDataBuffer gdb) { ensureWriteCapacity(gdb.writevalpos); System.arraycopy(gdb.vals, 0, vals, writevalpos, gdb.writevalpos); writevalpos += gdb.writevalpos; if (writeobjpos + gdb.writeobjpos > objs.length) { objs = Arrays.copyOf(objs, writeobjpos + gdb.writeobjpos); } System.arraycopy(gdb.objs, 0, objs, writeobjpos, gdb.writeobjpos); writeobjpos += gdb.writeobjpos; } private void ensureWriteCapacity(int newbytes) { if (newbytes > vals.length - writevalpos) { newbytes = writevalpos + newbytes - vals.length; // Double in size up to MAX_VAL_GROW int growbytes = Math.min(vals.length, MAX_VAL_GROW); // And at least by the number of new bytes if (growbytes < newbytes) growbytes = newbytes; int newsize = vals.length + growbytes; newsize = (newsize + (VAL_GROW_QUANTUM - 1)) & ~(VAL_GROW_QUANTUM - 1); vals = Arrays.copyOf(vals, newsize); } } private void ensureReadCapacity(int bytesneeded) { if (readvalpos + bytesneeded > writevalpos) { throw new BufferOverflowException(); } }
Encode a boolean value and write it to the end of the byte-encoding array
Params:
  • b – the boolean value to be written
/** * Encode a boolean value and write it to the end of the byte-encoding array * * @param b the boolean value to be written */
public void putBoolean(boolean b) { putByte(b ? (byte) 1 : (byte) 0); }
Write a byte value to the end of the byte-encoding array
Params:
  • b – the byte value to be written
/** * Write a byte value to the end of the byte-encoding array * * @param b the byte value to be written */
public void putByte(byte b) { ensureWriteCapacity(1); vals[writevalpos++] = b; }
Encode a char value and write it to the end of the byte-encoding array
Params:
  • c – the char value to be written
/** * Encode a char value and write it to the end of the byte-encoding array * * @param c the char value to be written */
public void putChar(char c) { ensureWriteCapacity(2); vals[writevalpos++] = (byte) (c >> 8); vals[writevalpos++] = (byte) (c ); }
Encode a short value and write it to the end of the byte-encoding array
Params:
  • s – the short value to be written
/** * Encode a short value and write it to the end of the byte-encoding array * * @param s the short value to be written */
public void putShort(short s) { ensureWriteCapacity(2); vals[writevalpos++] = (byte) (s >> 8); vals[writevalpos++] = (byte) (s ); }
Encode an int value and write it to the end of the byte-encoding array
Params:
  • i – the int value to be written
/** * Encode an int value and write it to the end of the byte-encoding array * * @param i the int value to be written */
public void putInt(int i) { ensureWriteCapacity(4); vals[writevalpos++] = (byte) (i >> 24); vals[writevalpos++] = (byte) (i >> 16); vals[writevalpos++] = (byte) (i >> 8); vals[writevalpos++] = (byte) (i ); }
Encode a long value and write it to the end of the byte-encoding array
Params:
  • l – the long value to be written
/** * Encode a long value and write it to the end of the byte-encoding array * * @param l the long value to be written */
public void putLong(long l) { ensureWriteCapacity(8); vals[writevalpos++] = (byte) (l >> 56); vals[writevalpos++] = (byte) (l >> 48); vals[writevalpos++] = (byte) (l >> 40); vals[writevalpos++] = (byte) (l >> 32); vals[writevalpos++] = (byte) (l >> 24); vals[writevalpos++] = (byte) (l >> 16); vals[writevalpos++] = (byte) (l >> 8); vals[writevalpos++] = (byte) (l ); }
Encode a float value and write it to the end of the byte-encoding array
Params:
  • f – the float value to be written
/** * Encode a float value and write it to the end of the byte-encoding array * * @param f the float value to be written */
public void putFloat(float f) { putInt(Float.floatToIntBits(f)); }
Encode a double value and write it to the end of the byte-encoding array
Params:
  • d – the double value to be written
/** * Encode a double value and write it to the end of the byte-encoding array * * @param d the double value to be written */
public void putDouble(double d) { putLong(Double.doubleToLongBits(d)); }
Write an Object to the end of the object array
Params:
  • o – the Object to be written
/** * Write an {@code Object} to the end of the object array * * @param o the {@code Object} to be written */
public void putObject(Object o) { if (writeobjpos >= objs.length) { objs = Arrays.copyOf(objs, writeobjpos+MIN_OBJ_GROW); } objs[writeobjpos++] = o; }
Read a single byte from the byte-encoded stream, ignoring any read position, but honoring the current write position as a limit. The read and saved positions are not used or modified in any way by this method
Params:
  • i – the absolute byte location to return from the byte-encoding array
Returns:the byte stored at the indicated location in the byte array
/** * Read a single byte from the byte-encoded stream, ignoring any read * position, but honoring the current write position as a limit. * The read and saved positions are not used or modified in any way * by this method * * @param i the absolute byte location to return from the byte-encoding array * @return the byte stored at the indicated location in the byte array */
public byte peekByte(int i) { if (i >= writevalpos) { throw new BufferOverflowException(); } return vals[i]; }
Read a single Object from the object buffer, ignoring any read position, but honoring the current write position as a limit. The read and saved positions are not used or modified in any way by this method
Params:
  • i – the absolute index to return from the Object array
Returns:the Object stored at the indicated index
/** * Read a single {@code Object} from the object buffer, ignoring any read * position, but honoring the current write position as a limit. * The read and saved positions are not used or modified in any way * by this method * * @param i the absolute index to return from the {@code Object} array * @return the {@code Object} stored at the indicated index */
public Object peekObject(int i) { if (i >= writeobjpos) { throw new BufferOverflowException(); } return objs[i]; }
Decodes and returns a single boolean value from the current read position in the byte-encoded stream and bumps the read position past the decoded value.
Returns:the decoded boolean value
/** * Decodes and returns a single boolean value from the current read * position in the byte-encoded stream and bumps the read position * past the decoded value. * * @return the decoded boolean value */
public boolean getBoolean() { ensureReadCapacity(1); return vals[readvalpos++] != 0; }
Returns a single byte value from the current read position in the byte-encoded stream and bumps the read position past the returned value.
Returns:the decoded byte value
/** * Returns a single byte value from the current read * position in the byte-encoded stream and bumps the read position * past the returned value. * * @return the decoded byte value */
public byte getByte() { ensureReadCapacity(1); return vals[readvalpos++]; }
Decodes a single unsigned byte value from the current read position in the byte-encoded stream and returns the value cast to an int and bumps the read position past the decoded value.
Returns:the decoded unsigned byte value as an int
/** * Decodes a single unsigned byte value from the current read * position in the byte-encoded stream and returns the value cast to * an int and bumps the read position * past the decoded value. * * @return the decoded unsigned byte value as an int */
public int getUByte() { ensureReadCapacity(1); return vals[readvalpos++] & 0xff; }
Decodes and returns a single char value from the current read position in the byte-encoded stream and bumps the read position past the decoded value.
Returns:the decoded char value
/** * Decodes and returns a single char value from the current read * position in the byte-encoded stream and bumps the read position * past the decoded value. * * @return the decoded char value */
public char getChar() { ensureReadCapacity(2); int c = vals[readvalpos++]; c = (c << 8) | (vals[readvalpos++] & 0xff); return (char) c; }
Decodes and returns a single short value from the current read position in the byte-encoded stream and bumps the read position past the decoded value.
Returns:the decoded short value
/** * Decodes and returns a single short value from the current read * position in the byte-encoded stream and bumps the read position * past the decoded value. * * @return the decoded short value */
public short getShort() { ensureReadCapacity(2); int s = vals[readvalpos++]; s = (s << 8) | (vals[readvalpos++] & 0xff); return (short) s; }
Decodes and returns a single int value from the current read position in the byte-encoded stream and bumps the read position past the decoded value.
Returns:the decoded int value
/** * Decodes and returns a single int value from the current read * position in the byte-encoded stream and bumps the read position * past the decoded value. * * @return the decoded int value */
public int getInt() { ensureReadCapacity(4); int i = vals[readvalpos++]; i = (i << 8) | (vals[readvalpos++] & 0xff); i = (i << 8) | (vals[readvalpos++] & 0xff); i = (i << 8) | (vals[readvalpos++] & 0xff); return i; }
Decodes and returns a single long value from the current read position in the byte-encoded stream and bumps the read position past the decoded value.
Returns:the decoded long value
/** * Decodes and returns a single long value from the current read * position in the byte-encoded stream and bumps the read position * past the decoded value. * * @return the decoded long value */
public long getLong() { ensureReadCapacity(8); long l = vals[readvalpos++]; l = (l << 8) | (vals[readvalpos++] & 0xff); l = (l << 8) | (vals[readvalpos++] & 0xff); l = (l << 8) | (vals[readvalpos++] & 0xff); l = (l << 8) | (vals[readvalpos++] & 0xff); l = (l << 8) | (vals[readvalpos++] & 0xff); l = (l << 8) | (vals[readvalpos++] & 0xff); l = (l << 8) | (vals[readvalpos++] & 0xff); return l; }
Decodes and returns a single float value from the current read position in the byte-encoded stream and bumps the read position past the decoded value.
Returns:the decoded float value
/** * Decodes and returns a single float value from the current read * position in the byte-encoded stream and bumps the read position * past the decoded value. * * @return the decoded float value */
public float getFloat() { return Float.intBitsToFloat(getInt()); }
Decodes and returns a single double value from the current read position in the byte-encoded stream and bumps the read position past the decoded value.
Returns:the decoded double value
/** * Decodes and returns a single double value from the current read * position in the byte-encoded stream and bumps the read position * past the decoded value. * * @return the decoded double value */
public double getDouble() { return Double.longBitsToDouble(getLong()); }
Returns a single Object from the current object read position in the Object stream and bumps the read position past the returned value.
Returns:the Object read from the buffer
/** * Returns a single {@code Object} from the current object read * position in the {@code Object} stream and bumps the read position * past the returned value. * * @return the {@code Object} read from the buffer */
public Object getObject() { if (readobjpos >= objs.length) { throw new BufferOverflowException(); } return objs[readobjpos++]; } }