Coverage Report - org.jaudiotagger.tag.id3.AbstractID3v2Frame
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractID3v2Frame
44%
52/116
35%
5/14
2.429
AbstractID3v2Frame$EncodingFlags
91%
11/12
N/A
2.429
AbstractID3v2Frame$StatusFlags
80%
4/5
N/A
2.429
 
 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.audio.mp3.MP3File;
 19  
 import org.jaudiotagger.tag.InvalidFrameException;
 20  
 import org.jaudiotagger.tag.InvalidTagException;
 21  
 import org.jaudiotagger.tag.TagField;
 22  
 import org.jaudiotagger.tag.TagOptionSingleton;
 23  
 import org.jaudiotagger.tag.id3.framebody.AbstractID3v2FrameBody;
 24  
 import org.jaudiotagger.tag.id3.framebody.FrameBodyUnsupported;
 25  
 
 26  
 import java.io.ByteArrayOutputStream;
 27  
 import java.lang.reflect.Constructor;
 28  
 import java.lang.reflect.InvocationTargetException;
 29  
 import java.nio.ByteBuffer;
 30  
 import java.util.logging.Level;
 31  
 
 32  
 /**
 33  
  * This abstract class is each frame header inside a ID3v2 tag.
 34  
  *
 35  
  * @author : Paul Taylor
 36  
  * @author : Eric Farng
 37  
  * @version $Id: AbstractID3v2Frame.java 836 2009-11-12 15:44:07Z paultaylor $
 38  
  */
 39  
 public abstract class AbstractID3v2Frame extends AbstractTagFrame implements TagField
 40  
 {
 41  
 
 42  
     protected static final String TYPE_FRAME = "frame";
 43  
     protected static final String TYPE_FRAME_SIZE = "frameSize";
 44  
     protected static final String UNSUPPORTED_ID = "Unsupported";
 45  
 
 46  
     //Frame identifier
 47  15556
     protected String identifier = "";
 48  
 
 49  
     //Frame Size
 50  
     protected int frameSize;
 51  
 
 52  
     //The purpose of this is to provide the filename that should be used when writing debug messages
 53  
     //when problems occur reading or writing to file, otherwise it is difficult to track down the error
 54  
     //when processing many files
 55  15556
     private String loggingFilename = "";
 56  
 
 57  
     /**
 58  
      * Create an empty frame
 59  
      */
 60  
     protected AbstractID3v2Frame()
 61  14390
     {
 62  14390
         }
 63  
 
 64  
     /**
 65  
      * This holds the Status flags (not supported in v2.20
 66  
      */
 67  15556
     StatusFlags statusFlags = null;
 68  
 
 69  
     /**
 70  
      * This holds the Encoding flags (not supported in v2.20)
 71  
      */
 72  15556
     EncodingFlags encodingFlags = null;
 73  
 
 74  
     /**
 75  
      * Create a frame based on another frame
 76  
      * @param frame
 77  
      */
 78  
     public AbstractID3v2Frame(AbstractID3v2Frame frame)
 79  
     {
 80  0
         super(frame);
 81  0
     }
 82  
 
 83  
     /**
 84  
      * Create a frame based on a body
 85  
      * @param body
 86  
      */
 87  
     public AbstractID3v2Frame(AbstractID3v2FrameBody body)
 88  0
     {
 89  0
         this.frameBody = body;
 90  0
         this.frameBody.setHeader(this);
 91  0
     }
 92  
 
 93  
     /**
 94  
      * Create a new frame with empty body based on identifier
 95  
      * @param identifier
 96  
      */
 97  
     //TODO the identifier checks should be done in the relevent subclasses
 98  
     public AbstractID3v2Frame(String identifier)
 99  1166
     {
 100  1166
         logger.info("Creating empty frame of type" + identifier);
 101  1166
         this.identifier = identifier;
 102  
 
 103  
         // Use reflection to map id to frame body, which makes things much easier
 104  
         // to keep things up to date.
 105  
         try
 106  
         {
 107  1166
             Class<AbstractID3v2FrameBody> c = (Class<AbstractID3v2FrameBody>) Class.forName("org.jaudiotagger.tag.id3.framebody.FrameBody" + identifier);
 108  1166
             frameBody = c.newInstance();
 109  
         }
 110  0
         catch (ClassNotFoundException cnfe)
 111  
         {
 112  0
             logger.severe(cnfe.getMessage());
 113  0
             frameBody = new FrameBodyUnsupported(identifier);
 114  
         }
 115  
         //Instantiate Interface/Abstract should not happen
 116  0
         catch (InstantiationException ie)
 117  
         {
 118  0
             logger.log(Level.SEVERE, "InstantiationException:" + identifier, ie);
 119  0
             throw new RuntimeException(ie);
 120  
         }
 121  
         //Private Constructor shouild not happen
 122  0
         catch (IllegalAccessException iae)
 123  
         {
 124  0
             logger.log(Level.SEVERE, "IllegalAccessException:" + identifier, iae);
 125  0
             throw new RuntimeException(iae);
 126  1166
         }
 127  1166
         frameBody.setHeader(this);
 128  1166
         if (this instanceof ID3v24Frame)
 129  
         {
 130  733
             frameBody.setTextEncoding(TagOptionSingleton.getInstance().getId3v24DefaultTextEncoding());
 131  
         }
 132  433
         else if (this instanceof ID3v23Frame)
 133  
         {
 134  433
             frameBody.setTextEncoding(TagOptionSingleton.getInstance().getId3v23DefaultTextEncoding());
 135  
         }
 136  
 
 137  1166
         logger.info("Created empty frame of type" + identifier);
 138  1166
     }
 139  
 
 140  
     /**
 141  
      * Retrieve the logging filename to be used in debugging
 142  
      *
 143  
      * @return logging filename to be used in debugging
 144  
      */
 145  
     protected String getLoggingFilename()
 146  
     {
 147  17833
         return loggingFilename;
 148  
     }
 149  
 
 150  
     /**
 151  
      * Set logging filename when construct tag for read from file
 152  
      *
 153  
      * @param loggingFilename
 154  
      */
 155  
     protected void setLoggingFilename(String loggingFilename)
 156  
     {
 157  8067
         this.loggingFilename = loggingFilename;
 158  8067
     }
 159  
 
 160  
     /**
 161  
      * Return the frame identifier, this only identifies the frame it does not provide a unique
 162  
      * key, when using frames such as TXXX which are used by many fields     *
 163  
      *
 164  
      * @return the frame identifier (Tag Field Interface)
 165  
      */
 166  
     //TODO, this is confusing only returns the frameId, which does not neccessarily uniquely
 167  
     //identify the frame
 168  
     public String getId()
 169  
     {
 170  1327
         return getIdentifier();
 171  
     }
 172  
 
 173  
     /**
 174  
      * Return the frame identifier
 175  
      *
 176  
      * @return the frame identifier
 177  
      */
 178  
     public String getIdentifier()
 179  
     {
 180  68806
         return identifier;
 181  
     }
 182  
 
 183  
     //TODO:needs implementing but not sure if this method is required at all
 184  
     public void copyContent(TagField field)
 185  
     {
 186  
 
 187  0
     }
 188  
 
 189  
     /**
 190  
      * Read the frame body from the specified file via the buffer
 191  
      *
 192  
      * @param identifier the frame identifier
 193  
      * @param byteBuffer to read the frabe body from
 194  
      * @param frameSize
 195  
      * @return a newly created FrameBody
 196  
      * @throws InvalidFrameException unable to construct a framebody from the data
 197  
      */
 198  
     @SuppressWarnings("unchecked")
 199  
     protected AbstractID3v2FrameBody readBody(String identifier, ByteBuffer byteBuffer, int frameSize) throws InvalidFrameException
 200  
     {
 201  
         //Use reflection to map id to frame body, which makes things much easier
 202  
         //to keep things up to date,although slight performance hit.
 203  6748
         logger.finest("Creating framebody:start");
 204  
 
 205  
         AbstractID3v2FrameBody frameBody;
 206  
         try
 207  
         {
 208  6748
             Class<AbstractID3v2FrameBody> c = (Class<AbstractID3v2FrameBody>) Class.forName("org.jaudiotagger.tag.id3.framebody.FrameBody" + identifier);
 209  6728
             Class<?>[] constructorParameterTypes = {Class.forName("java.nio.ByteBuffer"), Integer.TYPE};
 210  6728
             Object[] constructorParameterValues = {byteBuffer, frameSize};
 211  6728
             Constructor<AbstractID3v2FrameBody> construct = c.getConstructor(constructorParameterTypes);
 212  6728
             frameBody = (construct.newInstance(constructorParameterValues));
 213  
         }
 214  
         //No class defined for this frame type,use FrameUnsupported
 215  20
         catch (ClassNotFoundException cex)
 216  
         {
 217  20
             logger.info(getLoggingFilename() + ":" + "Identifier not recognised:" + identifier + " using FrameBodyUnsupported");
 218  
             try
 219  
             {
 220  20
                 frameBody = new FrameBodyUnsupported(byteBuffer, frameSize);
 221  
             }
 222  
             //Should only throw InvalidFrameException but unfortunately legacy hierachy forces
 223  
             //read method to declare it can throw InvalidtagException
 224  0
             catch (InvalidFrameException ife)
 225  
             {
 226  0
                 throw ife;
 227  
             }
 228  0
             catch (InvalidTagException te)
 229  
             {
 230  0
                 throw new InvalidFrameException(te.getMessage());
 231  20
             }
 232  
         }
 233  
         //An error has occurred during frame instantiation, if underlying cause is an unchecked exception or error
 234  
         //propagate it up otherwise mark this frame as invalid
 235  17
         catch (InvocationTargetException ite)
 236  
         {
 237  17
             logger.severe(getLoggingFilename() + ":" + "An error occurred within abstractID3v2FrameBody for identifier:" + identifier + ":" + ite.getCause().getMessage());
 238  17
             if (ite.getCause() instanceof Error)
 239  
             {
 240  0
                 throw (Error) ite.getCause();
 241  
             }
 242  17
             else if (ite.getCause() instanceof RuntimeException)
 243  
             {
 244  0
                 throw (RuntimeException) ite.getCause();
 245  
             }
 246  
             else
 247  
             {
 248  17
                 throw new InvalidFrameException(ite.getCause().getMessage());
 249  
             }
 250  
         }
 251  
         //No Such Method should not happen
 252  0
         catch (NoSuchMethodException sme)
 253  
         {
 254  0
             logger.log(Level.SEVERE, getLoggingFilename() + ":" + "No such method:" + sme.getMessage(), sme);
 255  0
             throw new RuntimeException(sme.getMessage());
 256  
         }
 257  
         //Instantiate Interface/Abstract should not happen
 258  0
         catch (InstantiationException ie)
 259  
         {
 260  0
             logger.log(Level.SEVERE, getLoggingFilename() + ":" + "Instantiation exception:" + ie.getMessage(), ie);
 261  0
             throw new RuntimeException(ie.getMessage());
 262  
         }
 263  
         //Private Constructor shouild not happen
 264  0
         catch (IllegalAccessException iae)
 265  
         {
 266  0
             logger.log(Level.SEVERE, getLoggingFilename() + ":" + "Illegal access exception :" + iae.getMessage(), iae);
 267  0
             throw new RuntimeException(iae.getMessage());
 268  6731
         }
 269  6731
         logger.finest(getLoggingFilename() + ":" + "Created framebody:end" + frameBody.getIdentifier());
 270  6731
         frameBody.setHeader(this);
 271  6731
         return frameBody;
 272  
     }
 273  
 
 274  
     /**
 275  
      * This creates a new body based of type identifier but populated by the data
 276  
      * in the body. This is a different type to the body being created which is why
 277  
      * TagUtility.copyObject() can't be used. This is used when converting between
 278  
      * different versions of a tag for frames that have a non-trivial mapping such
 279  
      * as TYER in v3 to TDRC in v4. This will only work where appropriate constructors
 280  
      * exist in the frame body to be created, for example a FrameBodyTYER requires a constructor
 281  
      * consisting of a FrameBodyTDRC.
 282  
      * <p/>
 283  
      * If this method is called and a suitable constructor does not exist then an InvalidFrameException
 284  
      * will be thrown
 285  
      *
 286  
      * @param identifier to determine type of the frame
 287  
      * @param body
 288  
      * @return newly created framebody for this type
 289  
      * @throws InvalidFrameException if unable to construct a framebody for the identifier and body provided.
 290  
      */
 291  
     @SuppressWarnings("unchecked")
 292  
     protected AbstractID3v2FrameBody readBody(String identifier, AbstractID3v2FrameBody body) throws InvalidFrameException
 293  
     {
 294  
         /* Use reflection to map id to frame body, which makes things much easier
 295  
          * to keep things up to date, although slight performance hit.
 296  
          */
 297  
         AbstractID3v2FrameBody frameBody;
 298  
         try
 299  
         {
 300  469
             Class<AbstractID3v2FrameBody> c = (Class<AbstractID3v2FrameBody>) Class.forName("org.jaudiotagger.tag.id3.framebody.FrameBody" + identifier);
 301  469
             Class<?>[] constructorParameterTypes = {body.getClass()};
 302  469
             Object[] constructorParameterValues = {body};
 303  469
             Constructor<AbstractID3v2FrameBody> construct = c.getConstructor(constructorParameterTypes);
 304  469
             frameBody = (construct.newInstance(constructorParameterValues));
 305  
         }
 306  0
         catch (ClassNotFoundException cex)
 307  
         {
 308  0
             logger.info("Identifier not recognised:" + identifier + " unable to create framebody");
 309  0
             throw new InvalidFrameException("FrameBody" + identifier + " does not exist");
 310  
         }
 311  
         //If suitable constructor does not exist
 312  0
         catch (NoSuchMethodException sme)
 313  
         {
 314  0
             logger.log(Level.SEVERE, "No such method:" + sme.getMessage(), sme);
 315  0
             throw new InvalidFrameException("FrameBody" + identifier + " does not have a constructor that takes:" + body.getClass().getName());
 316  
         }
 317  0
         catch (InvocationTargetException ite)
 318  
         {
 319  0
             logger.severe("An error occurred within abstractID3v2FrameBody");
 320  0
             logger.log(Level.SEVERE, "Invocation target exception:" + ite.getCause().getMessage(), ite.getCause());
 321  0
             if (ite.getCause() instanceof Error)
 322  
             {
 323  0
                 throw (Error) ite.getCause();
 324  
             }
 325  0
             else if (ite.getCause() instanceof RuntimeException)
 326  
             {
 327  0
                 throw (RuntimeException) ite.getCause();
 328  
             }
 329  
             else
 330  
             {
 331  0
                 throw new InvalidFrameException(ite.getCause().getMessage());
 332  
             }
 333  
         }
 334  
 
 335  
         //Instantiate Interface/Abstract should not happen
 336  0
         catch (InstantiationException ie)
 337  
         {
 338  0
             logger.log(Level.SEVERE, "Instantiation exception:" + ie.getMessage(), ie);
 339  0
             throw new RuntimeException(ie.getMessage());
 340  
         }
 341  
         //Private Constructor shouild not happen
 342  0
         catch (IllegalAccessException iae)
 343  
         {
 344  0
             logger.log(Level.SEVERE, "Illegal access exception :" + iae.getMessage(), iae);
 345  0
             throw new RuntimeException(iae.getMessage());
 346  469
         }
 347  
 
 348  469
         logger.finer("frame Body created" + frameBody.getIdentifier());
 349  469
         frameBody.setHeader(this);
 350  469
         return frameBody;
 351  
     }
 352  
 
 353  
     public byte[] getRawContent()
 354  
     {
 355  0
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 356  0
         write(baos);
 357  0
         return baos.toByteArray();
 358  
     }
 359  
 
 360  
     public abstract void write(ByteArrayOutputStream tagBuffer);
 361  
 
 362  
     /**
 363  
      * @param b
 364  
      */
 365  
     public void isBinary(boolean b)
 366  
     {
 367  
         //do nothing because whether or not a field is binary is defined by its id and is immutable
 368  0
     }
 369  
 
 370  
 
 371  
     public boolean isEmpty()
 372  
     {
 373  0
         AbstractTagFrameBody body = this.getBody();
 374  0
         if (body == null)
 375  
         {
 376  0
             return true;
 377  
         }
 378  
         //TODO depends on the body
 379  0
         return false;
 380  
     }
 381  
 
 382  
     protected StatusFlags getStatusFlags()
 383  
     {
 384  0
         return statusFlags;
 385  
     }
 386  
 
 387  
     protected EncodingFlags getEncodingFlags()
 388  
     {
 389  0
         return encodingFlags;
 390  
     }
 391  
 
 392  
     class StatusFlags
 393  
     {
 394  
         protected static final String TYPE_FLAGS = "statusFlags";
 395  
 
 396  
         protected byte originalFlags;
 397  
         protected byte writeFlags;
 398  
 
 399  
         protected StatusFlags()
 400  11666
         {
 401  
 
 402  11666
         }
 403  
 
 404  
         /**
 405  
          * This returns the flags as they were originally read or created
 406  
          * @return
 407  
          */
 408  
         public byte getOriginalFlags()
 409  
         {
 410  3565
             return originalFlags;
 411  
         }
 412  
 
 413  
         /**
 414  
          * This returns the flags amended to meet specification
 415  
          * @return
 416  
          */
 417  
         public byte getWriteFlags()
 418  
         {
 419  3437
             return writeFlags;
 420  
         }
 421  
 
 422  
         public void createStructure()
 423  
         {
 424  0
         }
 425  
 
 426  
 
 427  
     }
 428  
 
 429  
     class EncodingFlags
 430  
     {
 431  
         protected static final String TYPE_FLAGS = "encodingFlags";
 432  
 
 433  
         protected byte flags;
 434  
 
 435  
         protected EncodingFlags()
 436  2305
         {
 437  2305
             resetFlags();
 438  2305
         }
 439  
 
 440  
         protected EncodingFlags(byte flags)
 441  9361
         {
 442  9361
             setFlags(flags);
 443  9361
         }
 444  
 
 445  
         public byte getFlags()
 446  
         {
 447  4483
             return flags;
 448  
         }
 449  
 
 450  
         public void setFlags(byte flags)
 451  
         {
 452  15103
             this.flags = flags;
 453  15103
         }
 454  
 
 455  
         public void resetFlags()
 456  
         {
 457  5742
             setFlags((byte) 0);
 458  5742
         }
 459  
 
 460  
         public void createStructure()
 461  
         {
 462  0
         }
 463  
     }
 464  
 
 465  
     /**
 466  
      * Return String Representation of frame
 467  
      */
 468  
     public void createStructure()
 469  
     {
 470  0
         MP3File.getStructureFormatter().openHeadingElement(TYPE_FRAME, getIdentifier());
 471  0
         MP3File.getStructureFormatter().closeHeadingElement(TYPE_FRAME);
 472  0
     }
 473  
 }