1
package org.jaudiotagger.tag.datatype;
3
import org.jaudiotagger.tag.InvalidDataTypeException;
4
import org.jaudiotagger.tag.id3.AbstractTagFrameBody;
5
import org.jaudiotagger.tag.id3.valuepair.TextEncoding;
7
import java.nio.ByteBuffer;
8
import java.nio.CharBuffer;
9
import java.nio.charset.*;
12
* Represents a String whose size is determined by finding of a null character at the end of the String.
14
* The String itself might be of length zero (i.e just consist of the null character). The String will be encoded based
15
* upon the text encoding of the frame that it belongs to.
17
public class TextEncodedStringNullTerminated extends AbstractString
20
* Creates a new TextEncodedStringNullTerminated datatype.
22
* @param identifier identifies the frame type
24
public TextEncodedStringNullTerminated(String identifier, AbstractTagFrameBody frameBody)
26
super(identifier, frameBody);
30
* Creates a new TextEncodedStringNullTerminated datatype, with value
36
public TextEncodedStringNullTerminated(String identifier, AbstractTagFrameBody frameBody, String value)
38
super(identifier, frameBody, value);
41
public TextEncodedStringNullTerminated(TextEncodedStringNullTerminated object)
46
public boolean equals(Object obj)
48
if (obj instanceof TextEncodedStringNullTerminated == false)
52
return super.equals(obj);
56
* Read a string from buffer upto null character (if exists)
58
* Must take into account the text encoding defined in the Encoding Object
59
* ID3 Text Frames often allow multiple strings seperated by the null char
60
* appropriate for the encoding.
62
* @param arr this is the buffer for the frame
63
* @param offset this is where to start reading in the buffer for this field
65
public void readByteArray(byte[] arr, int offset) throws InvalidDataTypeException
69
logger.finer("Reading from array starting from offset:" + offset);
72
//Get the Specified Decoder
73
String charSetName = getTextEncodingCharSet();
74
CharsetDecoder decoder = Charset.forName(charSetName).newDecoder();
76
//We only want to load up to null terminator, data after this is part of different
77
//field and it may not be possible to decode it so do the check before we do
78
//do the decoding,encoding dependent.
79
ByteBuffer buffer = ByteBuffer.wrap(arr, offset, arr.length - offset);
82
//Latin-1 and UTF-8 strings are terminated by a single-byte null,
83
//while UTF-16 and its variants need two bytes for the null terminator.
84
final boolean nullIsOneByte = (charSetName.equals(TextEncoding.CHARSET_ISO_8859_1) || charSetName.equals(TextEncoding.CHARSET_UTF_8));
86
boolean isNullTerminatorFound = false;
87
while (buffer.hasRemaining())
89
byte nextByte = buffer.get();
96
endPosition = buffer.position() - 1;
97
logger.finest("Null terminator found starting at:" + endPosition);
99
isNullTerminatorFound = true;
104
// Looking for two-byte null
105
if (buffer.hasRemaining())
107
nextByte = buffer.get();
108
if (nextByte == 0x00)
112
endPosition = buffer.position() - 2;
113
logger.finest("UTF16:Null terminator found starting at:" + endPosition);
114
isNullTerminatorFound = true;
119
//Nothing to do, we have checked 2nd value of pair it was not a null terminator
120
//so will just start looking again in next invocation of loop
127
endPosition = buffer.position() - 1;
128
logger.warning("UTF16:Should be two null terminator marks but only found one starting at:" + endPosition);
130
isNullTerminatorFound = true;
137
//If UTF16, we should only be looking on 2 byte boundaries
140
if (buffer.hasRemaining())
148
if (isNullTerminatorFound == false)
150
throw new InvalidDataTypeException("Unable to find null terminated string");
154
logger.finest("End Position is:" + endPosition + "Offset:" + offset);
156
//Set Size so offset is ready for next field (includes the null terminator)
157
size = endPosition - offset;
165
//Decode buffer if runs into problems should throw exception which we
166
//catch and then set value to empty string. (We don't read the null terminator
167
//because we dont want to display this)
168
bufferSize = endPosition - offset;
169
logger.finest("Text size is:" + bufferSize);
176
//Decode sliced inBuffer
177
ByteBuffer inBuffer = ByteBuffer.wrap(arr, offset, bufferSize).slice();
178
CharBuffer outBuffer = CharBuffer.allocate(bufferSize);
180
CoderResult coderResult = decoder.decode(inBuffer, outBuffer, true);
181
if (coderResult.isError())
183
logger.warning("Problem decoding text encoded null terminated string:" + coderResult.toString());
185
decoder.flush(outBuffer);
187
value = outBuffer.toString();
189
//Set Size so offset is ready for next field (includes the null terminator)
190
logger.info("Read NullTerminatedString:" + value + " size inc terminator:" + size);
194
* Write String into byte array, adding a null character to the end of the String
196
* @return the data as a byte array in format to write to file
198
public byte[] writeByteArray()
200
logger.info("Writing NullTerminatedString." + value);
202
//Write to buffer using the CharSet defined by getTextEncodingCharSet()
203
//Add a null terminator which will be encoded based on encoding.
206
String charSetName = getTextEncodingCharSet();
207
if (charSetName.equals(TextEncoding.CHARSET_UTF_16))
209
charSetName = TextEncoding.CHARSET_UTF_16_ENCODING_FORMAT;
210
CharsetEncoder encoder = Charset.forName(charSetName).newEncoder();
211
//Note remember LE BOM is ff fe but tis is handled by encoder Unicode char is fe ff
212
ByteBuffer bb = encoder.encode(CharBuffer.wrap('\ufeff' + (String) value + '\0'));
213
data = new byte[bb.limit()];
214
bb.get(data, 0, bb.limit());
218
CharsetEncoder encoder = Charset.forName(charSetName).newEncoder();
219
ByteBuffer bb = encoder.encode(CharBuffer.wrap((String) value + '\0'));
220
data = new byte[bb.limit()];
221
bb.get(data, 0, bb.limit());
224
//Should never happen so if does throw a RuntimeException
225
catch (CharacterCodingException ce)
227
logger.severe(ce.getMessage());
228
throw new RuntimeException(ce);
230
setSize(data.length);
234
protected String getTextEncodingCharSet()
236
byte textEncoding = this.getBody().getTextEncoding();
237
String charSetName = TextEncoding.getInstanceOf().getValueForId(textEncoding);
238
logger.finest("text encoding:" + textEncoding + " charset:" + charSetName);