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