Coverage Report - org.jaudiotagger.tag.id3.ID3v23Frame
 
Classes in this File Line Coverage Branch Coverage Complexity
ID3v23Frame
70%
143/205
66%
42/64
3.069
ID3v23Frame$EncodingFlags
65%
15/23
67%
8/12
3.069
ID3v23Frame$StatusFlags
79%
26/33
83%
5/6
3.069
 
 1  
 /*
 2  
  *  MusicTag Copyright (C)2003,2004
 3  
  *
 4  
  *  This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
 5  
  *  General Public  License as published by the Free Software Foundation; either version 2.1 of the License,
 6  
  *  or (at your option) any later version.
 7  
  *
 8  
  *  This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 9  
  *  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 10  
  *  See the GNU Lesser General Public License for more details.
 11  
  *
 12  
  *  You should have received a copy of the GNU Lesser General Public License along with this library; if not,
 13  
  *  you can get a copy from http://www.opensource.org/licenses/lgpl-license.php or write to the Free Software
 14  
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 15  
  */
 16  
 package org.jaudiotagger.tag.id3;
 17  
 
 18  
 import org.jaudiotagger.FileConstants;
 19  
 import org.jaudiotagger.audio.generic.Utils;
 20  
 import org.jaudiotagger.audio.mp3.MP3File;
 21  
 import org.jaudiotagger.tag.EmptyFrameException;
 22  
 import org.jaudiotagger.tag.InvalidFrameException;
 23  
 import org.jaudiotagger.tag.InvalidFrameIdentifierException;
 24  
 import org.jaudiotagger.tag.id3.framebody.AbstractID3v2FrameBody;
 25  
 import org.jaudiotagger.tag.id3.framebody.FrameBodyDeprecated;
 26  
 import org.jaudiotagger.tag.id3.framebody.FrameBodyUnsupported;
 27  
 import org.jaudiotagger.tag.id3.framebody.ID3v23FrameBody;
 28  
 
 29  
 import java.io.ByteArrayOutputStream;
 30  
 import java.io.IOException;
 31  
 import java.nio.ByteBuffer;
 32  
 import java.util.regex.Matcher;
 33  
 import java.util.regex.Pattern;
 34  
 import java.util.zip.DataFormatException;
 35  
 import java.util.zip.Inflater;
 36  
 
 37  
 /**
 38  
  * Represents an ID3v2.3 frame.
 39  
  *
 40  
  * @author : Paul Taylor
 41  
  * @author : Eric Farng
 42  
  * @version $Id: ID3v23Frame.java,v 1.38 2008/11/12 16:41:39 paultaylor Exp $
 43  
  */
 44  
 public class ID3v23Frame extends AbstractID3v2Frame
 45  
 {
 46  1057
     Pattern validFrameIdentifier = Pattern.compile("[A-Z][0-9A-Z]{3}");
 47  
 
 48  
     protected static final int FRAME_ID_SIZE = 4;
 49  
     protected static final int FRAME_FLAGS_SIZE = 2;
 50  
     protected static final int FRAME_SIZE_SIZE = 4;
 51  
     protected static final int FRAME_COMPRESSION_UNCOMPRESSED_SIZE = 4;
 52  
     protected static final int FRAME_ENCRYPTION_INDICATOR_SIZE = 1;
 53  
     protected static final int FRAME_GROUPING_INDICATOR_SIZE = 1;
 54  
 
 55  
     protected static final int FRAME_HEADER_SIZE = FRAME_ID_SIZE + FRAME_SIZE_SIZE + FRAME_FLAGS_SIZE;
 56  
 
 57  
     /**
 58  
      * Creates a new ID3v23 Frame
 59  
      */
 60  
     public ID3v23Frame()
 61  0
     {
 62  0
     }
 63  
 
 64  
     /**
 65  
      * Creates a new ID3v23 Frame of type identifier.
 66  
      * <p/>
 67  
      * <p>An empty body of the correct type will be automatically created.
 68  
      * This constructor should be used when wish to create a new
 69  
      * frame from scratch using user data.
 70  
      */
 71  
     public ID3v23Frame(String identifier)
 72  
     {
 73  78
         super(identifier);
 74  78
         statusFlags = new StatusFlags();
 75  78
         encodingFlags = new EncodingFlags();
 76  78
     }
 77  
 
 78  
     /**
 79  
      * Copy Constructor
 80  
      * <p/>
 81  
      * Creates a new v23 frame  based on another v23 frame
 82  
      */
 83  
     public ID3v23Frame(ID3v23Frame frame)
 84  
     {
 85  0
         super(frame);
 86  0
         statusFlags = new StatusFlags(frame.getStatusFlags().getOriginalFlags());
 87  0
         encodingFlags = new EncodingFlags(frame.getEncodingFlags().getFlags());
 88  0
     }
 89  
 
 90  
     /**
 91  
      * Creates a new ID3v23Frame  based on another frame.
 92  
      *
 93  
      * @param frame
 94  
      */
 95  
     public ID3v23Frame(AbstractID3v2Frame frame) throws InvalidFrameException
 96  377
     {
 97  377
         logger.info("Creating frame from a frame of a different version");
 98  377
         if ((frame instanceof ID3v23Frame) && (frame instanceof ID3v24Frame == false))
 99  
         {
 100  0
             throw new UnsupportedOperationException("Copy Constructor not called. Please type cast the argument");
 101  
         }
 102  377
         if (frame instanceof ID3v24Frame)
 103  
         {
 104  138
             statusFlags = new StatusFlags((ID3v24Frame.StatusFlags) frame.getStatusFlags());
 105  138
             encodingFlags = new EncodingFlags(frame.getEncodingFlags().getFlags());
 106  
         }
 107  
 
 108  377
         if (frame instanceof ID3v24Frame)
 109  
         {
 110  138
             if (ID3Tags.isID3v24FrameIdentifier(frame.getIdentifier()))
 111  
             {
 112  
                 //Version between v4 and v3
 113  132
                 identifier = ID3Tags.convertFrameID24To23(frame.getIdentifier());
 114  132
                 if (identifier != null)
 115  
                 {
 116  126
                     logger.info("V4:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 117  126
                     this.frameBody = (AbstractTagFrameBody) ID3Tags.copyObject(frame.getBody());
 118  126
                     this.frameBody.setHeader(this);
 119  126
                     return;
 120  
                 }
 121  
                 else
 122  
                 {
 123  
                     //Is it a known v4 frame which needs forcing to v3 frame e.g. TDRC - TYER,TDAT
 124  6
                     identifier = ID3Tags.forceFrameID24To23(frame.getIdentifier());
 125  6
                     if (identifier != null)
 126  
                     {
 127  2
                         logger.info("V4:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 128  2
                         this.frameBody = this.readBody(identifier, (AbstractID3v2FrameBody) frame.getBody());
 129  2
                         this.frameBody.setHeader(this);
 130  2
                         return;
 131  
                     }
 132  
                     //It is a v24 frame that is not known and cannot be forced in v23 e.g TDRL,in which case
 133  
                     //we convert to a framebody unsupported by writing contents as a byte array and feeding
 134  
                     //it into FrameBodyUnsupported
 135  
                     else
 136  
                     {
 137  4
                         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 138  4
                         ((AbstractID3v2FrameBody) frame.getBody()).write(baos);
 139  
 
 140  4
                         identifier = frame.getIdentifier();
 141  4
                         this.frameBody = new FrameBodyUnsupported(identifier, baos.toByteArray());
 142  4
                         this.frameBody.setHeader(this);
 143  4
                         logger.info("V4:Orig id is:" + frame.getIdentifier() + ":New Id Unsupported is:" + identifier);
 144  4
                         return;
 145  
                     }
 146  
                 }
 147  
             }
 148  
             //Unknown Frame e.g NCON
 149  6
             else if (frame.getBody() instanceof FrameBodyUnsupported)
 150  
             {
 151  0
                 this.frameBody = new FrameBodyUnsupported((FrameBodyUnsupported) frame.getBody());
 152  0
                 this.frameBody.setHeader(this);
 153  0
                 identifier = frame.getIdentifier();
 154  0
                 logger.info("UNKNOWN:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 155  0
                 return;
 156  
             }
 157  
             // Deprecated frame for v24
 158  6
             else if (frame.getBody() instanceof FrameBodyDeprecated)
 159  
             {
 160  
                 //Was it valid for this tag version, if so try and reconstruct
 161  6
                 if (ID3Tags.isID3v23FrameIdentifier(frame.getIdentifier()))
 162  
                 {
 163  6
                     this.frameBody = ((FrameBodyDeprecated) frame.getBody()).getOriginalFrameBody();
 164  6
                     this.frameBody.setHeader(this);
 165  6
                     identifier = frame.getIdentifier();
 166  6
                     logger.info("DEPRECATED:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 167  
                 }
 168  
                 //or was it still deprecated, if so leave as is
 169  
                 else
 170  
                 {
 171  0
                     this.frameBody = new FrameBodyDeprecated((FrameBodyDeprecated) frame.getBody());
 172  0
                     this.frameBody.setHeader(this);
 173  0
                     identifier = frame.getIdentifier();
 174  0
                     logger.info("DEPRECATED:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 175  0
                     return;
 176  
                 }
 177  
             }
 178  
             // Unable to find a suitable framebody, this shold not happen
 179  
             else
 180  
             {
 181  0
                 logger.severe("Orig id is:" + frame.getIdentifier() + "Unable to create Frame Body");
 182  0
                 throw new InvalidFrameException("Orig id is:" + frame.getIdentifier() + "Unable to create Frame Body");
 183  
             }
 184  
         }
 185  239
         else if (frame instanceof ID3v22Frame)
 186  
         {
 187  239
             if (ID3Tags.isID3v22FrameIdentifier(frame.getIdentifier()))
 188  
             {
 189  236
                 identifier = ID3Tags.convertFrameID22To23(frame.getIdentifier());
 190  236
                 if (identifier != null)
 191  
                 {
 192  224
                     logger.info("V3:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 193  224
                     this.frameBody = (AbstractTagFrameBody) ID3Tags.copyObject(frame.getBody());
 194  224
                     this.frameBody.setHeader(this);
 195  224
                     return;
 196  
                 }
 197  
                 //Is it a known v2 frame which needs forcing to v23 frame e.g PIC - APIC
 198  12
                 else if (ID3Tags.isID3v22FrameIdentifier(frame.getIdentifier()))
 199  
                 {
 200  
                     //Force v2 to v3
 201  12
                     identifier = ID3Tags.forceFrameID22To23(frame.getIdentifier());
 202  12
                     if (identifier != null)
 203  
                     {
 204  12
                         logger.info("V22Orig id is:" + frame.getIdentifier() + "New id is:" + identifier);
 205  12
                         this.frameBody = this.readBody(identifier, (AbstractID3v2FrameBody) frame.getBody());
 206  12
                         this.frameBody.setHeader(this);
 207  12
                         return;
 208  
                     }
 209  
                     //No mechanism exists to convert it to a v23 frame
 210  
                     else
 211  
                     {
 212  0
                         this.frameBody = new FrameBodyDeprecated((AbstractID3v2FrameBody) frame.getBody());
 213  0
                         this.frameBody.setHeader(this);
 214  0
                         identifier = frame.getIdentifier();
 215  0
                         logger.info("Deprecated:V22:orig id id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 216  0
                         return;
 217  
                     }
 218  
                 }
 219  
             }
 220  
             // Unknown Frame e.g NCON
 221  
             else
 222  
             {
 223  3
                 this.frameBody = new FrameBodyUnsupported((FrameBodyUnsupported) frame.getBody());
 224  3
                 this.frameBody.setHeader(this);
 225  3
                 identifier = frame.getIdentifier();
 226  3
                 logger.info("UNKNOWN:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 227  3
                 return;
 228  
             }
 229  
         }
 230  
 
 231  6
         logger.warning("Frame is unknown version");
 232  6
     }
 233  
 
 234  
     /**
 235  
      * Creates a new ID3v23Frame datatype by reading from byteBuffer.
 236  
      *
 237  
      * @param byteBuffer to read from
 238  
      */
 239  
     public ID3v23Frame(ByteBuffer byteBuffer, String loggingFilename) throws InvalidFrameException
 240  602
     {
 241  602
         setLoggingFilename(loggingFilename);
 242  602
         read(byteBuffer);
 243  500
     }
 244  
 
 245  
     /**
 246  
      * Creates a new ID3v23Frame datatype by reading from byteBuffer.
 247  
      *
 248  
      * @param byteBuffer to read from
 249  
      * @deprecated use {@link #ID3v23Frame(ByteBuffer,String)} instead
 250  
      */
 251  
     public ID3v23Frame(ByteBuffer byteBuffer) throws InvalidFrameException
 252  
     {
 253  0
         this(byteBuffer, "");
 254  0
     }
 255  
 
 256  
     /**
 257  
      * Return size of frame
 258  
      *
 259  
      * @return int frame size
 260  
      */
 261  
     public int getSize()
 262  
     {
 263  1
         return frameBody.getSize() + ID3v23Frame.FRAME_HEADER_SIZE;
 264  
     }
 265  
 
 266  
     /**
 267  
      * Compare for equality
 268  
      * To be deemed equal obj must be a IDv23Frame with the same identifier
 269  
      * and the same flags.
 270  
      * containing the same body,datatype list ectera.
 271  
      * equals() method is made up from all the various components
 272  
      *
 273  
      * @param obj
 274  
      * @return if true if this object is equivalent to obj
 275  
      */
 276  
     public boolean equals(Object obj)
 277  
     {
 278  0
         if ((obj instanceof ID3v23Frame) == false)
 279  
         {
 280  0
             return false;
 281  
         }
 282  0
         ID3v23Frame object = (ID3v23Frame) obj;
 283  0
         if (this.statusFlags.getOriginalFlags() != object.statusFlags.getOriginalFlags())
 284  
         {
 285  0
             return false;
 286  
         }
 287  0
         if (this.encodingFlags.getFlags() != object.encodingFlags.getFlags())
 288  
         {
 289  0
             return false;
 290  
         }
 291  0
         return super.equals(obj);
 292  
     }
 293  
 
 294  
     /**
 295  
      * Read the frame from a bytebuffer
 296  
      *
 297  
      * @param byteBuffer buffer to read from
 298  
      */
 299  
     public void read(ByteBuffer byteBuffer) throws InvalidFrameException
 300  
     {
 301  602
         logger.info(getLoggingFilename() + ":Read Frame from byteBuffer");
 302  
 
 303  602
         if (byteBuffer.position() + FRAME_HEADER_SIZE >= byteBuffer.limit())
 304  
         {
 305  0
             logger.warning(getLoggingFilename() + ":No space to find another frame:");
 306  0
             throw new InvalidFrameException(getLoggingFilename() + ":No space to find another frame");
 307  
         }
 308  
 
 309  602
         byte[] buffer = new byte[FRAME_ID_SIZE];
 310  
 
 311  
         // Read the Frame Identifier
 312  602
         byteBuffer.get(buffer, 0, FRAME_ID_SIZE);
 313  602
         identifier = new String(buffer);
 314  
 
 315  
         // Is this a valid identifier?
 316  602
         if (isValidID3v2FrameIdentifier(identifier) == false)
 317  
         {
 318  99
             logger.info(getLoggingFilename() + ":Invalid identifier:" + identifier);
 319  99
             byteBuffer.position(byteBuffer.position() - (FRAME_ID_SIZE - 1));
 320  99
             throw new InvalidFrameIdentifierException(getLoggingFilename() + ":" + identifier + "is not a valid ID3v2.30 frame");
 321  
         }
 322  
         //Read the size field (as Big Endian Int - byte buffers always initlised to Big endian order)
 323  503
         frameSize = byteBuffer.getInt();
 324  503
         if (frameSize < 0)
 325  
         {
 326  0
             logger.warning(getLoggingFilename() + ":Invalid Frame Size:" + identifier);
 327  0
             throw new InvalidFrameException(identifier + " is invalid frame");
 328  
         }
 329  503
         else if (frameSize == 0)
 330  
         {
 331  1
             logger.warning(getLoggingFilename() + ":Empty Frame Size:" + identifier);
 332  
             //We dont process this frame or add to framemap becuase contains no useful information
 333  
             //Skip the two flag bytes so in correct position for subsequent frames
 334  1
             byteBuffer.get();
 335  1
             byteBuffer.get();
 336  1
             throw new EmptyFrameException(identifier + " is empty frame");
 337  
         }
 338  502
         else if (frameSize > byteBuffer.remaining())
 339  
         {
 340  0
             logger.warning(getLoggingFilename() + ":Invalid Frame size larger than size before mp3 audio:" + identifier);
 341  0
             throw new InvalidFrameException(identifier + " is invalid frame");
 342  
         }
 343  
 
 344  
         //Read the flag bytes
 345  502
         statusFlags = new StatusFlags(byteBuffer.get());
 346  502
         encodingFlags = new EncodingFlags(byteBuffer.get());
 347  
         String id;
 348  
 
 349  
         //If this identifier is a valid v24 identifier or easily converted to v24
 350  502
         id = ID3Tags.convertFrameID23To24(identifier);
 351  
 
 352  
         // Cant easily be converted to v23 but is it a valid v24 identifier
 353  502
         if (id == null)
 354  
         {
 355  
             // It is a valid v23 identifier so should be able to find a
 356  
             //  frame body for it.
 357  51
             if (ID3Tags.isID3v23FrameIdentifier(identifier) == true)
 358  
             {
 359  45
                 id = identifier;
 360  
             }
 361  
             // Unknown so will be created as FrameBodyUnsupported
 362  
             else
 363  
             {
 364  6
                 id = UNSUPPORTED_ID;
 365  
             }
 366  
         }
 367  502
         logger.fine(getLoggingFilename() + ":Identifier was:" + identifier + " reading using:" + id + "with frame size:" + frameSize);
 368  
 
 369  
         //Read extra bits appended to frame header for various encodings
 370  
         //These are not included in header size but are included in frame size but wont be read when we actually
 371  
         //try to read the frame body data
 372  502
         int extraHeaderBytesCount = 0;
 373  502
         int decompressedFrameSize = -1;
 374  502
         if (((EncodingFlags) encodingFlags).isCompression())
 375  
         {
 376  
             //Read the Decompressed Size
 377  2
             decompressedFrameSize = byteBuffer.getInt();
 378  2
             extraHeaderBytesCount = FRAME_COMPRESSION_UNCOMPRESSED_SIZE;
 379  2
             logger.fine(getLoggingFilename() + ":Decompressed frame size is:" + decompressedFrameSize);
 380  
         }
 381  
 
 382  502
         if (((EncodingFlags) encodingFlags).isEncryption())
 383  
         {
 384  
             //Read the Encryption byte, but do nothing with it
 385  0
             extraHeaderBytesCount += FRAME_ENCRYPTION_INDICATOR_SIZE;
 386  0
             byteBuffer.get();
 387  
         }
 388  
 
 389  502
         if (((EncodingFlags) encodingFlags).isGrouping())
 390  
         {
 391  
             //Read the Grouping byte, but do nothing with it
 392  0
             extraHeaderBytesCount += FRAME_GROUPING_INDICATOR_SIZE;
 393  0
             byteBuffer.get();
 394  
         }
 395  
 
 396  
         //Work out the real size of the framebody data
 397  502
         int realFrameSize = frameSize - extraHeaderBytesCount;
 398  
 
 399  502
         ByteBuffer frameBodyBuffer = null;
 400  
 
 401  
         //Uncompress the frame
 402  502
         if (((EncodingFlags) encodingFlags).isCompression())
 403  
         {
 404  
             // Decompress the bytes into this buffer, size initialized from header field
 405  2
             byte[] result = new byte[decompressedFrameSize];
 406  2
             byte[] input = new byte[realFrameSize];
 407  
 
 408  
             //Store position ( just after frame header and any extra bits)
 409  
             //Read frame data into array, and then put buffer back to where it was
 410  2
             int position = byteBuffer.position();
 411  2
             byteBuffer.get(input, 0, realFrameSize);
 412  2
             byteBuffer.position(position);
 413  
 
 414  2
             Inflater decompresser = new Inflater();
 415  2
             decompresser.setInput(input);
 416  
             try
 417  
             {
 418  2
                 decompresser.inflate(result);
 419  
             }
 420  0
             catch (DataFormatException dfe)
 421  
             {
 422  0
                 dfe.printStackTrace();
 423  
                 //Update position of main buffer, so no attempt is made to reread these bytes
 424  0
                 byteBuffer.position(byteBuffer.position() + realFrameSize);
 425  0
                 throw new InvalidFrameException(getLoggingFilename() + "Unable to decompress this frame:" + identifier + ":" + dfe.getMessage());
 426  2
             }
 427  2
             decompresser.end();
 428  2
             frameBodyBuffer = ByteBuffer.wrap(result);
 429  
         }
 430  
 
 431  
         //Read the body data
 432  
         try
 433  
         {
 434  502
             if (((EncodingFlags) encodingFlags).isCompression())
 435  
             {
 436  2
                 frameBody = readBody(id, frameBodyBuffer, decompressedFrameSize);
 437  
             }
 438  
             else
 439  
             {
 440  
                 //Create Buffer that only contains the body of this frame rather than the remainder of tag
 441  500
                 frameBodyBuffer = byteBuffer.slice();
 442  500
                 frameBodyBuffer.limit(realFrameSize);
 443  500
                 frameBody = readBody(id, frameBodyBuffer, realFrameSize);
 444  
             }
 445  
             //TODO code seems to assume that if the frame created is not a v23FrameBody
 446  
             //it should be deprecated, but what about if somehow a V24Frame has been put into a V23 Tag, shouldnt
 447  
             //it then be created as FrameBodyUnsupported
 448  500
             if (!(frameBody instanceof ID3v23FrameBody))
 449  
             {
 450  0
                 logger.info(getLoggingFilename() + ":Converted frame body with:" + identifier + " to deprecated framebody");
 451  0
                 frameBody = new FrameBodyDeprecated((AbstractID3v2FrameBody) frameBody);
 452  
             }
 453  500
         }
 454  
         finally
 455  
         {
 456  
             //Update position of main buffer, so no attempt is made to reread these bytes
 457  2
             byteBuffer.position(byteBuffer.position() + realFrameSize);
 458  500
         }
 459  500
     }
 460  
 
 461  
     /**
 462  
      * Write the frame to bufferOutputStream
 463  
      *
 464  
      * @throws IOException
 465  
      */
 466  
     public void write(ByteArrayOutputStream tagBuffer)
 467  
     {
 468  304
         logger.info("Writing frame to buffer:" + getIdentifier());
 469  
         //This is where we will write header, move position to where we can
 470  
         //write body
 471  304
         ByteBuffer headerBuffer = ByteBuffer.allocate(FRAME_HEADER_SIZE);
 472  
 
 473  
         //Write Frame Body Data
 474  304
         ByteArrayOutputStream bodyOutputStream = new ByteArrayOutputStream();
 475  304
         ((AbstractID3v2FrameBody) frameBody).write(bodyOutputStream);
 476  
         //Write Frame Header write Frame ID
 477  304
         if (getIdentifier().length() == 3)
 478  
         {
 479  0
             identifier = identifier + ' ';
 480  
         }
 481  304
         headerBuffer.put(Utils.getDefaultBytes(getIdentifier(), "ISO-8859-1"), 0, FRAME_ID_SIZE);
 482  
         //Write Frame Size
 483  304
         int size = frameBody.getSize();
 484  304
         logger.fine("Frame Size Is:" + size);
 485  304
         headerBuffer.putInt(frameBody.getSize());
 486  
 
 487  
         //Write the Flags
 488  
         //Status Flags:leave as they were when we read
 489  304
         headerBuffer.put(statusFlags.getWriteFlags());
 490  
 
 491  
         //Enclosing Flags, first reset
 492  304
         encodingFlags.resetFlags();
 493  
         //Encoding we dont support any of flags so don't set any
 494  304
         headerBuffer.put(encodingFlags.getFlags());
 495  
 
 496  
         try
 497  
         {
 498  
             //Add header to the Byte Array Output Stream
 499  304
             tagBuffer.write(headerBuffer.array());
 500  
 
 501  
             //Add body to the Byte Array Output Stream
 502  304
             tagBuffer.write(bodyOutputStream.toByteArray());
 503  
         }
 504  0
         catch (IOException ioe)
 505  
         {
 506  
             //This could never happen coz not writing to file, so convert to RuntimeException
 507  0
             throw new RuntimeException(ioe);
 508  304
         }
 509  
 
 510  
 
 511  304
     }
 512  
 
 513  
     protected AbstractID3v2Frame.StatusFlags getStatusFlags()
 514  
     {
 515  614
         return statusFlags;
 516  
     }
 517  
 
 518  
     protected AbstractID3v2Frame.EncodingFlags getEncodingFlags()
 519  
     {
 520  616
         return encodingFlags;
 521  
     }
 522  
 
 523  
     /**
 524  
      * This represents a frame headers Status Flags
 525  
      * Make adjustments if necessary based on frame type and specification.
 526  
      */
 527  
     class StatusFlags extends AbstractID3v2Frame.StatusFlags
 528  
     {
 529  
         public static final String TYPE_TAGALTERPRESERVATION = "typeTagAlterPreservation";
 530  
         public static final String TYPE_FILEALTERPRESERVATION = "typeFileAlterPreservation";
 531  
         public static final String TYPE_READONLY = "typeReadOnly";
 532  
 
 533  
         /**
 534  
          * Discard frame if tag altered
 535  
          */
 536  
         public static final int MASK_TAG_ALTER_PRESERVATION = FileConstants.BIT7;
 537  
 
 538  
         /**
 539  
          * Discard frame if audio file part  altered
 540  
          */
 541  
         public static final int MASK_FILE_ALTER_PRESERVATION = FileConstants.BIT6;
 542  
 
 543  
         /**
 544  
          * Frame tagged as read only
 545  
          */
 546  
         public static final int MASK_READ_ONLY = FileConstants.BIT5;
 547  
 
 548  
         public StatusFlags()
 549  78
         {
 550  78
             originalFlags = (byte) 0;
 551  78
             writeFlags = (byte) 0;
 552  78
         }
 553  
 
 554  
         StatusFlags(byte flags)
 555  502
         {
 556  502
             originalFlags = flags;
 557  502
             writeFlags = flags;
 558  502
             modifyFlags();
 559  502
         }
 560  
 
 561  
         /**
 562  
          * Use this constructor when convert a v24 frame
 563  
          */
 564  
         StatusFlags(ID3v24Frame.StatusFlags statusFlags)
 565  138
         {
 566  138
             originalFlags = convertV4ToV3Flags(statusFlags.getOriginalFlags());
 567  138
             writeFlags = originalFlags;
 568  138
             modifyFlags();
 569  138
         }
 570  
 
 571  
         private byte convertV4ToV3Flags(byte v4Flag)
 572  
         {
 573  138
             byte v3Flag = (byte) 0;
 574  138
             if ((v4Flag & ID3v24Frame.StatusFlags.MASK_FILE_ALTER_PRESERVATION) != 0)
 575  
             {
 576  0
                 v3Flag |= (byte) MASK_FILE_ALTER_PRESERVATION;
 577  
             }
 578  138
             if ((v4Flag & ID3v24Frame.StatusFlags.MASK_TAG_ALTER_PRESERVATION) != 0)
 579  
             {
 580  3
                 v3Flag |= (byte) MASK_TAG_ALTER_PRESERVATION;
 581  
             }
 582  138
             return v3Flag;
 583  
         }
 584  
 
 585  
         protected void modifyFlags()
 586  
         {
 587  640
             String str = getIdentifier();
 588  640
             if (ID3v23Frames.getInstanceOf().isDiscardIfFileAltered(str) == true)
 589  
             {
 590  10
                 writeFlags |= (byte) MASK_FILE_ALTER_PRESERVATION;
 591  10
                 writeFlags &= (byte) ~MASK_TAG_ALTER_PRESERVATION;
 592  
             }
 593  
             else
 594  
             {
 595  630
                 writeFlags &= (byte) ~MASK_FILE_ALTER_PRESERVATION;
 596  630
                 writeFlags &= (byte) ~MASK_TAG_ALTER_PRESERVATION;
 597  
             }
 598  640
         }
 599  
 
 600  
         public void createStructure()
 601  
         {
 602  0
             MP3File.getStructureFormatter().openHeadingElement(TYPE_FLAGS, "");
 603  0
             MP3File.getStructureFormatter().addElement(TYPE_TAGALTERPRESERVATION, originalFlags & MASK_TAG_ALTER_PRESERVATION);
 604  0
             MP3File.getStructureFormatter().addElement(TYPE_FILEALTERPRESERVATION, originalFlags & MASK_FILE_ALTER_PRESERVATION);
 605  0
             MP3File.getStructureFormatter().addElement(TYPE_READONLY, originalFlags & MASK_READ_ONLY);
 606  0
             MP3File.getStructureFormatter().closeHeadingElement(TYPE_FLAGS);
 607  0
         }
 608  
     }
 609  
 
 610  
     /**
 611  
      * This represents a frame headers Encoding Flags
 612  
      */
 613  
     class EncodingFlags extends AbstractID3v2Frame.EncodingFlags
 614  
     {
 615  
         public static final String TYPE_COMPRESSION = "compression";
 616  
         public static final String TYPE_ENCRYPTION = "encryption";
 617  
         public static final String TYPE_GROUPIDENTITY = "groupidentity";
 618  
 
 619  
         /**
 620  
          * Frame is compressed
 621  
          */
 622  
         public static final int MASK_COMPRESSION = FileConstants.BIT7;
 623  
 
 624  
         /**
 625  
          * Frame is encrypted
 626  
          */
 627  
         public static final int MASK_ENCRYPTION = FileConstants.BIT6;
 628  
 
 629  
         /**
 630  
          * Frame is part of a group
 631  
          */
 632  
         public static final int MASK_GROUPING_IDENTITY = FileConstants.BIT5;
 633  
 
 634  
         public EncodingFlags()
 635  78
         {
 636  78
             super();
 637  78
         }
 638  
 
 639  
         public EncodingFlags(byte flags)
 640  640
         {
 641  640
             super(flags);
 642  640
             logEnabledFlags();
 643  640
         }
 644  
 
 645  
         public void logEnabledFlags()
 646  
         {
 647  640
             if (isCompression())
 648  
             {
 649  2
                 logger.warning(getLoggingFilename() + ":" + identifier + " is compressed");
 650  
             }
 651  
 
 652  640
             if (isEncryption())
 653  
             {
 654  0
                 logger.warning(getLoggingFilename() + ":" + identifier + " is encrypted");
 655  
             }
 656  
 
 657  640
             if (isGrouping())
 658  
             {
 659  0
                 logger.warning(getLoggingFilename() + ":" + identifier + " is grouped");
 660  
             }
 661  640
         }
 662  
 
 663  
         public boolean isCompression()
 664  
         {
 665  2148
             return (flags & MASK_COMPRESSION) > 0;
 666  
         }
 667  
 
 668  
         public boolean isEncryption()
 669  
         {
 670  1142
             return (flags & MASK_ENCRYPTION) > 0;
 671  
         }
 672  
 
 673  
         public boolean isGrouping()
 674  
         {
 675  1142
             return (flags & MASK_GROUPING_IDENTITY) > 0;
 676  
         }
 677  
 
 678  
 
 679  
         public void createStructure()
 680  
         {
 681  0
             MP3File.getStructureFormatter().openHeadingElement(TYPE_FLAGS, "");
 682  0
             MP3File.getStructureFormatter().addElement(TYPE_COMPRESSION, flags & MASK_COMPRESSION);
 683  0
             MP3File.getStructureFormatter().addElement(TYPE_ENCRYPTION, flags & MASK_ENCRYPTION);
 684  0
             MP3File.getStructureFormatter().addElement(TYPE_GROUPIDENTITY, flags & MASK_GROUPING_IDENTITY);
 685  0
             MP3File.getStructureFormatter().closeHeadingElement(TYPE_FLAGS);
 686  0
         }
 687  
     }
 688  
 
 689  
     /**
 690  
      * Does the frame identifier meet the syntax for a idv3v2 frame identifier.
 691  
      * must start with a capital letter and only contain capital letters and numbers
 692  
      *
 693  
      * @param identifier to be checked
 694  
      * @return whether the identifier is valid
 695  
      */
 696  
     public boolean isValidID3v2FrameIdentifier(String identifier)
 697  
     {
 698  602
         Matcher m = validFrameIdentifier.matcher(identifier);
 699  602
         return m.matches();
 700  
     }
 701  
 
 702  
     /**
 703  
      * Return String Representation of body
 704  
      */
 705  
     public void createStructure()
 706  
     {
 707  0
         MP3File.getStructureFormatter().openHeadingElement(TYPE_FRAME, getIdentifier());
 708  0
         MP3File.getStructureFormatter().addElement(TYPE_FRAME_SIZE, frameSize);
 709  0
         statusFlags.createStructure();
 710  0
         encodingFlags.createStructure();
 711  0
         frameBody.createStructure();
 712  0
         MP3File.getStructureFormatter().closeHeadingElement(TYPE_FRAME);
 713  0
     }
 714  
 
 715  
     /**
 716  
      * @return true if considered a common frame
 717  
      */
 718  
     public boolean isCommon()
 719  
     {
 720  0
         return ID3v23Frames.getInstanceOf().isCommon(getId());
 721  
     }
 722  
 
 723  
     /**
 724  
      * @return true if considered a common frame
 725  
      */
 726  
     public boolean isBinary()
 727  
     {
 728  0
         return ID3v23Frames.getInstanceOf().isBinary(getId());
 729  
     }
 730  
 }