Coverage Report - org.jaudiotagger.tag.datatype.TextEncodedStringSizeTerminated
 
Classes in this File Line Coverage Branch Coverage Complexity
TextEncodedStringSizeTerminated
92%
57/62
86%
12/14
2
 
 1  
 package org.jaudiotagger.tag.datatype;
 2  
 
 3  
 import org.jaudiotagger.tag.InvalidDataTypeException;
 4  
 import org.jaudiotagger.tag.TagOptionSingleton;
 5  
 import org.jaudiotagger.tag.id3.AbstractTagFrameBody;
 6  
 import org.jaudiotagger.tag.id3.valuepair.TextEncoding;
 7  
 
 8  
 import java.nio.ByteBuffer;
 9  
 import java.nio.CharBuffer;
 10  
 import java.nio.charset.*;
 11  
 import java.util.ArrayList;
 12  
 import java.util.Arrays;
 13  
 import java.util.List;
 14  
 
 15  
 /**
 16  
  * Represents a String which is not delimited by null character.
 17  
  * <p/>
 18  
  * This type of String will usually only be used when it is the last field within a frame, when reading the remainder of
 19  
  * the byte array will be read, when writing the frame will be accomodate the required size for the String. The String
 20  
  * will be encoded based upon the text encoding of the frame that it belongs to.
 21  
  * <p/>
 22  
  * All TextInformation frames support multiple strings, stored as a null separated list, where null is represented by
 23  
  * the termination code for the character encoding. This functionality is only officially support in ID3v24.  Itunes
 24  
  * write null terminators characters even though only writes a single value.
 25  
  */
 26  
 public class TextEncodedStringSizeTerminated extends AbstractString
 27  
 {
 28  
 
 29  
     /**
 30  
      * Creates a new empty TextEncodedStringSizeTerminated datatype.
 31  
      *
 32  
      * @param identifier identifies the frame type
 33  
      */
 34  
     public TextEncodedStringSizeTerminated(String identifier, AbstractTagFrameBody frameBody)
 35  
     {
 36  1854
         super(identifier, frameBody);
 37  1854
     }
 38  
 
 39  
     /**
 40  
      * Copy constructor
 41  
      *
 42  
      * @param object
 43  
      */
 44  
     public TextEncodedStringSizeTerminated(TextEncodedStringSizeTerminated object)
 45  
     {
 46  1094
         super(object);
 47  1094
     }
 48  
 
 49  
     public boolean equals(Object obj)
 50  
     {
 51  1
         if (obj instanceof TextEncodedStringSizeTerminated == false)
 52  
         {
 53  0
             return false;
 54  
         }
 55  1
         return super.equals(obj);
 56  
     }
 57  
 
 58  
     /**
 59  
      * Read a 'n' bytes from buffer into a String where n is the framesize - offset
 60  
      * so thefore cannot use this if there are other objects after it because it has no
 61  
      * delimiter.
 62  
      * <p/>
 63  
      * Must take into account the text encoding defined in the Encoding Object
 64  
      * ID3 Text Frames often allow multiple strings seperated by the null char
 65  
      * appropriate for the encoding.
 66  
      *
 67  
      * @param arr    this is the buffer for the frame
 68  
      * @param offset this is where to start reading in the buffer for this field
 69  
      * @throws NullPointerException
 70  
      * @throws IndexOutOfBoundsException
 71  
      */
 72  
     public void readByteArray(byte[] arr, int offset) throws InvalidDataTypeException
 73  
     {
 74  1397
         logger.finest("Reading from array from offset:" + offset);
 75  
 
 76  
         //Get the Specified Decoder
 77  1397
         String charSetName = getTextEncodingCharSet();
 78  1397
         CharsetDecoder decoder = Charset.forName(charSetName).newDecoder();
 79  
 
 80  
         //Decode sliced inBuffer
 81  1397
         ByteBuffer inBuffer = ByteBuffer.wrap(arr, offset, arr.length - offset).slice();
 82  1397
         CharBuffer outBuffer = CharBuffer.allocate(arr.length - offset);
 83  1397
         decoder.reset();
 84  1397
         CoderResult coderResult = decoder.decode(inBuffer, outBuffer, true);
 85  1397
         if (coderResult.isError())
 86  
         {
 87  0
             logger.warning("Decoding error:" + coderResult.toString());
 88  
         }
 89  1397
         decoder.flush(outBuffer);
 90  1397
         outBuffer.flip();
 91  
 
 92  
         //Store value
 93  1397
         value = outBuffer.toString();
 94  
 
 95  
         //SetSize, important this is correct for finding the next datatype
 96  1397
         setSize(arr.length - offset);
 97  1397
         logger.info("Read SizeTerminatedString:" + value + " size:" + size);
 98  1397
     }
 99  
 
 100  
     /**
 101  
      * Write String into byte array
 102  
      * <p/>
 103  
      * It will remove a trailing null terminator if exists if the option
 104  
      * RemoveTrailingTerminatorOnWrite has been set.
 105  
      *
 106  
      * @return the data as a byte array in format to write to file
 107  
      */
 108  
     public byte[] writeByteArray()
 109  
     {
 110  881
         byte[] data = null;
 111  
         //Try and write to buffer using the CharSet defined by getTextEncodingCharSet()
 112  
         try
 113  
         {
 114  881
             if (TagOptionSingleton.getInstance().isRemoveTrailingTerminatorOnWrite())
 115  
             {
 116  856
                 String stringValue = (String) value;
 117  856
                 if (stringValue.length() > 0)
 118  
                 {
 119  820
                     if (stringValue.charAt(stringValue.length() - 1) == '\0')
 120  
                     {
 121  110
                         stringValue = (stringValue).substring(0, stringValue.length() - 1);
 122  110
                         value = stringValue;
 123  
                     }
 124  
                 }
 125  
             }
 126  
 
 127  881
             String charSetName = getTextEncodingCharSet();
 128  881
             if (charSetName.equals(TextEncoding.CHARSET_UTF_16))
 129  
             {
 130  154
                 charSetName = TextEncoding.CHARSET_UTF_16_ENCODING_FORMAT;
 131  154
                 CharsetEncoder encoder = Charset.forName(charSetName).newEncoder();
 132  
                 //Note remember LE BOM is ff fe but tis is handled by encoder Unicode char is fe ff
 133  154
                 ByteBuffer bb = encoder.encode(CharBuffer.wrap('\ufeff' + (String) value));
 134  154
                 data = new byte[bb.limit()];
 135  154
                 bb.get(data, 0, bb.limit());
 136  
 
 137  154
             }
 138  
             else
 139  
             {
 140  727
                 CharsetEncoder encoder = Charset.forName(charSetName).newEncoder();
 141  727
                 ByteBuffer bb = encoder.encode(CharBuffer.wrap((String) value));
 142  727
                 data = new byte[bb.limit()];
 143  727
                 bb.get(data, 0, bb.limit());
 144  
             }
 145  
         }
 146  
         //Should never happen so if does throw a RuntimeException
 147  0
         catch (CharacterCodingException ce)
 148  
         {
 149  0
             logger.severe(ce.getMessage());
 150  0
             throw new RuntimeException(ce);
 151  881
         }
 152  881
         setSize(data.length);
 153  881
         return data;
 154  
     }
 155  
 
 156  
     /**
 157  
      * Get the text encoding being used.
 158  
      * <p/>
 159  
      * The text encoding is defined by the frame body that the text field belongs to.
 160  
      *
 161  
      * @return the text encoding charset
 162  
      */
 163  
     protected String getTextEncodingCharSet()
 164  
     {
 165  2187
         byte textEncoding = this.getBody().getTextEncoding();
 166  2187
         String charSetName = TextEncoding.getInstanceOf().getValueForId(textEncoding);
 167  2187
         logger.finest("text encoding:" + textEncoding + " charset:" + charSetName);
 168  2187
         return charSetName;
 169  
     }
 170  
 
 171  
     /**
 172  
      * Split the values seperated by null character
 173  
      *
 174  
      * @param value the raw value
 175  
      * @return list of values, guaranteed to be at least one value
 176  
      */
 177  
     private static List splitByNullSeperator(String value)
 178  
     {
 179  185
         String[] valuesarray = value.split("\\u0000");
 180  185
         List values = Arrays.asList(valuesarray);
 181  
         //Read only list so if empty have to create new list
 182  185
         if (values.size() == 0)
 183  
         {
 184  1
             values = new ArrayList(1);
 185  1
             values.add("");
 186  
         }
 187  185
         return values;
 188  
     }
 189  
 
 190  
     /**
 191  
      * Add an additional String to the current String value
 192  
      *
 193  
      * @param value
 194  
      */
 195  
     public void addValue(String value)
 196  
     {
 197  1
         setValue(this.value + "\u0000" + value);
 198  1
     }
 199  
 
 200  
     /**
 201  
      * How many values are held, each value is seperated by a null terminator
 202  
      *
 203  
      * @return number of values held, usually this will be one.
 204  
      */
 205  
     public int getNumberOfValues()
 206  
     {
 207  4
         return splitByNullSeperator(((String) value)).size();
 208  
     }
 209  
 
 210  
     /**
 211  
      * Get the nth value
 212  
      *
 213  
      * @param index
 214  
      * @return the nth value
 215  
      * @throws IndexOutOfBoundsException if value does not exist
 216  
      */
 217  
     public String getValueAtIndex(int index)
 218  
     {
 219  
         //Split String into seperate components
 220  181
         List values = splitByNullSeperator((String) value);
 221  181
         return (String) values.get(index);
 222  
     }
 223  
 }