/* * 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 org.apache.mina.util.byteaccess; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.util.byteaccess.ByteArrayList.Node; /** * A ByteArray composed of other ByteArrays. Optimised for fast relative access * via cursors. Absolute access methods are provided, but may perform poorly. * * TODO: Write about laziness of cursor implementation - how movement doesn't * happen until actual get/put. * * @author <a href="http://mina.apache.org">Apache MINA Project</a> */ public final class CompositeByteArray extends AbstractByteArray { /** * Allows for efficient detection of component boundaries when using a cursor. * * TODO: Is this interface right? */ public interface CursorListener { /** * Called when the first component in the composite is entered by the cursor. */ public void enteredFirstComponent(int componentIndex, ByteArray component); /** * Called when the next component in the composite is entered by the cursor. */ public void enteredNextComponent(int componentIndex, ByteArray component); /** * Called when the previous component in the composite is entered by the cursor. */ public void enteredPreviousComponent(int componentIndex, ByteArray component); /** * Called when the last component in the composite is entered by the cursor. */ public void enteredLastComponent(int componentIndex, ByteArray component); } /** * Stores the underlying <code>ByteArray</code>s. */ private final ByteArrayList bas = new ByteArrayList(); /** * The byte order for data in the buffer */ private ByteOrder order; /** * May be used in <code>getSingleIoBuffer</code>. Optional. */ private final ByteArrayFactory byteArrayFactory; /** * Creates a new instance of CompositeByteArray. */ public CompositeByteArray() { this(null); } /** * * Creates a new instance of CompositeByteArray. * * @param byteArrayFactory * The factory used to create the ByteArray objects */ public CompositeByteArray(ByteArrayFactory byteArrayFactory) { this.byteArrayFactory = byteArrayFactory; } /** * Returns the first {@link ByteArray} in the list * * @return * The first ByteArray in the list */ public ByteArray getFirst() { if (bas.isEmpty()) { return null; } return bas.getFirst().getByteArray(); } /** * Adds the specified {@link ByteArray} to the first * position in the list * * @param ba * The ByteArray to add to the list */ public void addFirst(ByteArray ba) { addHook(ba); bas.addFirst(ba); } /** * Remove the first {@link ByteArray} in the list * * @return * The first ByteArray in the list */ public ByteArray removeFirst() { Node node = bas.removeFirst(); return node == null ? null : node.getByteArray(); } /** * Remove component <code>ByteArray</code>s to the given index (splitting * them if necessary) and returning them in a single <code>ByteArray</code>. * The caller is responsible for freeing the returned object. * * TODO: Document free behaviour more thoroughly. */ public ByteArray removeTo(int index) { if (index < first() || index > last()) { throw new IndexOutOfBoundsException(); } // Removing CompositeByteArray prefix = new CompositeByteArray(byteArrayFactory); int remaining = index - first(); while (remaining > 0) { ByteArray component = removeFirst(); if (component.last() <= remaining) { // Remove entire component. prefix.addLast(component); remaining -= component.last(); } else { // Remove part of component. Do this by removing entire // component then readding remaining bytes. // TODO: Consider using getIoBuffers(), as would avoid // performance problems for nested ComponentByteArrays. IoBuffer bb = component.getSingleIoBuffer(); // get the limit of the buffer int originalLimit = bb.limit(); // set the position to the beginning of the buffer bb.position(0); // set the limit of the buffer to what is remaining bb.limit(remaining); // create a new IoBuffer, sharing the data with 'bb' IoBuffer bb1 = bb.slice(); // set the position at the end of the buffer bb.position(remaining); // gets the limit of the buffer bb.limit(originalLimit); // create a new IoBuffer, sharing teh data with 'bb' IoBuffer bb2 = bb.slice(); // create a new ByteArray with 'bb1' ByteArray ba1 = new BufferByteArray(bb1) { @Override public void free() { // Do not free. This will get freed } }; // add the new ByteArray to the CompositeByteArray prefix.addLast(ba1); remaining -= ba1.last(); // final for anonymous inner class final ByteArray componentFinal = component; ByteArray ba2 = new BufferByteArray(bb2) { @Override public void free() { componentFinal.free(); } }; // add the new ByteArray to the CompositeByteArray addFirst(ba2); } } // return the CompositeByteArray return prefix; } /** * Adds the specified {@link ByteArray} to the end of the list * * @param ba * The ByteArray to add to the end of the list */ public void addLast(ByteArray ba) { addHook(ba); bas.addLast(ba); } /** * Removes the last {@link ByteArray} in the list * * @return * The ByteArray that was removed */ public ByteArray removeLast() { Node node = bas.removeLast(); return node == null ? null : node.getByteArray(); } /** * @inheritDoc */ public void free() { while (!bas.isEmpty()) { Node node = bas.getLast(); node.getByteArray().free(); bas.removeLast(); } } private void checkBounds(int index, int accessSize) { int lower = index; int upper = index + accessSize; if (lower < first()) { throw new IndexOutOfBoundsException("Index " + lower + " less than start " + first() + "."); } if (upper > last()) { throw new IndexOutOfBoundsException("Index " + upper + " greater than length " + last() + "."); } } /** * @inheritDoc */ public Iterable<IoBuffer> getIoBuffers() { if (bas.isEmpty()) { return Collections.emptyList(); } Collection<IoBuffer> result = new ArrayList<IoBuffer>(); Node node = bas.getFirst(); for (IoBuffer bb : node.getByteArray().getIoBuffers()) { result.add(bb); } while (node.hasNextNode()) { node = node.getNextNode(); for (IoBuffer bb : node.getByteArray().getIoBuffers()) { result.add(bb); } } return result; } /** * @inheritDoc */ public IoBuffer getSingleIoBuffer() { if (byteArrayFactory == null) { throw new IllegalStateException( "Can't get single buffer from CompositeByteArray unless it has a ByteArrayFactory."); } if (bas.isEmpty()) { ByteArray ba = byteArrayFactory.create(1); return ba.getSingleIoBuffer(); } int actualLength = last() - first(); { Node node = bas.getFirst(); ByteArray ba = node.getByteArray(); if (ba.last() == actualLength) { return ba.getSingleIoBuffer(); } } // Replace all nodes with a single node. ByteArray target = byteArrayFactory.create(actualLength); IoBuffer bb = target.getSingleIoBuffer(); Cursor cursor = cursor(); cursor.put(bb); // Copy all existing data into target IoBuffer. while (!bas.isEmpty()) { Node node = bas.getLast(); ByteArray component = node.getByteArray(); bas.removeLast(); component.free(); } bas.addLast(target); return bb; } /** * @inheritDoc */ public Cursor cursor() { return new CursorImpl(); } /** * @inheritDoc */ public Cursor cursor(int index) { return new CursorImpl(index); } /** * Get a cursor starting at index 0 (which may not be the start of the * array) and with the given listener. * * @param listener * Returns a new {@link org.apache.mina.util.byteaccess.ByteArray.Cursor} instance */ public Cursor cursor(CursorListener listener) { return new CursorImpl(listener); } /** * Get a cursor starting at the given index and with the given listener. * * @param index * The position of the array to start the Cursor at * @param listener * The listener for the Cursor that is returned */ public Cursor cursor(int index, CursorListener listener) { return new CursorImpl(index, listener); } /** * @inheritDoc */ public ByteArray slice(int index, int length) { return cursor(index).slice(length); } /** * @inheritDoc */ public byte get(int index) { return cursor(index).get(); } /** * @inheritDoc */ public void put(int index, byte b) { cursor(index).put(b); } /** * @inheritDoc */ public void get(int index, IoBuffer bb) { cursor(index).get(bb); } /** * @inheritDoc */ public void put(int index, IoBuffer bb) { cursor(index).put(bb); } /** * @inheritDoc */ public int first() { return bas.firstByte(); } /** * @inheritDoc */ public int last() { return bas.lastByte(); } /** * This method should be called prior to adding any component * <code>ByteArray</code> to a composite. * * @param ba * The component to add. */ private void addHook(ByteArray ba) { // Check first() is zero, otherwise cursor might not work. // TODO: Remove this restriction? if (ba.first() != 0) { throw new IllegalArgumentException("Cannot add byte array that doesn't start from 0: " + ba.first()); } // Check order. if (order == null) { order = ba.order(); } else if (!order.equals(ba.order())) { throw new IllegalArgumentException("Cannot add byte array with different byte order: " + ba.order()); } } /** * @inheritDoc */ public ByteOrder order() { if (order == null) { throw new IllegalStateException("Byte order not yet set."); } return order; } /** * @inheritDoc */ public void order(ByteOrder order) { if (order == null || !order.equals(this.order)) { this.order = order; if (!bas.isEmpty()) { for (Node node = bas.getFirst(); node.hasNextNode(); node = node.getNextNode()) { node.getByteArray().order(order); } } } } /** * @inheritDoc */ public short getShort(int index) { return cursor(index).getShort(); } /** * @inheritDoc */ public void putShort(int index, short s) { cursor(index).putShort(s); } /** * @inheritDoc */ public int getInt(int index) { return cursor(index).getInt(); } /** * @inheritDoc */ public void putInt(int index, int i) { cursor(index).putInt(i); } /** * @inheritDoc */ public long getLong(int index) { return cursor(index).getLong(); } /** * @inheritDoc */ public void putLong(int index, long l) { cursor(index).putLong(l); } /** * @inheritDoc */ public float getFloat(int index) { return cursor(index).getFloat(); } /** * @inheritDoc */ public void putFloat(int index, float f) { cursor(index).putFloat(f); } /** * @inheritDoc */ public double getDouble(int index) { return cursor(index).getDouble(); } /** * @inheritDoc */ public void putDouble(int index, double d) { cursor(index).putDouble(d); } /** * @inheritDoc */ public char getChar(int index) { return cursor(index).getChar(); } /** * @inheritDoc */ public void putChar(int index, char c) { cursor(index).putChar(c); } private class CursorImpl implements Cursor { private int index; private final CursorListener listener; private Node componentNode; // Index of start of current component. private int componentIndex; // Cursor within current component. private Cursor componentCursor; public CursorImpl() { this(0, null); } public CursorImpl(int index) { this(index, null); } public CursorImpl(CursorListener listener) { this(0, listener); } public CursorImpl(int index, CursorListener listener) { this.index = index; this.listener = listener; } /** * @inheritDoc */ public int getIndex() { return index; } /** * @inheritDoc */ public void setIndex(int index) { checkBounds(index, 0); this.index = index; } /** * @inheritDoc */ public void skip(int length) { setIndex(index + length); } /** * @inheritDoc */ public ByteArray slice(int length) { CompositeByteArray slice = new CompositeByteArray(byteArrayFactory); int remaining = length; while (remaining > 0) { prepareForAccess(remaining); int componentSliceSize = Math.min(remaining, componentCursor.getRemaining()); ByteArray componentSlice = componentCursor.slice(componentSliceSize); slice.addLast(componentSlice); index += componentSliceSize; remaining -= componentSliceSize; } return slice; } /** * @inheritDoc */ public ByteOrder order() { return CompositeByteArray.this.order(); } private void prepareForAccess(int accessSize) { // Handle removed node. Do this first so we can remove the reference // even if bounds checking fails. if (componentNode != null && componentNode.isRemoved()) { componentNode = null; componentCursor = null; } // Bounds checks checkBounds(index, accessSize); // Remember the current node so we can later tell whether or not we // need to create a new cursor. Node oldComponentNode = componentNode; // Handle missing node. if (componentNode == null) { int basMidpoint = (last() - first()) / 2 + first(); if (index <= basMidpoint) { // Search from the start. componentNode = bas.getFirst(); componentIndex = first(); if (listener != null) { listener.enteredFirstComponent(componentIndex, componentNode.getByteArray()); } } else { // Search from the end. componentNode = bas.getLast(); componentIndex = last() - componentNode.getByteArray().last(); if (listener != null) { listener.enteredLastComponent(componentIndex, componentNode.getByteArray()); } } } // Go back, if necessary. while (index < componentIndex) { componentNode = componentNode.getPreviousNode(); componentIndex -= componentNode.getByteArray().last(); if (listener != null) { listener.enteredPreviousComponent(componentIndex, componentNode.getByteArray()); } } // Go forward, if necessary. while (index >= componentIndex + componentNode.getByteArray().length()) { componentIndex += componentNode.getByteArray().last(); componentNode = componentNode.getNextNode(); if (listener != null) { listener.enteredNextComponent(componentIndex, componentNode.getByteArray()); } } // Update the cursor. int internalComponentIndex = index - componentIndex; if (componentNode == oldComponentNode) { // Move existing cursor. componentCursor.setIndex(internalComponentIndex); } else { // Create new cursor. componentCursor = componentNode.getByteArray().cursor(internalComponentIndex); } } /** * @inheritDoc */ public int getRemaining() { return last() - index + 1; } /** * @inheritDoc */ public boolean hasRemaining() { return getRemaining() > 0; } /** * @inheritDoc */ public byte get() { prepareForAccess(1); byte b = componentCursor.get(); index += 1; return b; } /** * @inheritDoc */ public void put(byte b) { prepareForAccess(1); componentCursor.put(b); index += 1; } /** * @inheritDoc */ public void get(IoBuffer bb) { while (bb.hasRemaining()) { int remainingBefore = bb.remaining(); prepareForAccess(remainingBefore); componentCursor.get(bb); int remainingAfter = bb.remaining(); // Advance index by actual amount got. int chunkSize = remainingBefore - remainingAfter; index += chunkSize; } } /** * @inheritDoc */ public void put(IoBuffer bb) { while (bb.hasRemaining()) { int remainingBefore = bb.remaining(); prepareForAccess(remainingBefore); componentCursor.put(bb); int remainingAfter = bb.remaining(); // Advance index by actual amount put. int chunkSize = remainingBefore - remainingAfter; index += chunkSize; } } /** * @inheritDoc */ public short getShort() { prepareForAccess(2); if (componentCursor.getRemaining() >= 4) { short s = componentCursor.getShort(); index += 2; return s; } else { byte b0 = get(); byte b1 = get(); if (order.equals(ByteOrder.BIG_ENDIAN)) { return (short) ((b0 << 8) | (b1 << 0)); } else { return (short) ((b1 << 8) | (b0 << 0)); } } } /** * @inheritDoc */ public void putShort(short s) { prepareForAccess(2); if (componentCursor.getRemaining() >= 4) { componentCursor.putShort(s); index += 2; } else { byte b0; byte b1; if (order.equals(ByteOrder.BIG_ENDIAN)) { b0 = (byte) ((s >> 8) & 0xff); b1 = (byte) ((s >> 0) & 0xff); } else { b0 = (byte) ((s >> 0) & 0xff); b1 = (byte) ((s >> 8) & 0xff); } put(b0); put(b1); } } /** * @inheritDoc */ public int getInt() { prepareForAccess(4); if (componentCursor.getRemaining() >= 4) { int i = componentCursor.getInt(); index += 4; return i; } else { byte b0 = get(); byte b1 = get(); byte b2 = get(); byte b3 = get(); if (order.equals(ByteOrder.BIG_ENDIAN)) { return ((b0 << 24) | (b1 << 16) | (b2 << 8) | (b3 << 0)); } else { return ((b3 << 24) | (b2 << 16) | (b1 << 8) | (b0 << 0)); } } } /** * @inheritDoc */ public void putInt(int i) { prepareForAccess(4); if (componentCursor.getRemaining() >= 4) { componentCursor.putInt(i); index += 4; } else { byte b0; byte b1; byte b2; byte b3; if (order.equals(ByteOrder.BIG_ENDIAN)) { b0 = (byte) ((i >> 24) & 0xff); b1 = (byte) ((i >> 16) & 0xff); b2 = (byte) ((i >> 8) & 0xff); b3 = (byte) ((i >> 0) & 0xff); } else { b0 = (byte) ((i >> 0) & 0xff); b1 = (byte) ((i >> 8) & 0xff); b2 = (byte) ((i >> 16) & 0xff); b3 = (byte) ((i >> 24) & 0xff); } put(b0); put(b1); put(b2); put(b3); } } /** * @inheritDoc */ public long getLong() { prepareForAccess(8); if (componentCursor.getRemaining() >= 4) { long l = componentCursor.getLong(); index += 8; return l; } else { byte b0 = get(); byte b1 = get(); byte b2 = get(); byte b3 = get(); byte b4 = get(); byte b5 = get(); byte b6 = get(); byte b7 = get(); if (order.equals(ByteOrder.BIG_ENDIAN)) { return ((b0 & 0xffL) << 56) | ((b1 & 0xffL) << 48) | ((b2 & 0xffL) << 40) | ((b3 & 0xffL) << 32) | ((b4 & 0xffL) << 24) | ((b5 & 0xffL) << 16) | ((b6 & 0xffL) << 8) | ((b7 & 0xffL) << 0); } else { return ((b7 & 0xffL) << 56) | ((b6 & 0xffL) << 48) | ((b5 & 0xffL) << 40) | ((b4 & 0xffL) << 32) | ((b3 & 0xffL) << 24) | ((b2 & 0xffL) << 16) | ((b1 & 0xffL) << 8) | ((b0 & 0xffL) << 0); } } } /** * @inheritDoc */ public void putLong(long l) { //TODO: see if there is some optimizing that can be done here prepareForAccess(8); if (componentCursor.getRemaining() >= 4) { componentCursor.putLong(l); index += 8; } else { byte b0; byte b1; byte b2; byte b3; byte b4; byte b5; byte b6; byte b7; if (order.equals(ByteOrder.BIG_ENDIAN)) { b0 = (byte) ((l >> 56) & 0xff); b1 = (byte) ((l >> 48) & 0xff); b2 = (byte) ((l >> 40) & 0xff); b3 = (byte) ((l >> 32) & 0xff); b4 = (byte) ((l >> 24) & 0xff); b5 = (byte) ((l >> 16) & 0xff); b6 = (byte) ((l >> 8) & 0xff); b7 = (byte) ((l >> 0) & 0xff); } else { b0 = (byte) ((l >> 0) & 0xff); b1 = (byte) ((l >> 8) & 0xff); b2 = (byte) ((l >> 16) & 0xff); b3 = (byte) ((l >> 24) & 0xff); b4 = (byte) ((l >> 32) & 0xff); b5 = (byte) ((l >> 40) & 0xff); b6 = (byte) ((l >> 48) & 0xff); b7 = (byte) ((l >> 56) & 0xff); } put(b0); put(b1); put(b2); put(b3); put(b4); put(b5); put(b6); put(b7); } } /** * @inheritDoc */ public float getFloat() { prepareForAccess(4); if (componentCursor.getRemaining() >= 4) { float f = componentCursor.getFloat(); index += 4; return f; } else { int i = getInt(); return Float.intBitsToFloat(i); } } /** * @inheritDoc */ public void putFloat(float f) { prepareForAccess(4); if (componentCursor.getRemaining() >= 4) { componentCursor.putFloat(f); index += 4; } else { int i = Float.floatToIntBits(f); putInt(i); } } /** * @inheritDoc */ public double getDouble() { prepareForAccess(8); if (componentCursor.getRemaining() >= 4) { double d = componentCursor.getDouble(); index += 8; return d; } else { long l = getLong(); return Double.longBitsToDouble(l); } } /** * @inheritDoc */ public void putDouble(double d) { prepareForAccess(8); if (componentCursor.getRemaining() >= 4) { componentCursor.putDouble(d); index += 8; } else { long l = Double.doubleToLongBits(d); putLong(l); } } /** * @inheritDoc */ public char getChar() { prepareForAccess(2); if (componentCursor.getRemaining() >= 4) { char c = componentCursor.getChar(); index += 2; return c; } else { byte b0 = get(); byte b1 = get(); if (order.equals(ByteOrder.BIG_ENDIAN)) { return (char) ((b0 << 8) | (b1 << 0)); } else { return (char) ((b1 << 8) | (b0 << 0)); } } } /** * @inheritDoc */ public void putChar(char c) { prepareForAccess(2); if (componentCursor.getRemaining() >= 4) { componentCursor.putChar(c); index += 2; } else { byte b0; byte b1; if (order.equals(ByteOrder.BIG_ENDIAN)) { b0 = (byte) ((c >> 8) & 0xff); b1 = (byte) ((c >> 0) & 0xff); } else { b0 = (byte) ((c >> 0) & 0xff); b1 = (byte) ((c >> 8) & 0xff); } put(b0); put(b1); } } } }