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