1// =================================================================================================
2// ADOBE SYSTEMS INCORPORATED
3// Copyright 2006 Adobe Systems Incorporated
4// All Rights Reserved
5//
6// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
7// of the Adobe license agreement accompanying it.
8// =================================================================================================
9
10
11
12package com.adobe.xmp.impl;
13
14import java.io.ByteArrayInputStream;
15import java.io.IOException;
16import java.io.InputStream;
17
18
19/**
20 * Byte buffer container including length of valid data.
21 *
22 * @since   11.10.2006
23 */
24public class ByteBuffer
25{
26	/** */
27	private byte[] buffer;
28	/** */
29	private int length;
30	/** */
31	private String encoding = null;
32
33
34	/**
35	 * @param initialCapacity the initial capacity for this buffer
36	 */
37	public ByteBuffer(int initialCapacity)
38	{
39		this.buffer = new byte[initialCapacity];
40		this.length = 0;
41	}
42
43
44	/**
45	 * @param buffer a byte array that will be wrapped with <code>ByteBuffer</code>.
46	 */
47	public ByteBuffer(byte[] buffer)
48	{
49		this.buffer = buffer;
50		this.length = buffer.length;
51	}
52
53
54	/**
55	 * @param buffer a byte array that will be wrapped with <code>ByteBuffer</code>.
56	 * @param length the length of valid bytes in the array
57	 */
58	public ByteBuffer(byte[] buffer, int length)
59	{
60		if (length > buffer.length)
61		{
62			throw new ArrayIndexOutOfBoundsException("Valid length exceeds the buffer length.");
63		}
64		this.buffer = buffer;
65		this.length = length;
66	}
67
68
69	/**
70	 * Loads the stream into a buffer.
71	 *
72	 * @param in an InputStream
73	 * @throws IOException If the stream cannot be read.
74	 */
75	public ByteBuffer(InputStream in) throws IOException
76	{
77		// load stream into buffer
78		int chunk = 16384;
79		this.length = 0;
80		this.buffer = new byte[chunk];
81
82		int read;
83		while ((read = in.read(this.buffer, this.length, chunk)) > 0)
84		{
85			this.length += read;
86			if (read == chunk)
87			{
88				ensureCapacity(length + chunk);
89			}
90			else
91			{
92				break;
93			}
94		}
95	}
96
97
98	/**
99	 * @param buffer a byte array that will be wrapped with <code>ByteBuffer</code>.
100	 * @param offset the offset of the provided buffer.
101	 * @param length the length of valid bytes in the array
102	 */
103	public ByteBuffer(byte[] buffer, int offset, int length)
104	{
105		if (length > buffer.length - offset)
106		{
107			throw new ArrayIndexOutOfBoundsException("Valid length exceeds the buffer length.");
108		}
109		this.buffer = new byte[length];
110		System.arraycopy(buffer, offset, this.buffer, 0, length);
111		this.length = length;
112	}
113
114
115	/**
116	 * @return Returns a byte stream that is limited to the valid amount of bytes.
117	 */
118	public InputStream getByteStream()
119	{
120		return new ByteArrayInputStream(buffer, 0, length);
121	}
122
123
124	/**
125	 * @return Returns the length, that means the number of valid bytes, of the buffer;
126	 * the inner byte array might be bigger than that.
127	 */
128	public int length()
129	{
130		return length;
131	}
132
133
134//	/**
135//	 * <em>Note:</em> Only the byte up to length are valid!
136//	 * @return Returns the inner byte buffer.
137//	 */
138//	public byte[] getBuffer()
139//	{
140//		return buffer;
141//	}
142
143
144	/**
145	 * @param index the index to retrieve the byte from
146	 * @return Returns a byte from the buffer
147	 */
148	public byte byteAt(int index)
149	{
150		if (index < length)
151		{
152			return buffer[index];
153		}
154		else
155		{
156			throw new IndexOutOfBoundsException("The index exceeds the valid buffer area");
157		}
158	}
159
160
161	/**
162	 * @param index the index to retrieve a byte as int or char.
163	 * @return Returns a byte from the buffer
164	 */
165	public int charAt(int index)
166	{
167		if (index < length)
168		{
169			return buffer[index] & 0xFF;
170		}
171		else
172		{
173			throw new IndexOutOfBoundsException("The index exceeds the valid buffer area");
174		}
175	}
176
177
178	/**
179	 * Appends a byte to the buffer.
180	 * @param b a byte
181	 */
182	public void append(byte b)
183	{
184		ensureCapacity(length + 1);
185		buffer[length++] = b;
186	}
187
188
189	/**
190	 * Appends a byte array or part of to the buffer.
191	 *
192	 * @param bytes a byte array
193	 * @param offset an offset with
194	 * @param len
195	 */
196	public void append(byte[] bytes, int offset, int len)
197	{
198		ensureCapacity(length + len);
199		System.arraycopy(bytes, offset, buffer, length, len);
200		length += len;
201	}
202
203
204	/**
205	 * Append a byte array to the buffer
206	 * @param bytes a byte array
207	 */
208	public void append(byte[] bytes)
209	{
210		append(bytes, 0, bytes.length);
211	}
212
213
214	/**
215	 * Append another buffer to this buffer.
216	 * @param anotherBuffer another <code>ByteBuffer</code>
217	 */
218	public void append(ByteBuffer anotherBuffer)
219	{
220		append(anotherBuffer.buffer, 0, anotherBuffer.length);
221	}
222
223
224	/**
225	 * Detects the encoding of the byte buffer, stores and returns it.
226	 * Only UTF-8, UTF-16LE/BE and UTF-32LE/BE are recognized.
227	 * <em>Note:</em> UTF-32 flavors are not supported by Java, the XML-parser will complain.
228	 *
229	 * @return Returns the encoding string.
230	 */
231	public String getEncoding()
232	{
233		if (encoding == null)
234		{
235			// needs four byte at maximum to determine encoding
236			if (length < 2)
237			{
238				// only one byte length must be UTF-8
239				encoding = "UTF-8";
240			}
241			else if (buffer[0] == 0)
242			{
243				// These cases are:
244				//   00 nn -- -- - Big endian UTF-16
245				//   00 00 00 nn - Big endian UTF-32
246				//   00 00 FE FF - Big endian UTF 32
247
248				if (length < 4  ||  buffer[1] != 0)
249				{
250					encoding =  "UTF-16BE";
251				}
252				else if ((buffer[2] & 0xFF) == 0xFE  &&  (buffer[3] & 0xFF) == 0xFF)
253				{
254					encoding = "UTF-32BE";
255				}
256				else
257				{
258					encoding = "UTF-32";
259				}
260			}
261			else if ((buffer[0] & 0xFF) < 0x80)
262			{
263				// These cases are:
264				//   nn mm -- -- - UTF-8, includes EF BB BF case
265				//   nn 00 -- -- - Little endian UTF-16
266
267				if (buffer[1] != 0)
268				{
269					encoding = "UTF-8";
270				}
271				else if (length < 4  ||  buffer[2] != 0)
272				{
273					encoding = "UTF-16LE";
274				}
275				else
276				{
277					encoding = "UTF-32LE";
278				}
279			}
280			else
281			{
282				// These cases are:
283				//   EF BB BF -- - UTF-8
284				//   FE FF -- -- - Big endian UTF-16
285				//   FF FE 00 00 - Little endian UTF-32
286				//   FF FE -- -- - Little endian UTF-16
287
288				if ((buffer[0] & 0xFF) == 0xEF)
289				{
290					encoding = "UTF-8";
291				}
292				else if ((buffer[0] & 0xFF) == 0xFE)
293				{
294					encoding = "UTF-16"; // in fact BE
295				}
296				else if (length < 4  ||  buffer[2] != 0)
297				{
298					encoding = "UTF-16"; // in fact LE
299				}
300				else
301				{
302					encoding = "UTF-32"; // in fact LE
303				}
304			}
305		}
306
307		return encoding;
308	}
309
310
311	/**
312	 * Ensures the requested capacity by increasing the buffer size when the
313	 * current length is exceeded.
314	 *
315	 * @param requestedLength requested new buffer length
316	 */
317	private void ensureCapacity(int requestedLength)
318	{
319		if (requestedLength > buffer.length)
320		{
321			byte[] oldBuf = buffer;
322			buffer = new byte[oldBuf.length * 2];
323			System.arraycopy(oldBuf, 0, buffer, 0, oldBuf.length);
324		}
325	}
326}