FileDocCategorySizeDatePackage
TextEncodedStringSizeTerminated.javaAPI DocJaudiotagger 2.0.414446Mon Sep 26 13:06:22 BST 2011org.jaudiotagger.tag.datatype

TextEncodedStringSizeTerminated

public class TextEncodedStringSizeTerminated extends AbstractString
Represents a String which is not delimited by null character.

This type of String will usually only be used when it is the last field within a frame, when reading the remainder of the byte array will be read, when writing the frame will be accommodate the required size for the String. The String will be encoded based upon the text encoding of the frame that it belongs to.

All TextInformation frames support multiple strings, stored as a null separated list, where null is represented by the termination code for the character encoding. This functionality is only officially support in ID3v24. Most applications will ignore any but the first value, but some such as Foobar 2000 will decode them properly iTunes write null terminators characters after the String even though it only writes a single value.

Fields Summary
Constructors Summary
public TextEncodedStringSizeTerminated(String identifier, org.jaudiotagger.tag.id3.AbstractTagFrameBody frameBody)
Creates a new empty TextEncodedStringSizeTerminated datatype.

param
identifier identifies the frame type
param
frameBody

        super(identifier, frameBody);
    
public TextEncodedStringSizeTerminated(TextEncodedStringSizeTerminated object)
Copy constructor

param
object

        super(object);
    
Methods Summary
public voidaddValue(java.lang.String value)
Add an additional String to the current String value

param
value

        setValue(this.value + "\u0000" + value);
    
private voidcheckTrailingNull(java.util.List values, java.lang.String stringValue)
Because nulls are stripped we need to check if not removing trailing nulls whether the original value ended with a null and if so add it back in.

