// ================================================================================================= // ADOBE SYSTEMS INCORPORATED // Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms // of the Adobe license agreement accompanying it. // ================================================================================================= package com.adobe.xmp.impl; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; /** * Byte buffer container including length of valid data. * * @since 11.10.2006 */ public class ByteBuffer { /** */ private byte[] buffer; /** */ private int length; /** */ private String encoding = null; /** * @param initialCapacity the initial capacity for this buffer */ public ByteBuffer(int initialCapacity) { this.buffer = new byte[initialCapacity]; this.length = 0; } /** * @param buffer a byte array that will be wrapped with ByteBuffer. */ public ByteBuffer(byte[] buffer) { this.buffer = buffer; this.length = buffer.length; } /** * @param buffer a byte array that will be wrapped with ByteBuffer. * @param length the length of valid bytes in the array */ public ByteBuffer(byte[] buffer, int length) { if (length > buffer.length) { throw new ArrayIndexOutOfBoundsException("Valid length exceeds the buffer length."); } this.buffer = buffer; this.length = length; } /** * Loads the stream into a buffer. * * @param in an InputStream * @throws IOException If the stream cannot be read. */ public ByteBuffer(InputStream in) throws IOException { // load stream into buffer int chunk = 16384; this.length = 0; this.buffer = new byte[chunk]; int read; while ((read = in.read(this.buffer, this.length, chunk)) > 0) { this.length += read; if (read == chunk) { ensureCapacity(length + chunk); } else { break; } } } /** * @param buffer a byte array that will be wrapped with ByteBuffer. * @param offset the offset of the provided buffer. * @param length the length of valid bytes in the array */ public ByteBuffer(byte[] buffer, int offset, int length) { if (length > buffer.length - offset) { throw new ArrayIndexOutOfBoundsException("Valid length exceeds the buffer length."); } this.buffer = new byte[length]; System.arraycopy(buffer, offset, this.buffer, 0, length); this.length = length; } /** * @return Returns a byte stream that is limited to the valid amount of bytes. */ public InputStream getByteStream() { return new ByteArrayInputStream(buffer, 0, length); } /** * @return Returns the length, that means the number of valid bytes, of the buffer; * the inner byte array might be bigger than that. */ public int length() { return length; } // /** // * Note: Only the byte up to length are valid! // * @return Returns the inner byte buffer. // */ // public byte[] getBuffer() // { // return buffer; // } /** * @param index the index to retrieve the byte from * @return Returns a byte from the buffer */ public byte byteAt(int index) { if (index < length) { return buffer[index]; } else { throw new IndexOutOfBoundsException("The index exceeds the valid buffer area"); } } /** * @param index the index to retrieve a byte as int or char. * @return Returns a byte from the buffer */ public int charAt(int index) { if (index < length) { return buffer[index] & 0xFF; } else { throw new IndexOutOfBoundsException("The index exceeds the valid buffer area"); } } /** * Appends a byte to the buffer. * @param b a byte */ public void append(byte b) { ensureCapacity(length + 1); buffer[length++] = b; } /** * Appends a byte array or part of to the buffer. * * @param bytes a byte array * @param offset an offset with * @param len */ public void append(byte[] bytes, int offset, int len) { ensureCapacity(length + len); System.arraycopy(bytes, offset, buffer, length, len); length += len; } /** * Append a byte array to the buffer * @param bytes a byte array */ public void append(byte[] bytes) { append(bytes, 0, bytes.length); } /** * Append another buffer to this buffer. * @param anotherBuffer another ByteBuffer */ public void append(ByteBuffer anotherBuffer) { append(anotherBuffer.buffer, 0, anotherBuffer.length); } /** * Detects the encoding of the byte buffer, stores and returns it. * Only UTF-8, UTF-16LE/BE and UTF-32LE/BE are recognized. * Note: UTF-32 flavors are not supported by Java, the XML-parser will complain. * * @return Returns the encoding string. */ public String getEncoding() { if (encoding == null) { // needs four byte at maximum to determine encoding if (length < 2) { // only one byte length must be UTF-8 encoding = "UTF-8"; } else if (buffer[0] == 0) { // These cases are: // 00 nn -- -- - Big endian UTF-16 // 00 00 00 nn - Big endian UTF-32 // 00 00 FE FF - Big endian UTF 32 if (length < 4 || buffer[1] != 0) { encoding = "UTF-16BE"; } else if ((buffer[2] & 0xFF) == 0xFE && (buffer[3] & 0xFF) == 0xFF) { encoding = "UTF-32BE"; } else { encoding = "UTF-32"; } } else if ((buffer[0] & 0xFF) < 0x80) { // These cases are: // nn mm -- -- - UTF-8, includes EF BB BF case // nn 00 -- -- - Little endian UTF-16 if (buffer[1] != 0) { encoding = "UTF-8"; } else if (length < 4 || buffer[2] != 0) { encoding = "UTF-16LE"; } else { encoding = "UTF-32LE"; } } else { // These cases are: // EF BB BF -- - UTF-8 // FE FF -- -- - Big endian UTF-16 // FF FE 00 00 - Little endian UTF-32 // FF FE -- -- - Little endian UTF-16 if ((buffer[0] & 0xFF) == 0xEF) { encoding = "UTF-8"; } else if ((buffer[0] & 0xFF) == 0xFE) { encoding = "UTF-16"; // in fact BE } else if (length < 4 || buffer[2] != 0) { encoding = "UTF-16"; // in fact LE } else { encoding = "UTF-32"; // in fact LE } } } return encoding; } /** * Ensures the requested capacity by increasing the buffer size when the * current length is exceeded. * * @param requestedLength requested new buffer length */ private void ensureCapacity(int requestedLength) { if (requestedLength > buffer.length) { byte[] oldBuf = buffer; buffer = new byte[oldBuf.length * 2]; System.arraycopy(oldBuf, 0, buffer, 0, oldBuf.length); } } }