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