param
values
param
stringValue

        if(!TagOptionSingleton.getInstance().isRemoveTrailingTerminatorOnWrite())
        {
            if (stringValue.length() > 0 && stringValue.charAt(stringValue.length() - 1) == '\0")
            {
                String lastVal = values.get(values.size() - 1);
                String newLastVal = lastVal + '\0";
                values.set(values.size() - 1,newLastVal);
            }
        }
    
public booleanequals(java.lang.Object obj)

        if(this==obj)
        {
            return true;
        }
        return obj instanceof TextEncodedStringSizeTerminated && super.equals(obj);
    
public intgetNumberOfValues()
How many values are held, each value is separated by a null terminator

return
number of values held, usually this will be one.

        return splitByNullSeperator(((String) value)).size();
    
protected java.lang.StringgetTextEncodingCharSet()
Get the text encoding being used.

The text encoding is defined by the frame body that the text field belongs to.

return
the text encoding charset

        byte textEncoding = this.getBody().getTextEncoding();
        String charSetName = TextEncoding.getInstanceOf().getValueForId(textEncoding);
        logger.finest("text encoding:" + textEncoding + " charset:" + charSetName);
        return charSetName;
    
public java.lang.StringgetValueAtIndex(int index)
Get the nth value

param
index
return
the nth value
throws
IndexOutOfBoundsException if value does not exist

        //Split String into separate components
        List values = splitByNullSeperator((String) value);
        return (String) values.get(index);
    
public java.lang.StringgetValueWithoutTrailingNull()
Get value(s) whilst removing any trailing nulls

return

        List<String> values = splitByNullSeperator((String) value);
        StringBuffer sb = new StringBuffer();
        for(int i=0;i<values.size();i++)
        {
            if(i!=0)
            {
                sb.append("\u0000");
            }
            sb.append(values.get(i));
        }
        return sb.toString();
    
public java.util.ListgetValues()

return
list of all values

        return splitByNullSeperator((String) value);
    
public voidreadByteArray(byte[] arr, int offset)
Read a 'n' bytes from buffer into a String where n is the framesize - offset so thefore cannot use this if there are other objects after it because it has no delimiter.

Must take into account the text encoding defined in the Encoding Object ID3 Text Frames often allow multiple strings seperated by the null char appropriate for the encoding.

param
arr this is the buffer for the frame
param
offset this is where to start reading in the buffer for this field
throws
NullPointerException
throws
IndexOutOfBoundsException

        logger.finest("Reading from array from offset:" + offset);

        //Get the Specified Decoder
        String charSetName = getTextEncodingCharSet();
        CharsetDecoder decoder = Charset.forName(charSetName).newDecoder();
        decoder.reset();

        //Decode sliced inBuffer
        ByteBuffer inBuffer;
        if(TagOptionSingleton.getInstance().isAndroid())
        {
           //#302 [dallen] truncating array manually since the decoder.decode() does not honor the offset in the in buffer
           byte[] truncArr = new byte[arr.length - offset];
           System.arraycopy(arr, offset, truncArr, 0, truncArr.length);
           inBuffer = ByteBuffer.wrap(truncArr);
        }
        else
        {
           inBuffer = ByteBuffer.wrap(arr, offset, arr.length - offset).slice();
        }

        CharBuffer outBuffer = CharBuffer.allocate(arr.length - offset);
        CoderResult coderResult = decoder.decode(inBuffer, outBuffer, true);
        if (coderResult.isError())
        {
            logger.warning("Decoding error:" + coderResult.toString());
        }
        decoder.flush(outBuffer);
        outBuffer.flip();

        //If using UTF16 with BOM we then search through the text removing any BOMs that could exist
        //for multiple values, BOM could be Big Endian or Little Endian
        if (charSetName.equals(TextEncoding.CHARSET_UTF_16))
        {
            value = outBuffer.toString().replace("\ufeff","").replace("\ufffe","");
        }
        else
        {
            value = outBuffer.toString();
        }
        //SetSize, important this is correct for finding the next datatype
        setSize(arr.length - offset);
        logger.config("Read SizeTerminatedString:" + value + " size:" + size);

    
public static java.util.ListsplitByNullSeperator(java.lang.String value)
Split the values separated by null character

param
value the raw value
return
list of values, guaranteed to be at least one value

        String[] valuesarray = value.split("\\u0000");
        List<String> values = Arrays.asList(valuesarray);
        //Read only list so if empty have to create new list
        if (values.size() == 0)
        {
            values = new ArrayList<String>(1);
            values.add("");
        }
        return values;
    
private voidstripTrailingNull()
Removing trailing null from end of String, this should be there but some applications continue to write this unnecessary null char.

        if (TagOptionSingleton.getInstance().isRemoveTrailingTerminatorOnWrite())
        {
            String stringValue = (String) value;
            if (stringValue.length() > 0)
            {
                if (stringValue.charAt(stringValue.length() - 1) == '\0")
                {
                    stringValue = (stringValue).substring(0, stringValue.length() - 1);
                    value = stringValue;
                }
            }
        }
    
public byte[]writeByteArray()
Write String into byte array

It will remove a trailing null terminator if exists if the option RemoveTrailingTerminatorOnWrite has been set.

return
the data as a byte array in format to write to file

        byte[] data;
        //Try and write to buffer using the CharSet defined by getTextEncodingCharSet()
        try
        {
            
            stripTrailingNull();

            //Special Handling because there is no UTF16 BOM LE charset
            String stringValue   = (String)value;
            String charSetName   = getTextEncodingCharSet();
            String actualCharSet = null;
            if (charSetName.equals(TextEncoding.CHARSET_UTF_16))
            {
                if(TagOptionSingleton.getInstance().isEncodeUTF16BomAsLittleEndian())
                {
                    actualCharSet = TextEncoding.CHARSET_UTF_16_LE_ENCODING_FORMAT;
                }
                else
                {
                    actualCharSet = TextEncoding.CHARSET_UTF_16_BE_ENCODING_FORMAT;
                }
            }

            //Ensure large enough for any encoding
            ByteBuffer outputBuffer = ByteBuffer.allocate((stringValue.length() + 3)* 3);

            //Ensure each string (if multiple values) is written with BOM by writing separately
            List<String> values = splitByNullSeperator(stringValue);
            checkTrailingNull(values, stringValue);

            //For each value
            for(int i=0;i<values.size();i++)
            {
                String next = values.get(i);
                if(actualCharSet!=null)
                {
                    if (actualCharSet.equals(TextEncoding.CHARSET_UTF_16_LE_ENCODING_FORMAT))
                    {
                        outputBuffer.put(writeStringUTF16LEBOM( next, i, values.size()));
                    }
                    else if (actualCharSet.equals(TextEncoding.CHARSET_UTF_16_BE_ENCODING_FORMAT))
                    {
                        outputBuffer.put(writeStringUTF16BEBOM( next, i, values.size()));
                    }
                }
                else
                {
                    outputBuffer.put(writeString( Charset.forName(charSetName).newEncoder(), next, i, values.size()));
                }
            }
            outputBuffer.flip();
            data = new byte[outputBuffer.limit()];
            outputBuffer.rewind();
            outputBuffer.get(data, 0, outputBuffer.limit());
            setSize(data.length);
        }
        //Should never happen so if does throw a RuntimeException
        catch (CharacterCodingException ce)
        {
            logger.severe(ce.getMessage());
            throw new RuntimeException(ce);
        }
        return data;
    
private java.nio.ByteBufferwriteString(java.nio.charset.CharsetEncoder encoder, java.lang.String next, int i, int noOfValues)
Write String using specified encoding When this is called multiple times, all but the last value has a trailing null

param
encoder
param
next
param
i
param
noOfValues
return
throws
CharacterCodingException


        ByteBuffer bb;
        if(( i + 1) == noOfValues )
        {
            bb = encoder.encode(CharBuffer.wrap(next));
        }
        else
        {
            bb = encoder.encode(CharBuffer.wrap(next + '\0"));
        }
        bb.rewind();
        return bb;
    
private java.nio.ByteBufferwriteStringUTF16BEBOM(java.lang.String next, int i, int noOfValues)
Write String in UTF-BEBOM format When this is called multiple times, all but the last value has a trailing null

param
next
param
i
param
noOfValues
return
throws
CharacterCodingException

        CharsetEncoder encoder = Charset.forName(TextEncoding.CHARSET_UTF_16_BE_ENCODING_FORMAT).newEncoder();
        ByteBuffer bb = null;
        //Add BOM
        if(( i + 1)==noOfValues)
        {
            bb = encoder.encode(CharBuffer.wrap('\ufeff" + next ));
        }
        else
        {
            bb = encoder.encode(CharBuffer.wrap('\ufeff" + next + '\0"));
        }
        bb.rewind();
        return bb;
    
private java.nio.ByteBufferwriteStringUTF16LEBOM(java.lang.String next, int i, int noOfValues)
Write String in UTF-LEBOM format When this is called multiple times, all but the last value has a trailing null Remember we are using this charset because the charset that writes BOM does it the wrong way for us so we use this none and then manually add the BOM ourselves.

param
next
param
i
param
noOfValues
return
throws
CharacterCodingException

        CharsetEncoder encoder = Charset.forName(TextEncoding.CHARSET_UTF_16_LE_ENCODING_FORMAT).newEncoder();
        ByteBuffer bb = null;
        //Note remember LE BOM is ff fe but this is handled by encoder Unicode char is fe ff
        if(( i + 1)==noOfValues)
        {
            bb = encoder.encode(CharBuffer.wrap('\ufeff" + next ));
        }
        else
        {
            bb = encoder.encode(CharBuffer.wrap('\ufeff" + next + '\0"));
        }
        bb.rewind();
        return bb;