Coverage Report - org.jaudiotagger.tag.id3.AbstractID3v2Tag
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractID3v2Tag
68%
493/724
57%
207/362
0
AbstractID3v2Tag$1
89%
32/36
92%
22/24
0
AbstractID3v2Tag$FrameAndSubId
100%
6/6
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.generic.Utils;
 19  
 import org.jaudiotagger.audio.mp3.MP3File;
 20  
 import org.jaudiotagger.audio.AudioFile;
 21  
 import org.jaudiotagger.audio.exceptions.UnableToCreateFileException;
 22  
 import org.jaudiotagger.audio.exceptions.UnableToModifyFileException;
 23  
 import org.jaudiotagger.audio.exceptions.UnableToRenameFileException;
 24  
 import org.jaudiotagger.logging.ErrorMessage;
 25  
 import org.jaudiotagger.logging.FileSystemMessage;
 26  
 import org.jaudiotagger.tag.*;
 27  
 import org.jaudiotagger.tag.mp4.Mp4TagField;
 28  
 import org.jaudiotagger.tag.mp4.Mp4FieldKey;
 29  
 import org.jaudiotagger.tag.mp4.field.Mp4TagCoverField;
 30  
 import org.jaudiotagger.tag.datatype.DataTypes;
 31  
 import org.jaudiotagger.tag.datatype.Artwork;
 32  
 import org.jaudiotagger.tag.id3.framebody.*;
 33  
 import org.jaudiotagger.tag.id3.valuepair.ImageFormats;
 34  
 import org.jaudiotagger.tag.id3.valuepair.TextEncoding;
 35  
 import org.jaudiotagger.tag.reference.PictureTypes;
 36  
 
 37  
 import java.io.*;
 38  
 import java.nio.ByteBuffer;
 39  
 import java.nio.channels.FileChannel;
 40  
 import java.nio.channels.FileLock;
 41  
 import java.nio.channels.WritableByteChannel;
 42  
 import java.util.*;
 43  
 import java.util.logging.Level;
 44  
 
 45  
 /**
 46  
  * This is the abstract base class for all ID3v2 tags.
 47  
  *
 48  
  * @author : Paul Taylor
 49  
  * @author : Eric Farng
 50  
  * @version $Id: AbstractID3v2Tag.java,v 1.56 2008/11/24 09:53:12 paultaylor Exp $
 51  
  */
 52  8
 public abstract class AbstractID3v2Tag extends AbstractID3Tag implements Tag
 53  
 {
 54  
     protected static final String TYPE_HEADER = "header";
 55  
     protected static final String TYPE_BODY = "body";
 56  
 
 57  
     //Tag ID as held in file
 58  52
     protected static final byte[] TAG_ID = {'I', 'D', '3'};
 59  
 
 60  
     //The tag header is the same for ID3v2 versions
 61  
     public static final int TAG_HEADER_LENGTH = 10;
 62  
     protected static final int FIELD_TAGID_LENGTH = 3;
 63  
     protected static final int FIELD_TAG_MAJOR_VERSION_LENGTH = 1;
 64  
     protected static final int FIELD_TAG_MINOR_VERSION_LENGTH = 1;
 65  
     protected static final int FIELD_TAG_FLAG_LENGTH = 1;
 66  
     protected static final int FIELD_TAG_SIZE_LENGTH = 4;
 67  
 
 68  
     protected static final int FIELD_TAGID_POS = 0;
 69  
     protected static final int FIELD_TAG_MAJOR_VERSION_POS = 3;
 70  
     protected static final int FIELD_TAG_MINOR_VERSION_POS = 4;
 71  
     protected static final int FIELD_TAG_FLAG_POS = 5;
 72  
     protected static final int FIELD_TAG_SIZE_POS = 6;
 73  
 
 74  
     protected static final int TAG_SIZE_INCREMENT = 100;
 75  
 
 76  
     //The max size we try to write in one go to avoid out of memory errors (10mb)
 77  
     private static final long MAXIMUM_WRITABLE_CHUNK_SIZE = 10000000;
 78  
 
 79  
     /**
 80  
      * Map of all frames for this tag
 81  
      */
 82  998
     public HashMap frameMap = null;
 83  
 
 84  
     /**
 85  
      * Holds the ids of invalid duplicate frames
 86  
      */
 87  
     protected static final String TYPE_DUPLICATEFRAMEID = "duplicateFrameId";
 88  998
     protected String duplicateFrameId = "";
 89  
 
 90  
     /**
 91  
      * Holds byte count of invalid duplicate frames
 92  
      */
 93  
     protected static final String TYPE_DUPLICATEBYTES = "duplicateBytes";
 94  998
     protected int duplicateBytes = 0;
 95  
 
 96  
     /**
 97  
      * Holds byte count of empty frames
 98  
      */
 99  
     protected static final String TYPE_EMPTYFRAMEBYTES = "emptyFrameBytes";
 100  998
     protected int emptyFrameBytes = 0;
 101  
 
 102  
     /**
 103  
      * Holds the size of the tag as reported by the tag header
 104  
      */
 105  
     protected static final String TYPE_FILEREADSIZE = "fileReadSize";
 106  998
     protected int fileReadSize = 0;
 107  
 
 108  
     /**
 109  
      * Holds byte count of invalid frames
 110  
      */
 111  
     protected static final String TYPE_INVALIDFRAMEBYTES = "invalidFrameBytes";
 112  998
     protected int invalidFrameBytes = 0;
 113  
 
 114  
     /**
 115  
      * Empty Constructor
 116  
      */
 117  
     public AbstractID3v2Tag()
 118  998
     {
 119  998
     }
 120  
 
 121  
     /**
 122  
      * This constructor is used when a tag is created as a duplicate of another
 123  
      * tag of the same type and version.
 124  
      */
 125  
     protected AbstractID3v2Tag(AbstractID3v2Tag copyObject)
 126  0
     {
 127  0
     }
 128  
 
 129  
     /**
 130  
      * Copy primitives apply to all tags
 131  
      */
 132  
     protected void copyPrimitives(AbstractID3v2Tag copyObject)
 133  
     {
 134  265
         logger.info("Copying Primitives");
 135  
         //Primitives type variables common to all IDv2 Tags
 136  265
         this.duplicateFrameId = new String(copyObject.duplicateFrameId);
 137  265
         this.duplicateBytes = copyObject.duplicateBytes;
 138  265
         this.emptyFrameBytes = copyObject.emptyFrameBytes;
 139  265
         this.fileReadSize = copyObject.fileReadSize;
 140  265
         this.invalidFrameBytes = copyObject.invalidFrameBytes;
 141  265
     }
 142  
 
 143  
     /**
 144  
      * Copy frames from another tag, needs implemanting by subclasses
 145  
      */
 146  
     protected abstract void copyFrames(AbstractID3v2Tag copyObject);
 147  
 
 148  
 
 149  
     /**
 150  
      * Returns the number of bytes which come from duplicate frames
 151  
      *
 152  
      * @return the number of bytes which come from duplicate frames
 153  
      */
 154  
     public int getDuplicateBytes()
 155  
     {
 156  0
         return duplicateBytes;
 157  
     }
 158  
 
 159  
     /**
 160  
      * Return the string which holds the ids of all
 161  
      * duplicate frames.
 162  
      *
 163  
      * @return the string which holds the ids of all duplicate frames.
 164  
      */
 165  
     public String getDuplicateFrameId()
 166  
     {
 167  0
         return duplicateFrameId;
 168  
     }
 169  
 
 170  
     /**
 171  
      * Returns the number of bytes which come from empty frames
 172  
      *
 173  
      * @return the number of bytes which come from empty frames
 174  
      */
 175  
     public int getEmptyFrameBytes()
 176  
     {
 177  0
         return emptyFrameBytes;
 178  
     }
 179  
 
 180  
     /**
 181  
      * Return  byte count of invalid frames
 182  
      *
 183  
      * @return byte count of invalid frames
 184  
      */
 185  
     public int getInvalidFrameBytes()
 186  
     {
 187  0
         return invalidFrameBytes;
 188  
     }
 189  
 
 190  
     /**
 191  
      * Returns the tag size as reported by the tag header
 192  
      *
 193  
      * @return the tag size as reported by the tag header
 194  
      */
 195  
     public int getFileReadBytes()
 196  
     {
 197  0
         return fileReadSize;
 198  
     }
 199  
 
 200  
     /**
 201  
      * Return whether tag has frame with this identifier
 202  
      * <p/>
 203  
      * Warning the match is only done against the identifier so if a tag contains a frame with an unsuported body
 204  
      * but happens to have an identifier that is valid for another version of the tag it will return true
 205  
      *
 206  
      * @param identifier frameId to lookup
 207  
      * @return true if tag has frame with this identifier
 208  
      */
 209  
     public boolean hasFrame(String identifier)
 210  
     {
 211  48
         return frameMap.containsKey(identifier);
 212  
     }
 213  
 
 214  
 
 215  
     /**
 216  
      * Return whether tag has frame with this identifier and a related body. This is required to protect
 217  
      * against circumstances whereby a tag contains a frame with an unsupported body
 218  
      * but happens to have an identifier that is valid for another version of the tag which it has been converted to
 219  
      * <p/>
 220  
      * e.g TDRC is an invalid frame in a v23 tag but if somehow a v23tag has been created by another application
 221  
      * with a TDRC frame we construct an UnsupportedFrameBody to hold it, then this library constructs a
 222  
      * v24 tag, it will contain a frame with id TDRC but it will not have the expected frame body it is not really a
 223  
      * TDRC frame.
 224  
      *
 225  
      * @param identifier frameId to lookup
 226  
      * @return true if tag has frame with this identifier
 227  
      */
 228  
     public boolean hasFrameAndBody(String identifier)
 229  
     {
 230  0
         if (hasFrame(identifier))
 231  
         {
 232  0
             Object o = getFrame(identifier);
 233  0
             if (o instanceof AbstractID3v2Frame)
 234  
             {
 235  0
                 if (((AbstractID3v2Frame) o).getBody() instanceof FrameBodyUnsupported)
 236  
                 {
 237  0
                     return false;
 238  
                 }
 239  0
                 return true;
 240  
             }
 241  0
             return true;
 242  
         }
 243  0
         return false;
 244  
     }
 245  
 
 246  
     /**
 247  
      * Return whether tag has frame starting with this identifier
 248  
      * <p/>
 249  
      * Warning the match is only done against the identifier so if a tag contains a frame with an unsupported body
 250  
      * but happens to have an identifier that is valid for another version of the tag it will return true
 251  
      *
 252  
      * @param identifier start of frameId to lookup
 253  
      * @return tag has frame starting with this identifier
 254  
      */
 255  
     public boolean hasFrameOfType(String identifier)
 256  
     {
 257  0
         Iterator<String> iterator = frameMap.keySet().iterator();
 258  
         String key;
 259  0
         boolean found = false;
 260  0
         while (iterator.hasNext() && !found)
 261  
         {
 262  0
             key = iterator.next();
 263  0
             if (key.startsWith(identifier))
 264  
             {
 265  0
                 found = true;
 266  
             }
 267  
         }
 268  0
         return found;
 269  
     }
 270  
 
 271  
 
 272  
     /**
 273  
      * For single frames return the frame in this tag with given identifier if it exists, if multiple frames
 274  
      * exist with the same identifier it will return a list containing all the frames with this identifier
 275  
      * <p/>
 276  
      * Warning the match is only done against the identifier so if a tag contains a frame with an unsupported body
 277  
      * but happens to have an identifier that is valid for another version of the tag it will be returned.
 278  
      * <p/>
 279  
      *
 280  
      * @param identifier is an ID3Frame identifier
 281  
      * @return matching frame, or list of matching frames
 282  
      */
 283  
     //TODO:This method is problematic because sometimes it returns a list and sometimes a frame, we need to
 284  
     //replace with two seperate methods as in the tag interface.
 285  
     public Object getFrame(String identifier)
 286  
     {
 287  571
         return frameMap.get(identifier);
 288  
     }
 289  
 
 290  
     /**
 291  
      * Retrieve the first value that exists for this identifier
 292  
      * <p/>
 293  
      * If the value is a String it returns that, otherwise returns a summary of the fields information
 294  
      * <p/>
 295  
      *
 296  
      * @param identifier
 297  
      * @return
 298  
      */
 299  
     //TODO:we should be just be using the bodies toString() method so we dont have if statement in this method
 300  
     //but this is being used by something else at the moment
 301  
     public String getFirst(String identifier)
 302  
     {
 303  165
         AbstractID3v2Frame frame = getFirstField(identifier);
 304  165
         if (frame == null)
 305  
         {
 306  11
             return "";
 307  
         }
 308  154
         if (frame.getBody() instanceof FrameBodyCOMM)
 309  
         {
 310  11
             return ((FrameBodyCOMM) frame.getBody()).getText();
 311  
         }
 312  143
         else if (frame.getBody() instanceof FrameBodyUSLT)
 313  
         {
 314  1
             return ((FrameBodyUSLT) frame.getBody()).getFirstTextValue();
 315  
         }
 316  142
         else if (frame.getBody() instanceof AbstractFrameBodyTextInfo)
 317  
         {
 318  142
             return ((AbstractFrameBodyTextInfo) frame.getBody()).getFirstTextValue();
 319  
         }
 320  0
         else if (frame.getBody() instanceof AbstractFrameBodyUrlLink)
 321  
         {
 322  0
             return ((AbstractFrameBodyUrlLink) frame.getBody()).getUrlLink();
 323  
         }
 324  
         else
 325  
         {
 326  0
             return frame.getBody().toString();
 327  
         }
 328  
     }
 329  
 
 330  
     public TagField getFirstField(TagFieldKey genericKey) throws KeyNotFoundException
 331  
     {
 332  0
         List<TagField> fields = get(genericKey);
 333  0
         if(fields.size()>0)
 334  
         {
 335  0
             return fields.get(0);
 336  
         }
 337  0
         return null;
 338  
     }
 339  
 
 340  
 
 341  
 
 342  
     /**
 343  
      * Retrieve the first tagfield that exists for this identifier
 344  
      *
 345  
      * @param identifier
 346  
      * @return tag field or null if doesnt exist
 347  
      */
 348  
     public AbstractID3v2Frame getFirstField(String identifier)
 349  
     {
 350  175
         Object object = getFrame(identifier);
 351  175
         if (object == null)
 352  
         {
 353  11
             return null;
 354  
         }
 355  164
         if (object instanceof List)
 356  
         {
 357  0
             return ((List<AbstractID3v2Frame>) object).get(0);
 358  
         }
 359  
         else
 360  
         {
 361  164
             return (AbstractID3v2Frame) object;
 362  
         }
 363  
     }
 364  
 
 365  
     /**
 366  
      * Add a frame to this tag
 367  
      *
 368  
      * @param frame the frame to add
 369  
      *              <p/>
 370  
      *              <p/>
 371  
      *              Warning if frame(s) already exists for this identifier thay are overwritten
 372  
      *              <p/>
 373  
      */
 374  
     //TODO needs to ensure do not add an invalid frame for this tag
 375  
     //TODO what happens if already contains a list with this ID
 376  
     public void setFrame(AbstractID3v2Frame frame)
 377  
     {
 378  106
         frameMap.put(frame.getIdentifier(), frame);
 379  106
     }
 380  
 
 381  
     protected abstract ID3Frames getID3Frames();
 382  
 
 383  
     /**
 384  
      * @param field
 385  
      * @throws FieldDataInvalidException
 386  
      */
 387  
     public void set(TagField field) throws FieldDataInvalidException
 388  
     {
 389  98
         if (!(field instanceof AbstractID3v2Frame))
 390  
         {
 391  0
             throw new FieldDataInvalidException("Field " + field + " is not of type AbstractID3v2Frame");
 392  
         }
 393  
 
 394  98
         AbstractID3v2Frame newFrame = (AbstractID3v2Frame) field;
 395  
 
 396  98
         Object o = frameMap.get(field.getId());
 397  98
         if (o == null || (!getID3Frames().isMultipleAllowed(newFrame.getId())))
 398  
         {
 399  67
             frameMap.put(field.getId(), field);
 400  
         }
 401  31
         else if (o instanceof AbstractID3v2Frame)
 402  
         {
 403  11
             AbstractID3v2Frame oldFrame = (AbstractID3v2Frame) o;
 404  11
             if (newFrame.getBody() instanceof FrameBodyTXXX)
 405  
             {
 406  
                 //Different key so convert to list and add as new frame
 407  4
                 if (!((FrameBodyTXXX) newFrame.getBody()).getDescription()
 408  
                         .equals(((FrameBodyTXXX) oldFrame.getBody()).getDescription()))
 409  
                 {
 410  4
                     List<AbstractID3v2Frame> frames = new ArrayList<AbstractID3v2Frame>();
 411  4
                     frames.add(oldFrame);
 412  4
                     frames.add(newFrame);
 413  4
                     frameMap.put(newFrame.getId(), frames);
 414  4
                 }
 415  
                 //Same key so replace
 416  
                 else
 417  
                 {
 418  0
                     frameMap.put(newFrame.getId(), newFrame);
 419  
                 }
 420  
             }
 421  7
             else if (newFrame.getBody() instanceof FrameBodyWXXX)
 422  
             {
 423  
                 //Different key so convert to list and add as new frame
 424  3
                 if (!((FrameBodyWXXX) newFrame.getBody()).getDescription()
 425  
                         .equals(((FrameBodyWXXX) oldFrame.getBody()).getDescription()))
 426  
                 {
 427  3
                     List<AbstractID3v2Frame> frames = new ArrayList<AbstractID3v2Frame>();
 428  3
                     frames.add(oldFrame);
 429  3
                     frames.add(newFrame);
 430  3
                     frameMap.put(newFrame.getId(), frames);
 431  3
                 }
 432  
                 //Same key so replace
 433  
                 else
 434  
                 {
 435  0
                     frameMap.put(newFrame.getId(), newFrame);
 436  
                 }
 437  
             }
 438  4
             else if (newFrame.getBody() instanceof FrameBodyCOMM)
 439  
             {
 440  2
                 if (!((FrameBodyCOMM) newFrame.getBody()).getDescription()
 441  
                         .equals(((FrameBodyCOMM) oldFrame.getBody()).getDescription()))
 442  
                 {
 443  2
                     List<AbstractID3v2Frame> frames = new ArrayList<AbstractID3v2Frame>();
 444  2
                     frames.add(oldFrame);
 445  2
                     frames.add(newFrame);
 446  2
                     frameMap.put(newFrame.getId(), frames);
 447  2
                 }
 448  
                 //Same key so replace
 449  
                 else
 450  
                 {
 451  0
                     frameMap.put(newFrame.getId(), newFrame);
 452  
                 }
 453  
             }
 454  2
             else if (newFrame.getBody() instanceof FrameBodyUFID)
 455  
             {
 456  1
                 if (!((FrameBodyUFID) newFrame.getBody()).getOwner()
 457  
                         .equals(((FrameBodyUFID) oldFrame.getBody()).getOwner()))
 458  
                 {
 459  1
                     List<AbstractID3v2Frame> frames = new ArrayList<AbstractID3v2Frame>();
 460  1
                     frames.add(oldFrame);
 461  1
                     frames.add(newFrame);
 462  1
                     frameMap.put(newFrame.getId(), frames);
 463  1
                 }
 464  
                 //Same key so replace
 465  
                 else
 466  
                 {
 467  0
                     frameMap.put(newFrame.getId(), newFrame);
 468  
                 }
 469  
             }
 470  1
             else if (newFrame.getBody() instanceof FrameBodyUSLT)
 471  
             {
 472  1
                 if (!((FrameBodyUSLT) newFrame.getBody()).getDescription()
 473  
                         .equals(((FrameBodyUSLT) oldFrame.getBody()).getDescription()))
 474  
                 {
 475  1
                     List<AbstractID3v2Frame> frames = new ArrayList<AbstractID3v2Frame>();
 476  1
                     frames.add(oldFrame);
 477  1
                     frames.add(newFrame);
 478  1
                     frameMap.put(newFrame.getId(), frames);
 479  1
                 }
 480  
                 //Same key so replace
 481  
                 else
 482  
                 {
 483  0
                     frameMap.put(newFrame.getId(), newFrame);
 484  
                 }
 485  
             }
 486  0
             else if (newFrame.getBody() instanceof FrameBodyPOPM)
 487  
             {
 488  0
                 if (!((FrameBodyPOPM) newFrame.getBody()).getEmailToUser()
 489  
                         .equals(((FrameBodyPOPM) oldFrame.getBody()).getEmailToUser()))
 490  
                 {
 491  0
                     List<AbstractID3v2Frame> frames = new ArrayList<AbstractID3v2Frame>();
 492  0
                     frames.add(oldFrame);
 493  0
                     frames.add(newFrame);
 494  0
                     frameMap.put(newFrame.getId(), frames);
 495  0
                 }
 496  
                 //Same key so replace
 497  
                 else
 498  
                 {
 499  0
                     frameMap.put(newFrame.getId(), newFrame);
 500  
                 }
 501  
             }
 502  
             //Just add new one
 503  
             else
 504  
             {
 505  0
                 List<AbstractID3v2Frame> frames = new ArrayList<AbstractID3v2Frame>();
 506  0
                 frames.add(oldFrame);
 507  0
                 frames.add(newFrame);
 508  0
                 frameMap.put(newFrame.getId(), frames);
 509  
             }
 510  11
         }
 511  20
         else if (o instanceof List)
 512  
         {
 513  20
             for (ListIterator<AbstractID3v2Frame> li = ((List<AbstractID3v2Frame>) o).listIterator(); li.hasNext();)
 514  
             {
 515  52
                 AbstractID3v2Frame nextFrame = li.next();
 516  
 
 517  52
                 if (newFrame.getBody() instanceof FrameBodyTXXX)
 518  
                 {
 519  
                     //Value with matching key exists so replace
 520  8
                     if (((FrameBodyTXXX) newFrame.getBody()).getDescription()
 521  
                             .equals(((FrameBodyTXXX) nextFrame.getBody()).getDescription()))
 522  
                     {
 523  1
                         li.set(newFrame);
 524  1
                         frameMap.put(newFrame.getId(), o);
 525  1
                         return;
 526  
                     }
 527  
                 }
 528  44
                 else if (newFrame.getBody() instanceof FrameBodyWXXX)
 529  
                 {
 530  
                     //Value with matching key exists so replace
 531  27
                     if (((FrameBodyWXXX) newFrame.getBody()).getDescription()
 532  
                             .equals(((FrameBodyWXXX) nextFrame.getBody()).getDescription()))
 533  
                     {
 534  0
                         li.set(newFrame);
 535  0
                         frameMap.put(newFrame.getId(), o);
 536  0
                         return;
 537  
                     }
 538  
                 }
 539  17
                 else if (newFrame.getBody() instanceof FrameBodyCOMM)
 540  
                 {
 541  13
                     if (((FrameBodyCOMM) newFrame.getBody()).getDescription()
 542  
                             .equals(((FrameBodyCOMM) nextFrame.getBody()).getDescription()))
 543  
                     {
 544  3
                         li.set(newFrame);
 545  3
                         frameMap.put(newFrame.getId(), o);
 546  3
                         return;
 547  
                     }
 548  
                 }
 549  4
                 else if (newFrame.getBody() instanceof FrameBodyUFID)
 550  
                 {
 551  2
                     if (((FrameBodyUFID) newFrame.getBody()).getOwner()
 552  
                             .equals(((FrameBodyUFID) nextFrame.getBody()).getOwner()))
 553  
                     {
 554  1
                         li.set(newFrame);
 555  1
                         frameMap.put(newFrame.getId(), o);
 556  1
                         return;
 557  
                     }
 558  
                 }
 559  2
                 else if (newFrame.getBody() instanceof FrameBodyUSLT)
 560  
                 {
 561  2
                     if (((FrameBodyUSLT) newFrame.getBody()).getDescription()
 562  
                             .equals(((FrameBodyUSLT) nextFrame.getBody()).getDescription()))
 563  
                     {
 564  1
                         li.set(newFrame);
 565  1
                         frameMap.put(newFrame.getId(), o);
 566  1
                         return;
 567  
                     }
 568  
                 }
 569  0
                 else if (newFrame.getBody() instanceof FrameBodyPOPM)
 570  
                 {
 571  0
                     if (((FrameBodyPOPM) newFrame.getBody()).getEmailToUser()
 572  
                             .equals(((FrameBodyPOPM) nextFrame.getBody()).getEmailToUser()))
 573  
                     {
 574  0
                         li.set(newFrame);
 575  0
                         frameMap.put(newFrame.getId(), o);
 576  0
                         return;
 577  
                     }
 578  
                 }
 579  46
             }
 580  
             //No match found so add new one
 581  14
             ((List<AbstractID3v2Frame>) o).add(newFrame);
 582  
         }
 583  92
     }
 584  
 
 585  
     public void setAlbum(String s) throws FieldDataInvalidException
 586  
     {
 587  9
         if (s == null)
 588  
         {
 589  1
             throw new IllegalArgumentException(ErrorMessage.GENERAL_INVALID_NULL_ARGUMENT.getMsg());
 590  
         }
 591  8
         set(createAlbumField(s));
 592  8
     }
 593  
 
 594  
     public void setArtist(String s) throws FieldDataInvalidException
 595  
     {
 596  4
         if (s == null)
 597  
         {
 598  1
             throw new IllegalArgumentException(ErrorMessage.GENERAL_INVALID_NULL_ARGUMENT.getMsg());
 599  
         }
 600  3
         set(createArtistField(s));
 601  3
     }
 602  
 
 603  
     public void setComment(String s) throws FieldDataInvalidException
 604  
     {
 605  13
         if (s == null)
 606  
         {
 607  1
             throw new IllegalArgumentException(ErrorMessage.GENERAL_INVALID_NULL_ARGUMENT.getMsg());
 608  
         }
 609  12
         set(createCommentField(s));
 610  12
     }
 611  
 
 612  
     public void setGenre(String s) throws FieldDataInvalidException
 613  
     {
 614  7
         if (s == null)
 615  
         {
 616  1
             throw new IllegalArgumentException(ErrorMessage.GENERAL_INVALID_NULL_ARGUMENT.getMsg());
 617  
         }
 618  6
         set(createGenreField(s));
 619  6
     }
 620  
 
 621  
     public void setTitle(String s) throws FieldDataInvalidException
 622  
     {
 623  9
         if (s == null)
 624  
         {
 625  3
             throw new IllegalArgumentException(ErrorMessage.GENERAL_INVALID_NULL_ARGUMENT.getMsg());
 626  
         }
 627  6
         set(createTitleField(s));
 628  6
     }
 629  
 
 630  
     public void setTrack(String s) throws FieldDataInvalidException
 631  
     {
 632  5
         if (s == null)
 633  
         {
 634  1
             throw new IllegalArgumentException(ErrorMessage.GENERAL_INVALID_NULL_ARGUMENT.getMsg());
 635  
         }
 636  4
         set(createTrackField(s));
 637  4
     }
 638  
 
 639  
     public void setYear(String s) throws FieldDataInvalidException
 640  
     {
 641  4
         if (s == null)
 642  
         {
 643  0
             throw new IllegalArgumentException(ErrorMessage.GENERAL_INVALID_NULL_ARGUMENT.getMsg());
 644  
         }
 645  4
         set(createYearField(s));
 646  4
     }
 647  
 
 648  
     /**
 649  
      * @param field
 650  
      * @throws FieldDataInvalidException
 651  
      */
 652  
     public void add(TagField field) throws FieldDataInvalidException
 653  
     {
 654  12
         if (field == null)
 655  
         {
 656  0
             return;
 657  
         }
 658  
 
 659  12
         if (!(field instanceof AbstractID3v2Frame))
 660  
         {
 661  0
             throw new FieldDataInvalidException("Field " + field + " is not of type AbstractID3v2Frame");
 662  
         }
 663  
 
 664  12
         Object o = frameMap.get(field.getId());
 665  
 
 666  
         //There are already frames of this type
 667  12
         if (o instanceof List)
 668  
         {
 669  1
             List<TagField> list = (List<TagField>) o;
 670  1
             list.add(field);
 671  1
         }
 672  
         //No frame of this type
 673  11
         else if (o == null)
 674  
         {
 675  4
             frameMap.put(field.getId(), field);
 676  
         }
 677  
         //One frame exists, we are adding another so convert to list
 678  
         else
 679  
         {
 680  7
             List<TagField> list = new ArrayList<TagField>();
 681  7
             list.add((TagField) o);
 682  7
             list.add(field);
 683  7
             frameMap.put(field.getId(), list);
 684  
         }
 685  12
     }
 686  
 
 687  
     /**
 688  
      * Adds an album to the tag.<br>
 689  
      *
 690  
      * @param album Album description
 691  
      */
 692  
     public void addAlbum(String album) throws FieldDataInvalidException
 693  
     {
 694  4
         if (album == null)
 695  
         {
 696  0
             throw new IllegalArgumentException(ErrorMessage.GENERAL_INVALID_NULL_ARGUMENT.getMsg());
 697  
         }
 698  4
         add(createAlbumField(album));
 699  4
     }
 700  
 
 701  
     /**
 702  
      * Adds an artist to the tag.<br>
 703  
      *
 704  
      * @param artist Artist's name
 705  
      */
 706  
     public void addArtist(String artist) throws FieldDataInvalidException
 707  
     {
 708  0
         if (artist == null)
 709  
         {
 710  0
             throw new IllegalArgumentException(ErrorMessage.GENERAL_INVALID_NULL_ARGUMENT.getMsg());
 711  
         }
 712  0
         add(createArtistField(artist));
 713  0
     }
 714  
 
 715  
     /**
 716  
      * Adds a comment to the tag.<br>
 717  
      *
 718  
      * @param comment Comment.
 719  
      */
 720  
     public void addComment(String comment) throws FieldDataInvalidException
 721  
     {
 722  0
         if (comment == null)
 723  
         {
 724  0
             throw new IllegalArgumentException(ErrorMessage.GENERAL_INVALID_NULL_ARGUMENT.getMsg());
 725  
         }
 726  0
         add(createCommentField(comment));
 727  0
     }
 728  
 
 729  
     /**
 730  
      * Adds a genre to the tag.<br>
 731  
      *
 732  
      * @param genre Genre
 733  
      */
 734  
     public void addGenre(String genre) throws FieldDataInvalidException
 735  
     {
 736  0
         if (genre == null)
 737  
         {
 738  0
             throw new IllegalArgumentException(ErrorMessage.GENERAL_INVALID_NULL_ARGUMENT.getMsg());
 739  
         }
 740  0
         add(createGenreField(genre));
 741  0
     }
 742  
 
 743  
     /**
 744  
      * Adds a title to the tag.<br>
 745  
      *
 746  
      * @param title Title
 747  
      */
 748  
     public void addTitle(String title) throws FieldDataInvalidException
 749  
     {
 750  0
         if (title == null)
 751  
         {
 752  0
             throw new IllegalArgumentException(ErrorMessage.GENERAL_INVALID_NULL_ARGUMENT.getMsg());
 753  
         }
 754  0
         add(createTitleField(title));
 755  0
     }
 756  
 
 757  
     /**
 758  
      * Adds a track to the tag.<br>
 759  
      *
 760  
      * @param track Track
 761  
      */
 762  
     public void addTrack(String track) throws FieldDataInvalidException
 763  
     {
 764  0
         if (track == null)
 765  
         {
 766  0
             throw new IllegalArgumentException(ErrorMessage.GENERAL_INVALID_NULL_ARGUMENT.getMsg());
 767  
         }
 768  0
         add(createTrackField(track));
 769  0
     }
 770  
 
 771  
     /**
 772  
      * Adds a year to the Tag.<br>
 773  
      *
 774  
      * @param year Year
 775  
      */
 776  
     public void addYear(String year) throws FieldDataInvalidException
 777  
     {
 778  0
         if (year == null)
 779  
         {
 780  0
             throw new IllegalArgumentException(ErrorMessage.GENERAL_INVALID_NULL_ARGUMENT.getMsg());
 781  
         }
 782  0
         add(createYearField(year));
 783  0
     }
 784  
 
 785  
 
 786  
     /**
 787  
      * Used for setting multiple frames for a single frame Identifier
 788  
      * <p/>
 789  
      * Warning if frame(s) already exists for this identifier thay are overwritten
 790  
      * <p/>
 791  
      * TODO needs to ensure do not add an invalid frame for this tag
 792  
      */
 793  
     public void setFrame(String identifier, List<AbstractID3v2Frame> multiFrame)
 794  
     {
 795  0
         logger.finest("Adding "+multiFrame.size() + " frames for "+identifier);
 796  0
         frameMap.put(identifier, multiFrame);
 797  0
     }
 798  
 
 799  
     /**
 800  
      * Return the number of frames in this tag of a particular type, multiple frames
 801  
      * of the same time will only be counted once
 802  
      *
 803  
      * @return a count of different frames
 804  
      */
 805  
     public int getFrameCount()
 806  
     {
 807  7
         if (frameMap == null)
 808  
         {
 809  0
             return 0;
 810  
         }
 811  
         else
 812  
         {
 813  7
             return frameMap.size();
 814  
         }
 815  
     }
 816  
 
 817  
     /**
 818  
      * Return all frames which start with the identifier, this
 819  
      * can be more than one which is useful if trying to retrieve
 820  
      * similar frames e.g TIT1,TIT2,TIT3 ... and don't know exaclty
 821  
      * which ones there are.
 822  
      * <p/>
 823  
      * Warning the match is only done against the identifier so if a tag contains a frame with an unsupported body
 824  
      * but happens to have an identifier that is valid for another version of the tag it will be returned.
 825  
      *
 826  
      * @param identifier
 827  
      * @return an iterator of all the frames starting with a particular identifier
 828  
      */
 829  
     public Iterator getFrameOfType(String identifier)
 830  
     {
 831  0
         Iterator<String> iterator = frameMap.keySet().iterator();
 832  0
         HashSet result = new HashSet();
 833  
         String key;
 834  0
         while (iterator.hasNext())
 835  
         {
 836  0
             key = iterator.next();
 837  0
             if (key.startsWith(identifier))
 838  
             {
 839  0
                 result.add(frameMap.get(key));
 840  
             }
 841  
         }
 842  0
         return result.iterator();
 843  
     }
 844  
 
 845  
 
 846  
     /**
 847  
      * Delete Tag
 848  
      *
 849  
      * @param file to delete the tag from
 850  
      * @throws IOException if problem accessing the file
 851  
      *                     <p/>
 852  
      */
 853  
     //TODO should clear all data and preferably recover lost space and go upto end of mp3s 
 854  
     public void delete(RandomAccessFile file) throws IOException
 855  
     {
 856  
         // this works by just erasing the "ID3" tag at the beginning
 857  
         // of the file
 858  33
         byte[] buffer = new byte[FIELD_TAGID_LENGTH];
 859  
         //Read into Byte Buffer
 860  33
         final FileChannel fc = file.getChannel();
 861  33
         fc.position();
 862  33
         ByteBuffer byteBuffer = ByteBuffer.allocate(TAG_HEADER_LENGTH);
 863  33
         fc.read(byteBuffer, 0);
 864  33
         byteBuffer.flip();
 865  33
         if (seek(byteBuffer))
 866  
         {
 867  5
             file.seek(0L);
 868  5
             file.write(buffer);
 869  
         }
 870  33
     }
 871  
 
 872  
     /**
 873  
      * Is this tag equivalent to another
 874  
      *
 875  
      * @param obj to test for equivalence
 876  
      * @return true if they are equivalent
 877  
      */
 878  
     public boolean equals(Object obj)
 879  
     {
 880  0
         if ((obj instanceof AbstractID3v2Tag) == false)
 881  
         {
 882  0
             return false;
 883  
         }
 884  0
         AbstractID3v2Tag object = (AbstractID3v2Tag) obj;
 885  0
         if (this.frameMap.equals(object.frameMap) == false)
 886  
         {
 887  0
             return false;
 888  
         }
 889  0
         return super.equals(obj);
 890  
     }
 891  
 
 892  
 
 893  
     /**
 894  
      * Return the frames in the order they were added
 895  
      *
 896  
      * @return and iterator of the frmaes/list of multi value frames
 897  
      */
 898  
     public Iterator iterator()
 899  
     {
 900  0
         return frameMap.values().iterator();
 901  
     }
 902  
 
 903  
     /**
 904  
      * Remove frame(s) with this identifier from tag
 905  
      *
 906  
      * @param identifier frameId to look for
 907  
      */
 908  
     public void removeFrame(String identifier)
 909  
     {
 910  12
         logger.finest("Removing frame with identifier:" + identifier);
 911  12
         frameMap.remove(identifier);
 912  12
     }
 913  
 
 914  
     /**
 915  
      * Remove all frame(s) which have an unsupported body, in other words
 916  
      * remove all frames that are not part of the standard frameset for
 917  
      * this tag
 918  
      */
 919  
     public void removeUnsupportedFrames()
 920  
     {
 921  0
         for (Iterator i = iterator(); i.hasNext();)
 922  
         {
 923  0
             Object o = i.next();
 924  0
             if (o instanceof AbstractID3v2Frame)
 925  
             {
 926  0
                 if (((AbstractID3v2Frame) o).getBody() instanceof FrameBodyUnsupported)
 927  
                 {
 928  0
                     logger.finest("Removing frame" + ((AbstractID3v2Frame) o).getIdentifier());
 929  0
                     i.remove();
 930  
                 }
 931  
             }
 932  0
         }
 933  0
     }
 934  
 
 935  
     /**
 936  
      * Remove any frames starting with this identifier from tag
 937  
      *
 938  
      * @param identifier start of frameId to look for
 939  
      */
 940  
     public void removeFrameOfType(String identifier)
 941  
     {
 942  
         //First fine matching keys
 943  2
         HashSet<String> result = new HashSet<String>();
 944  2
         for (Object match : frameMap.keySet())
 945  
         {
 946  15
             String key = (String) match;
 947  15
             if (key.startsWith(identifier))
 948  
             {
 949  2
                 result.add(key);
 950  
             }
 951  15
         }
 952  
         //Then delete outside of loop to prevent concurrent modificatioon eception if there are two keys
 953  
         //with the same id
 954  2
         for (String match : result)
 955  
         {
 956  2
             logger.finest("Removing frame with identifier:" + match + "because starts with:" + identifier);
 957  2
             frameMap.remove(match);
 958  
         }
 959  2
     }
 960  
 
 961  
 
 962  
     /**
 963  
      * Write tag to file.
 964  
      *
 965  
      * @param file
 966  
      * @param audioStartByte
 967  
      * @throws IOException TODO should be abstract
 968  
      */
 969  
     public void write(File file, long audioStartByte) throws IOException
 970  
     {
 971  0
     }
 972  
 
 973  
     /**
 974  
      * Get file lock for writing too file
 975  
      * <p/>
 976  
      * TODO:this appears to have little affect on Windows Vista
 977  
      *
 978  
      * @param fileChannel
 979  
      * @return lock or null if locking is not supported
 980  
      * @throws IOException if unable to get lock because already locked
 981  
      */
 982  
     protected FileLock getFileLockForWriting(FileChannel fileChannel, String filePath) throws IOException
 983  
     {
 984  376
         FileLock fileLock = null;
 985  
         try
 986  
         {
 987  376
             fileLock = fileChannel.tryLock();
 988  
         }
 989  
         //Assumes locking is not supported on this platform so just returns null
 990  0
         catch (IOException exception)
 991  
         {
 992  0
             return null;
 993  376
         }
 994  
 
 995  
         //Couldnt get lock because file is already locked by another application
 996  376
         if (fileLock == null)
 997  
         {
 998  0
             throw new IOException(ErrorMessage.GENERAL_WRITE_FAILED_FILE_LOCKED.getMsg(filePath));
 999  
         }
 1000  376
         return fileLock;
 1001  
     }
 1002  
 
 1003  
     /**
 1004  
      * Write tag to file.
 1005  
      *
 1006  
      * @param file
 1007  
      * @throws IOException TODO should be abstract
 1008  
      */
 1009  
     public void write(RandomAccessFile file) throws IOException
 1010  
     {
 1011  0
     }
 1012  
 
 1013  
     /**
 1014  
      * Write tag to channel.
 1015  
      *
 1016  
      * @param channel
 1017  
      * @throws IOException TODO should be abstract
 1018  
      */
 1019  
     public void write(WritableByteChannel channel) throws IOException
 1020  
     {
 1021  0
     }
 1022  
 
 1023  
 
 1024  
     /**
 1025  
      * Checks to see if the file contains an ID3tag and if so return its size as reported in
 1026  
      * the tag header  and return the size of the tag (including header), if no such tag exists return
 1027  
      * zero.
 1028  
      *
 1029  
      * @param file
 1030  
      * @return the end of the tag in the file or zero if no tag exists.
 1031  
      */
 1032  
     public static long getV2TagSizeIfExists(File file) throws IOException
 1033  
     {
 1034  446
         FileInputStream fis = null;
 1035  446
         FileChannel fc = null;
 1036  446
         ByteBuffer bb = null;
 1037  
         try
 1038  
         {
 1039  
             //Files
 1040  446
             fis = new FileInputStream(file);
 1041  446
             fc = fis.getChannel();
 1042  
 
 1043  
             //Read possible Tag header  Byte Buffer
 1044  446
             bb = ByteBuffer.allocate(TAG_HEADER_LENGTH);
 1045  446
             fc.read(bb);
 1046  446
             bb.flip();
 1047  446
             if (bb.limit() < (TAG_HEADER_LENGTH))
 1048  
             {
 1049  0
                 return 0;
 1050  
             }
 1051  
         }
 1052  
         finally
 1053  
         {
 1054  446
             if (fc != null)
 1055  
             {
 1056  446
                 fc.close();
 1057  
             }
 1058  
 
 1059  446
             if (fis != null)
 1060  
             {
 1061  446
                 fis.close();
 1062  
             }
 1063  
         }
 1064  
 
 1065  
         //ID3 identifier
 1066  446
         byte[] tagIdentifier = new byte[FIELD_TAGID_LENGTH];
 1067  446
         bb.get(tagIdentifier, 0, FIELD_TAGID_LENGTH);
 1068  446
         if (!(Arrays.equals(tagIdentifier, TAG_ID)))
 1069  
         {
 1070  139
             return 0;
 1071  
         }
 1072  
 
 1073  
         //Is it valid Major Version
 1074  307
         byte majorVersion = bb.get();
 1075  307
         if ((majorVersion != ID3v22Tag.MAJOR_VERSION) && (majorVersion != ID3v23Tag.MAJOR_VERSION) && (majorVersion != ID3v24Tag.MAJOR_VERSION))
 1076  
         {
 1077  0
             return 0;
 1078  
         }
 1079  
 
 1080  
         //Skip Minor Version
 1081  307
         bb.get();
 1082  
 
 1083  
         //Skip Flags
 1084  307
         bb.get();
 1085  
 
 1086  
         //Get size as recorded in frame header
 1087  307
         int frameSize = ID3SyncSafeInteger.bufferToValue(bb);
 1088  
 
 1089  
         //add header size to frame size
 1090  307
         frameSize += TAG_HEADER_LENGTH;
 1091  307
         return frameSize;
 1092  
     }
 1093  
 
 1094  
     /**
 1095  
      * Does a tag of the correct version exist in this file.
 1096  
      *
 1097  
      * @param byteBuffer to search through
 1098  
      * @return true if tag exists.
 1099  
      */
 1100  
     public boolean seek(ByteBuffer byteBuffer)
 1101  
     {
 1102  594
         byteBuffer.rewind();
 1103  594
         logger.info("ByteBuffer pos:" + byteBuffer.position() + ":limit" + byteBuffer.limit() + ":cap" + byteBuffer.capacity());
 1104  
 
 1105  
 
 1106  594
         byte[] tagIdentifier = new byte[FIELD_TAGID_LENGTH];
 1107  594
         byteBuffer.get(tagIdentifier, 0, FIELD_TAGID_LENGTH);
 1108  594
         if (!(Arrays.equals(tagIdentifier, TAG_ID)))
 1109  
         {
 1110  35
             return false;
 1111  
         }
 1112  
         //Major Version
 1113  559
         if (byteBuffer.get() != getMajorVersion())
 1114  
         {
 1115  238
             return false;
 1116  
         }
 1117  
         //Minor Version
 1118  321
         if (byteBuffer.get() != getRevision())
 1119  
         {
 1120  0
             return false;
 1121  
         }
 1122  321
         return true;
 1123  
     }
 1124  
 
 1125  
     /**
 1126  
      * This method determines the total tag size taking into account
 1127  
      * where the audio file starts, the size of the tagging data and
 1128  
      * user options for defining how tags should shrink or grow.
 1129  
      */
 1130  
     protected int calculateTagSize(int tagSize, int audioStart)
 1131  
     {
 1132  
         /** We can fit in the tag so no adjustments required */
 1133  250
         if (tagSize <= audioStart)
 1134  
         {
 1135  124
             return audioStart;
 1136  
         }
 1137  
         /** There is not enough room as we need to move the audio file we might
 1138  
          *  as well increase it more than neccessary for future changes
 1139  
          */
 1140  126
         return tagSize + TAG_SIZE_INCREMENT;
 1141  
     }
 1142  
 
 1143  
     /**
 1144  
      * Adjust the length of the  padding at the beginning of the MP3 file, this is only called when there is currently
 1145  
      * not enough space before the start of the audio to write the tag.
 1146  
      * <p/>
 1147  
      * A new file will be created with enough size to fit the <code>ID3v2</code> tag.
 1148  
      * The old file will be deleted, and the new file renamed.
 1149  
      *
 1150  
      * @param paddingSize This is total size required to store tag before audio
 1151  
      * @param file        The file to adjust the padding length of
 1152  
      * @throws FileNotFoundException if the file exists but is a directory
 1153  
      *                               rather than a regular file or cannot be opened for any other
 1154  
      *                               reason
 1155  
      * @throws IOException           on any I/O error
 1156  
      */
 1157  
     public void adjustPadding(File file, int paddingSize, long audioStart) throws FileNotFoundException, IOException
 1158  
     {           
 1159  126
         logger.finer("Need to move audio file to accomodate tag");
 1160  126
         FileChannel fcIn=null;
 1161  126
         FileChannel fcOut=null;
 1162  
 
 1163  
         //Create buffer holds the neccessary padding
 1164  126
         ByteBuffer paddingBuffer = ByteBuffer.wrap(new byte[paddingSize]);
 1165  
 
 1166  
         //Create Temporary File and write channel, make sure it is locked        
 1167  
         File paddedFile;
 1168  
 
 1169  
         try
 1170  
         {
 1171  126
             paddedFile=File.createTempFile(Utils.getMinBaseFilenameAllowedForTempFile(file), ".new", file.getParentFile());
 1172  
         }
 1173  
         //Vista:Can occur if have Write permission on folder this file would be created in Denied
 1174  0
         catch(IOException ioe)
 1175  
         {
 1176  0
             logger.log(Level.SEVERE,ioe.getMessage(),ioe);
 1177  0
             if(ioe.getMessage().equals(FileSystemMessage.ACCESS_IS_DENIED.getMsg()))
 1178  
             {
 1179  0
                 logger.severe(ErrorMessage.GENERAL_WRITE_FAILED_TO_CREATE_TEMPORARY_FILE_IN_FOLDER.getMsg(file.getName(),file.getParentFile().getPath()));
 1180  0
                 throw new UnableToCreateFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_CREATE_TEMPORARY_FILE_IN_FOLDER.getMsg(file.getName(),file.getParentFile().getPath()));
 1181  
             }
 1182  
             else
 1183  
             {
 1184  0
                 logger.severe(ErrorMessage.GENERAL_WRITE_FAILED_TO_CREATE_TEMPORARY_FILE_IN_FOLDER.getMsg(file.getName(),file.getParentFile().getPath()));
 1185  0
                 throw new UnableToCreateFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_CREATE_TEMPORARY_FILE_IN_FOLDER.getMsg(file.getName(),file.getParentFile().getPath()));
 1186  
             }
 1187  126
         }
 1188  
 
 1189  
         try
 1190  
         {
 1191  126
             fcOut = new FileOutputStream(paddedFile).getChannel();
 1192  
         }
 1193  
         //Vista:Can occur if have special permission Create Folder/Append Data denied
 1194  0
         catch(FileNotFoundException ioe)
 1195  
         {
 1196  0
             logger.log(Level.SEVERE,ioe.getMessage(),ioe);
 1197  0
             logger.severe(ErrorMessage.GENERAL_WRITE_FAILED_TO_MODIFY_TEMPORARY_FILE_IN_FOLDER.getMsg(file.getName(),file.getParentFile().getPath()));
 1198  0
             throw new UnableToModifyFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_MODIFY_TEMPORARY_FILE_IN_FOLDER.getMsg(file.getName(),file.getParentFile().getPath()));
 1199  126
         }
 1200  
 
 1201  126
         FileLock fileTmpLock = null;
 1202  
         try
 1203  
         {
 1204  
             //Lock file is possible, only throws exception if already locked
 1205  126
             fileTmpLock = getFileLockForWriting(fcOut, paddedFile.getPath());
 1206  
 
 1207  
             //Create read channel from original file and lock so cant be modified by anything else
 1208  126
             fcIn        = new FileInputStream(file).getChannel();
 1209  
 
 1210  
             //Write padding to new file (this is where the tag will be written to later)
 1211  126
             long written = fcOut.write(paddingBuffer);
 1212  
 
 1213  
             //Write rest of file starting from audio
 1214  126
             logger.finer("Copying:" + (file.length() - audioStart) + "bytes");
 1215  
 
 1216  
             //If the amount to be copied is very large we split into 10MB lumps to try and avoid
 1217  
             //out of memory errors
 1218  126
             long audiolength = file.length() - audioStart;
 1219  126
             if (audiolength <= MAXIMUM_WRITABLE_CHUNK_SIZE)
 1220  
             {
 1221  126
                 long written2 = fcIn.transferTo(audioStart, audiolength, fcOut);
 1222  126
                 logger.finer("Written padding:" + written + " Data:" + written2);
 1223  126
                 if (written2 != audiolength)
 1224  
                 {
 1225  0
                     throw new RuntimeException(ErrorMessage.MP3_UNABLE_TO_ADJUST_PADDING.getMsg(audiolength,written2));
 1226  
                 }
 1227  126
             }
 1228  
             else
 1229  
             {
 1230  0
                 long noOfChunks = audiolength / MAXIMUM_WRITABLE_CHUNK_SIZE;
 1231  0
                 long lastChunkSize = audiolength % MAXIMUM_WRITABLE_CHUNK_SIZE;
 1232  0
                 long written2 = 0;
 1233  0
                 for (int i = 0; i < noOfChunks; i++)
 1234  
                 {
 1235  0
                     written2 += fcIn.transferTo(audioStart + (i * MAXIMUM_WRITABLE_CHUNK_SIZE), MAXIMUM_WRITABLE_CHUNK_SIZE, fcOut);
 1236  
                     //Try and recover memory as quick as possible
 1237  0
                     Runtime.getRuntime().gc();
 1238  
                 }
 1239  0
                 written2 += fcIn.transferTo(audioStart + (noOfChunks * MAXIMUM_WRITABLE_CHUNK_SIZE), lastChunkSize, fcOut);
 1240  0
                 logger.finer("Written padding:" + written + " Data:" + written2);
 1241  0
                 if (written2 != audiolength)
 1242  
                 {
 1243  0
                     throw new RuntimeException(ErrorMessage.MP3_UNABLE_TO_ADJUST_PADDING.getMsg(audiolength,written2));
 1244  
                 }
 1245  
             }
 1246  
 
 1247  
             //Store original modification time
 1248  126
             long lastModified = file.lastModified();
 1249  
 
 1250  
             //Close Channels and locks
 1251  126
             if (fcIn != null)
 1252  
             {
 1253  126
                 if(fcIn.isOpen())
 1254  
                 {
 1255  126
                     fcIn.close();
 1256  
                 }
 1257  
             }
 1258  
 
 1259  126
             if (fcOut != null)
 1260  
             {
 1261  126
                 if(fcOut.isOpen())
 1262  
                 {
 1263  126
                     if (fileTmpLock != null)
 1264  
                     {
 1265  126
                          if(fileTmpLock.isValid())
 1266  
                          {
 1267  126
                             fileTmpLock.release();
 1268  
                          }
 1269  
                     }
 1270  126
                     fcOut.close();
 1271  
                 }
 1272  
             }
 1273  
 
 1274  
             //Replace file with paddedFile
 1275  126
             replaceFile(file,paddedFile);
 1276  
 
 1277  
             //Update modification time
 1278  126
             paddedFile.setLastModified(lastModified);
 1279  126
         }
 1280  
         finally
 1281  
         {
 1282  0
             try
 1283  
             {
 1284  
                 //Whatever happens ensure all locks and channels are closed/released
 1285  126
                 if (fcIn != null)
 1286  
                 {
 1287  126
                     if(fcIn.isOpen())
 1288  
                     {
 1289  0
                         fcIn.close();
 1290  
                     }
 1291  
                 }
 1292  
 
 1293  126
                 if (fcOut != null)
 1294  
                 {
 1295  126
                     if(fcOut.isOpen())
 1296  
                     {
 1297  0
                         if (fileTmpLock != null)
 1298  
                         {
 1299  0
                              if(fileTmpLock.isValid())
 1300  
                              {
 1301  0
                                 fileTmpLock.release();
 1302  
                              }
 1303  
                         }
 1304  0
                         fcOut.close();
 1305  
                     }
 1306  
                 }
 1307  
             }
 1308  0
             catch(Exception e)
 1309  
             {
 1310  0
                 logger.log(Level.WARNING,"Problem closing channels and locks:"+e.getMessage(),e);
 1311  252
             }
 1312  126
         }
 1313  126
     }
 1314  
 
 1315  
     /**
 1316  
      * Write the data from the buffer to the file
 1317  
      *
 1318  
      * @param file
 1319  
      * @param headerBuffer
 1320  
      * @param bodyByteBuffer
 1321  
      * @param padding
 1322  
      * @param sizeIncPadding
 1323  
      * @param audioStartLocation
 1324  
      * @throws IOException
 1325  
      */
 1326  
     protected void writeBufferToFile(File file,ByteBuffer headerBuffer, byte[] bodyByteBuffer,int padding,int sizeIncPadding,long audioStartLocation)
 1327  
             throws IOException
 1328  
     {
 1329  250
         FileChannel fc = null;
 1330  250
         FileLock fileLock = null;
 1331  
 
 1332  
         //We need to adjust location of audio File if true
 1333  250
         if (sizeIncPadding > audioStartLocation)
 1334  
         {
 1335  126
              logger.finest("Adjusting Padding");
 1336  126
              adjustPadding(file, sizeIncPadding, audioStartLocation);
 1337  
         }
 1338  
 
 1339  
         try
 1340  
         {
 1341  250
             fc = new RandomAccessFile(file, "rw").getChannel();
 1342  250
             fileLock = getFileLockForWriting(fc, file.getPath());
 1343  250
             fc.write(headerBuffer);
 1344  250
             fc.write(ByteBuffer.wrap(bodyByteBuffer));
 1345  250
             fc.write(ByteBuffer.wrap(new byte[padding]));
 1346  250
         }
 1347  0
         catch(FileNotFoundException fe)
 1348  
         {
 1349  0
             logger.log(Level.SEVERE,getLoggingFilename() + fe.getMessage(),fe);
 1350  0
             if(fe.getMessage().equals(FileSystemMessage.ACCESS_IS_DENIED.getMsg()))
 1351  
             {
 1352  0
                 logger.severe(ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg(file.getPath()));
 1353  0
                 throw new UnableToModifyFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg(file.getPath()));
 1354  
             }
 1355  
             else
 1356  
             {
 1357  0
                 logger.severe(ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg(file.getPath()));
 1358  0
                 throw new UnableToCreateFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg(file.getPath()));
 1359  
             }
 1360  
         }
 1361  0
         catch(IOException ioe)
 1362  
         {
 1363  0
             logger.log(Level.SEVERE,getLoggingFilename() + ioe.getMessage(),ioe);
 1364  0
             if(ioe.getMessage().equals(FileSystemMessage.ACCESS_IS_DENIED.getMsg()))
 1365  
             {
 1366  0
                 logger.severe(ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg(file.getParentFile().getPath()));
 1367  0
                 throw new UnableToModifyFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg(file.getParentFile().getPath()));
 1368  
             }
 1369  
             else
 1370  
             {
 1371  0
                 logger.severe(ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg(file.getParentFile().getPath()));
 1372  0
                 throw new UnableToCreateFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg(file.getParentFile().getPath()));
 1373  
             }
 1374  
         }
 1375  
         finally
 1376  
         {
 1377  0
             if (fc != null)
 1378  
             {
 1379  250
                 if (fileLock != null)
 1380  
                 {
 1381  250
                     fileLock.release();
 1382  
                 }
 1383  250
                 fc.close();
 1384  
             }
 1385  250
         }
 1386  250
     }
 1387  
     /**
 1388  
      * Replace originalFile with the contents of newFile
 1389  
      *
 1390  
      * Both files must exist in the same folder so that there are no problems with fileystem mount points
 1391  
      *
 1392  
      * @param newFile
 1393  
      * @param originalFile
 1394  
      * @throws IOException
 1395  
      */
 1396  
     private void replaceFile(File originalFile,File newFile) throws IOException
 1397  
     {
 1398  
         //Rename Original File to make a backup in case problem with new file
 1399  126
         File originalFileBackup = new File(originalFile.getParentFile().getPath(), AudioFile.getBaseFilename(originalFile)+ ".old");
 1400  126
         boolean renameOriginalResult = originalFile.renameTo(originalFileBackup);
 1401  126
         if (!renameOriginalResult)
 1402  
         {
 1403  0
             logger.warning(ErrorMessage.GENERAL_WRITE_FAILED_TO_RENAME_ORIGINAL_FILE_TO_BACKUP.getMsg(originalFile.getAbsolutePath(), originalFileBackup.getName()));
 1404  0
             throw new UnableToRenameFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_RENAME_ORIGINAL_FILE_TO_BACKUP.getMsg(originalFile.getAbsolutePath(), originalFileBackup.getName()));
 1405  
         }
 1406  
 
 1407  
         //Rename new Temporary file to the final file
 1408  126
         boolean renameResult = newFile.renameTo(originalFile);
 1409  126
         if (!renameResult)
 1410  
         {
 1411  
             //Renamed failed so lets do some checks rename the backup back to the original file
 1412  
             //New File doesnt exist
 1413  0
             if(!newFile.exists())
 1414  
             {
 1415  0
                 logger.warning(ErrorMessage.GENERAL_WRITE_FAILED_NEW_FILE_DOESNT_EXIST.getMsg(newFile.getAbsolutePath()));
 1416  
             }
 1417  
 
 1418  
             //Rename the backup back to the original
 1419  0
             renameOriginalResult = originalFileBackup.renameTo(originalFile);
 1420  0
             if(!renameOriginalResult)
 1421  
             {
 1422  
                 //TODO now if this happens we are left with testfile.old instead of testfile.mp3
 1423  0
                 logger.warning(ErrorMessage.GENERAL_WRITE_FAILED_TO_RENAME_ORIGINAL_BACKUP_TO_ORIGINAL.getMsg(originalFileBackup.getAbsolutePath(),originalFile.getName()));
 1424  
             }
 1425  
 
 1426  
 
 1427  0
             logger.warning(ErrorMessage.GENERAL_WRITE_FAILED_TO_RENAME_TO_ORIGINAL_FILE.getMsg(originalFile.getAbsolutePath(), newFile.getName()));
 1428  0
             throw new UnableToRenameFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_RENAME_TO_ORIGINAL_FILE.getMsg(originalFile.getAbsolutePath(), newFile.getName()));
 1429  
         }
 1430  
         else
 1431  
         {
 1432  
             //Rename was okay so we can now delete the backup of the original
 1433  126
             boolean deleteResult=originalFileBackup.delete();
 1434  126
             if(!deleteResult)
 1435  
             {
 1436  
                 //Not a disaster but can't delete the backup so make a warning
 1437  0
                 logger.warning(ErrorMessage.GENERAL_WRITE_WARNING_UNABLE_TO_DELETE_BACKUP_FILE.getMsg(originalFileBackup.getAbsolutePath()));
 1438  
             }
 1439  
         }
 1440  126
     }
 1441  
     
 1442  
     /**
 1443  
      * Add frame to HashMap used when converting between tag versions, take into account
 1444  
      * occurences when two frame may both map to a single frame when converting between
 1445  
      * versions
 1446  
      * <p/>
 1447  
      * TODO the logic here is messy and seems to be specific to date fields only when it
 1448  
      * was intended to be generic.
 1449  
      */
 1450  
     protected void copyFrameIntoMap(String id, AbstractID3v2Frame newFrame)
 1451  
     {
 1452  
         /* The frame already exists this shouldnt normally happen because frames
 1453  
         * that are allowed to be multiple don't call this method. Frames that
 1454  
         * arent allowed to be multiple aren't added to hashmap in first place when
 1455  
         * originally added.
 1456  
         *
 1457  
         * We only want to allow one of the frames going forward but we try and merge
 1458  
         * all the information into the one frame. However there is a problem here that
 1459  
         * if we then take this, modify it and try to write back the original values
 1460  
         * we could lose some information although this info is probably invalid anyway.
 1461  
         *
 1462  
         * However converting some frames from tag of one version to another may
 1463  
         * mean that two different frames both get converted to one frame, this
 1464  
         * particulary applies to DateTime fields which were originally two fields
 1465  
         * in v2.3 but are one field in v2.4.
 1466  
         */
 1467  682
         if (frameMap.containsKey(newFrame.getIdentifier()))
 1468  
         {
 1469  
             //Retrieve the frame with the same id we have already loaded into the map
 1470  6
             AbstractID3v2Frame firstFrame = (AbstractID3v2Frame) frameMap.get(newFrame.getIdentifier());
 1471  
 
 1472  
             /* Two different frames both converted to TDRCFrames, now if this is the case one of them
 1473  
             * may have actually have been created as a FrameUnsupportedBody because TDRC is only
 1474  
             * supported in ID3v24, but is often created in v23 tags as well together with the valid TYER
 1475  
             * frame
 1476  
             */
 1477  6
             if (newFrame.getBody() instanceof FrameBodyTDRC)
 1478  
             {
 1479  6
                 if (firstFrame.getBody() instanceof FrameBodyTDRC)
 1480  
                 {
 1481  5
                     logger.finest("Modifying frame in map:" + newFrame.getIdentifier());
 1482  5
                     FrameBodyTDRC body = (FrameBodyTDRC) firstFrame.getBody();
 1483  5
                     FrameBodyTDRC newBody = (FrameBodyTDRC) newFrame.getBody();
 1484  
                     //Just add the data to the frame
 1485  5
                     if (newBody.getOriginalID().equals(ID3v23Frames.FRAME_ID_V3_TYER))
 1486  
                     {
 1487  0
                         body.setYear(newBody.getText());
 1488  
                     }
 1489  5
                     else if (newBody.getOriginalID().equals(ID3v23Frames.FRAME_ID_V3_TDAT))
 1490  
                     {
 1491  3
                         body.setDate(newBody.getText());
 1492  
                     }
 1493  2
                     else if (newBody.getOriginalID().equals(ID3v23Frames.FRAME_ID_V3_TIME))
 1494  
                     {
 1495  2
                         body.setTime(newBody.getText());
 1496  
                     }
 1497  0
                     else if (newBody.getOriginalID().equals(ID3v23Frames.FRAME_ID_V3_TRDA))
 1498  
                     {
 1499  0
                         body.setReco(newBody.getText());
 1500  
                     }
 1501  5
                 }
 1502  
                 /* The first frame was a TDRC frame that was not really allowed, this new frame was probably a
 1503  
                 * valid frame such as TYER which has been converted to TDRC, replace the firstframe with this frame
 1504  
                 */
 1505  1
                 else if (firstFrame.getBody() instanceof FrameBodyUnsupported)
 1506  
                 {
 1507  1
                     frameMap.put(newFrame.getIdentifier(), newFrame);
 1508  
                 }
 1509  
                 else
 1510  
                 {
 1511  
                     //we just lose this frame, weve already got one with the correct id.
 1512  
                     //TODO may want to store this somewhere
 1513  0
                     logger.warning("Found duplicate TDRC frame in invalid situation,discarding:" + newFrame.getIdentifier());
 1514  
                 }
 1515  
             }
 1516  
             else
 1517  
             {
 1518  0
                 logger.warning("Found duplicate frame in invalid situation,discarding:" + newFrame.getIdentifier());
 1519  
             }
 1520  6
         }
 1521  
         else
 1522  
         //Just add frame to map
 1523  
         {
 1524  676
             logger.finest("Adding frame to map:" + newFrame.getIdentifier());
 1525  676
             frameMap.put(newFrame.getIdentifier(), newFrame);
 1526  
         }
 1527  682
     }
 1528  
 
 1529  
     /**
 1530  
      * Decides what to with the frame that has just be read from file.
 1531  
      * If the frame is an allowable duplicate frame and is a duplicate we add all
 1532  
      * frames into an ArrayList and add the Arraylist to the hashMap. if not allowed
 1533  
      * to be duplicate we store bytes in the duplicateBytes variable.
 1534  
      */
 1535  
     protected void loadFrameIntoMap(String frameId, AbstractID3v2Frame next)
 1536  
     {
 1537  1562
         if ((ID3v24Frames.getInstanceOf().isMultipleAllowed(frameId)) || (ID3v23Frames.getInstanceOf().isMultipleAllowed(frameId)) || (ID3v22Frames.getInstanceOf().isMultipleAllowed(frameId)))
 1538  
         {
 1539  
             //If a frame already exists of this type
 1540  586
             if (frameMap.containsKey(frameId))
 1541  
             {
 1542  299
                 Object o = frameMap.get(frameId);
 1543  299
                 if (o instanceof ArrayList)
 1544  
                 {
 1545  222
                     ArrayList<AbstractID3v2Frame> multiValues = (ArrayList<AbstractID3v2Frame>) o;
 1546  222
                     multiValues.add(next);
 1547  222
                     logger.finer("Adding Multi Frame(1)" + frameId);
 1548  222
                 }
 1549  
                 else
 1550  
                 {
 1551  77
                     ArrayList<AbstractID3v2Frame> multiValues = new ArrayList<AbstractID3v2Frame>();
 1552  77
                     multiValues.add((AbstractID3v2Frame) o);
 1553  77
                     multiValues.add(next);
 1554  77
                     frameMap.put(frameId, multiValues);
 1555  77
                     logger.finer("Adding Multi Frame(2)" + frameId);
 1556  
                 }
 1557  299
             }
 1558  
             else
 1559  
             {
 1560  287
                 logger.finer("Adding Multi FrameList(3)" + frameId);
 1561  287
                 frameMap.put(frameId, next);
 1562  
             }
 1563  
         }
 1564  
         //If duplicate frame just stores it somewhere else
 1565  976
         else if (frameMap.containsKey(frameId))
 1566  
         {
 1567  3
             logger.warning("Duplicate Frame" + frameId);
 1568  3
             this.duplicateFrameId += (frameId + "; ");
 1569  3
             this.duplicateBytes += ((AbstractID3v2Frame) frameMap.get(frameId)).getSize();
 1570  
         }
 1571  
         else
 1572  
         {
 1573  973
             logger.finer("Adding Frame" + frameId);
 1574  973
             frameMap.put(frameId, next);
 1575  
         }
 1576  1562
     }
 1577  
 
 1578  
     /**
 1579  
      * Return tag size based upon the sizes of the tags rather than the physical
 1580  
      * no of bytes between start of ID3Tag and start of Audio Data.Should be extended
 1581  
      * by subclasses to include header.
 1582  
      *
 1583  
      * @return size of the tag
 1584  
      */
 1585  
     public int getSize()
 1586  
     {
 1587  0
         int size = 0;
 1588  0
         Iterator iterator = frameMap.values().iterator();
 1589  
         AbstractID3v2Frame frame;
 1590  0
         while (iterator.hasNext())
 1591  
         {
 1592  0
             Object o = iterator.next();
 1593  0
             if (o instanceof AbstractID3v2Frame)
 1594  
             {
 1595  0
                 frame = (AbstractID3v2Frame) o;
 1596  0
                 size += frame.getSize();
 1597  
             }
 1598  
             else
 1599  
             {
 1600  0
                 ArrayList<AbstractID3v2Frame> multiFrames = (ArrayList<AbstractID3v2Frame>) o;
 1601  0
                 for (ListIterator<AbstractID3v2Frame> li = multiFrames.listIterator(); li.hasNext();)
 1602  
                 {
 1603  0
                     frame = li.next();
 1604  0
                     size += frame.getSize();
 1605  
                 }
 1606  
             }
 1607  0
         }
 1608  0
         return size;
 1609  
     }
 1610  
 
 1611  
     /**
 1612  
      * Write all the frames to the byteArrayOutputStream
 1613  
      * <p/>
 1614  
      * <p>Currently Write all frames, defaults to the order in which they were loaded, newly
 1615  
      * created frames will be at end of tag.
 1616  
      *
 1617  
      * @return ByteBuffer Contains all the frames written within the tag ready for writing to file
 1618  
      * @throws IOException
 1619  
      */    
 1620  
     protected ByteArrayOutputStream writeFramesToBuffer() throws IOException
 1621  
     {
 1622  
         //Increases as is required
 1623  250
         ByteArrayOutputStream bodyBuffer = new ByteArrayOutputStream();
 1624  
 
 1625  
         //Sort keys into Preferred Order
 1626  250
         TreeSet <String> sortedWriteOrder = new TreeSet<String>( getPreferredFrameOrderComparator());
 1627  250
         sortedWriteOrder.addAll(frameMap.keySet());
 1628  
 
 1629  
         AbstractID3v2Frame frame;
 1630  250
         for(String id:sortedWriteOrder)
 1631  
         {
 1632  794
             Object o = frameMap.get(id);
 1633  794
             if (o instanceof AbstractID3v2Frame)
 1634  
             {
 1635  742
                 frame = (AbstractID3v2Frame) o;
 1636  742
                 frame.write(bodyBuffer);
 1637  
             }
 1638  
             else
 1639  
             {
 1640  52
                 ArrayList<AbstractID3v2Frame> multiFrames = (ArrayList<AbstractID3v2Frame>) o;
 1641  52
                 for (ListIterator<AbstractID3v2Frame> li = multiFrames.listIterator(); li.hasNext();)
 1642  
                 {
 1643  211
                     frame = li.next();
 1644  211
                     frame.write(bodyBuffer);
 1645  
                 }
 1646  
             }
 1647  794
         }
 1648  
 
 1649  250
         return bodyBuffer;
 1650  
     }
 1651  
 
 1652  
 
 1653  
     /**
 1654  
      *
 1655  
      * @return comparator used to order frames in preffrred order for writing to file
 1656  
      * so that most important frames are written first.
 1657  
      */
 1658  
     public abstract Comparator getPreferredFrameOrderComparator();
 1659  
 
 1660  
     public void createStructure()
 1661  
     {
 1662  0
         createStructureHeader();
 1663  0
         createStructureBody();
 1664  0
     }
 1665  
 
 1666  
     public void createStructureHeader()
 1667  
     {
 1668  5
         MP3File.getStructureFormatter().addElement(this.TYPE_DUPLICATEBYTES, this.duplicateBytes);
 1669  5
         MP3File.getStructureFormatter().addElement(this.TYPE_DUPLICATEFRAMEID, this.duplicateFrameId);
 1670  5
         MP3File.getStructureFormatter().addElement(this.TYPE_EMPTYFRAMEBYTES, this.emptyFrameBytes);
 1671  5
         MP3File.getStructureFormatter().addElement(this.TYPE_FILEREADSIZE, this.fileReadSize);
 1672  5
         MP3File.getStructureFormatter().addElement(this.TYPE_INVALIDFRAMEBYTES, this.invalidFrameBytes);
 1673  5
     }
 1674  
 
 1675  
     public void createStructureBody()
 1676  
     {
 1677  5
         MP3File.getStructureFormatter().openHeadingElement(TYPE_BODY, "");
 1678  
 
 1679  
         AbstractID3v2Frame frame;
 1680  5
         for (Object o : frameMap.values())
 1681  
         {
 1682  69
             if (o instanceof AbstractID3v2Frame)
 1683  
             {
 1684  64
                 frame = (AbstractID3v2Frame) o;
 1685  64
                 frame.createStructure();
 1686  
             }
 1687  
             else
 1688  
             {
 1689  5
                 ArrayList<AbstractID3v2Frame> multiFrames = (ArrayList<AbstractID3v2Frame>) o;
 1690  5
                 for (ListIterator<AbstractID3v2Frame> li = multiFrames.listIterator(); li.hasNext();)
 1691  
                 {
 1692  31
                     frame = li.next();
 1693  31
                     frame.createStructure();
 1694  
                 }
 1695  69
             }
 1696  
         }
 1697  5
         MP3File.getStructureFormatter().closeHeadingElement(TYPE_BODY);
 1698  5
     }
 1699  
 
 1700  
     /**
 1701  
      * Retrieve the  values that exists for this id3 frame id
 1702  
      */
 1703  
     public List<TagField> get(String id) throws KeyNotFoundException
 1704  
     {
 1705  195
         Object o = getFrame(id);
 1706  195
         if (o == null)
 1707  
         {
 1708  50
             return new ArrayList<TagField>();
 1709  
         }
 1710  145
         else if (o instanceof List)
 1711  
         {
 1712  
             //TODO should return copy
 1713  86
             return (List<TagField>) o;
 1714  
         }
 1715  59
         else if (o instanceof AbstractID3v2Frame)
 1716  
         {
 1717  59
             List<TagField> list = new ArrayList<TagField>();
 1718  59
             list.add((TagField) o);
 1719  59
             return list;
 1720  
         }
 1721  
         else
 1722  
         {
 1723  0
             throw new RuntimeException("Found entry in frameMap that was not a frame or a list:" + o);
 1724  
         }
 1725  
     }
 1726  
 
 1727  
     public List<TagField> getAlbum()
 1728  
     {
 1729  12
         return get(getAlbumId());
 1730  
     }
 1731  
 
 1732  
     public List<TagField> getArtist()
 1733  
     {
 1734  0
         return get(getArtistId());
 1735  
     }
 1736  
 
 1737  
     public List<TagField> getComment()
 1738  
     {
 1739  14
         return get(getCommentId());
 1740  
     }
 1741  
 
 1742  
     public List<TagField> getGenre()
 1743  
     {
 1744  3
         return get(getGenreId());
 1745  
     }
 1746  
 
 1747  
     public List<TagField> getTitle()
 1748  
     {
 1749  3
         return get(getTitleId());
 1750  
     }
 1751  
 
 1752  
     public List<TagField> getTrack()
 1753  
     {
 1754  3
         return get(getTrackId());
 1755  
     }
 1756  
 
 1757  
 
 1758  
     public List<TagField> getYear()
 1759  
     {
 1760  3
         return get(getYearId());
 1761  
     }
 1762  
 
 1763  
     /**
 1764  
      * @return
 1765  
      */
 1766  
     public String getFirstAlbum()
 1767  
     {
 1768  16
         return getFirst(getAlbumId());
 1769  
     }
 1770  
 
 1771  
 
 1772  
     /**
 1773  
      * @return
 1774  
      */
 1775  
     public String getFirstArtist()
 1776  
     {
 1777  9
         return getFirst(getArtistId());
 1778  
     }
 1779  
 
 1780  
     /**
 1781  
      * @return
 1782  
      */
 1783  
     public String getFirstComment()
 1784  
     {
 1785  5
         return getFirst(getCommentId());
 1786  
     }
 1787  
 
 1788  
     /**
 1789  
      * @return
 1790  
      */
 1791  
     public String getFirstGenre()
 1792  
     {
 1793  8
         return getFirst(getGenreId());
 1794  
     }
 1795  
 
 1796  
     /**
 1797  
      * @return
 1798  
      */
 1799  
     public String getFirstTitle()
 1800  
     {
 1801  9
         return getFirst(getTitleId());
 1802  
     }
 1803  
 
 1804  
     /**
 1805  
      * @return
 1806  
      */
 1807  
     public String getFirstTrack()
 1808  
     {
 1809  6
         return getFirst(getTrackId());
 1810  
     }
 1811  
 
 1812  
     /**
 1813  
      * @return
 1814  
      */
 1815  
     public String getFirstYear()
 1816  
     {
 1817  6
         return getFirst(getYearId());
 1818  
     }
 1819  
 
 1820  
     /**
 1821  
      * @return
 1822  
      */
 1823  
     protected abstract String getArtistId();
 1824  
 
 1825  
     /**
 1826  
      * @return
 1827  
      */
 1828  
     protected abstract String getAlbumId();
 1829  
 
 1830  
     /**
 1831  
      * @return
 1832  
      */
 1833  
     protected abstract String getTitleId();
 1834  
 
 1835  
     /**
 1836  
      * @return
 1837  
      */
 1838  
     protected abstract String getTrackId();
 1839  
 
 1840  
     /**
 1841  
      * @return
 1842  
      */
 1843  
     protected abstract String getYearId();
 1844  
 
 1845  
     /**
 1846  
      * @return
 1847  
      */
 1848  
     protected abstract String getCommentId();
 1849  
 
 1850  
     /**
 1851  
      * @return
 1852  
      */
 1853  
     protected abstract String getGenreId();
 1854  
 
 1855  
     /**
 1856  
      * Create Frame of correct ID3 version with the specified id
 1857  
      *
 1858  
      * @param id
 1859  
      * @return
 1860  
      */
 1861  
     public abstract AbstractID3v2Frame createFrame(String id);
 1862  
 
 1863  
     /**
 1864  
      * @param content
 1865  
      * @return
 1866  
      */
 1867  
     public TagField createArtistField(String content)
 1868  
     {
 1869  3
         AbstractID3v2Frame frame = createFrame(getArtistId());
 1870  3
         ((AbstractFrameBodyTextInfo) frame.getBody()).setText(content);
 1871  3
         return frame;
 1872  
     }
 1873  
 
 1874  
     /**
 1875  
      * @param content
 1876  
      * @return
 1877  
      */
 1878  
     public TagField createAlbumField(String content)
 1879  
     {
 1880  12
         AbstractID3v2Frame frame = createFrame(getAlbumId());
 1881  12
         ((AbstractFrameBodyTextInfo) frame.getBody()).setText(content);
 1882  12
         return frame;
 1883  
     }
 1884  
 
 1885  
     /**
 1886  
      * @param content
 1887  
      * @return
 1888  
      */
 1889  
     public TagField createTitleField(String content)
 1890  
     {
 1891  6
         AbstractID3v2Frame frame = createFrame(getTitleId());
 1892  6
         ((AbstractFrameBodyTextInfo) frame.getBody()).setText(content);
 1893  6
         return frame;
 1894  
     }
 1895  
 
 1896  
     /**
 1897  
      * @param content
 1898  
      * @return
 1899  
      */
 1900  
     public TagField createTrackField(String content)
 1901  
     {
 1902  4
         AbstractID3v2Frame frame = createFrame(getTrackId());
 1903  4
         ((AbstractFrameBodyTextInfo) frame.getBody()).setText(content);
 1904  4
         return frame;
 1905  
     }
 1906  
 
 1907  
     /**
 1908  
      * @param content
 1909  
      * @return
 1910  
      */
 1911  
     public TagField createYearField(String content)
 1912  
     {
 1913  4
         AbstractID3v2Frame frame = createFrame(getYearId());
 1914  4
         ((AbstractFrameBodyTextInfo) frame.getBody()).setText(content);
 1915  4
         return frame;
 1916  
     }
 1917  
 
 1918  
     /**
 1919  
      * @param content
 1920  
      * @return
 1921  
      */
 1922  
     public TagField createCommentField(String content)
 1923  
     {
 1924  12
         AbstractID3v2Frame frame = createFrame(getCommentId());
 1925  12
         ((FrameBodyCOMM) frame.getBody()).setText(content);
 1926  12
         return frame;
 1927  
     }
 1928  
 
 1929  
     /**
 1930  
      * @param content
 1931  
      * @return
 1932  
      */
 1933  
     public TagField createGenreField(String content)
 1934  
     {
 1935  6
         AbstractID3v2Frame frame = createFrame(getGenreId());
 1936  6
         ((AbstractFrameBodyTextInfo) frame.getBody()).setText(content);
 1937  6
         return frame;
 1938  
     }
 1939  
 
 1940  
     //TODO
 1941  
     public boolean hasCommonFields()
 1942  
     {
 1943  0
         return true;
 1944  
     }
 1945  
 
 1946  
     /**
 1947  
      * Does this tag contain a field with the specified id
 1948  
      *
 1949  
      * @see org.jaudiotagger.tag.Tag#hasField(java.lang.String)
 1950  
      */
 1951  
     public boolean hasField(String id)
 1952  
     {
 1953  0
         return get(id).size() != 0;
 1954  
     }
 1955  
 
 1956  
     /**
 1957  
      * Is this tag empty
 1958  
      *
 1959  
      * @see org.jaudiotagger.tag.Tag#isEmpty()
 1960  
      */
 1961  
     public boolean isEmpty()
 1962  
     {
 1963  0
         return frameMap.size() == 0;
 1964  
     }
 1965  
 
 1966  
     /**
 1967  
      *
 1968  
      * @return iterator of all fields, multiple values for the same Id (e.g multiple TXXX frames) count as seperate
 1969  
      * fields
 1970  
      */
 1971  
     public Iterator<TagField> getFields()
 1972  
     {
 1973  
         //Iterator of each different frameId in this tag
 1974  46
         final Iterator<Map.Entry<String, Object>> it        = this.frameMap.entrySet().iterator();
 1975  
 
 1976  
         //Iterator used by hasNext() so doesnt effect next()
 1977  46
         final Iterator<Map.Entry<String, Object>> itHasNext = this.frameMap.entrySet().iterator();
 1978  
 
 1979  
 
 1980  46
         return new Iterator<TagField>()
 1981  
         {
 1982  46
             Map.Entry<String, Object> latestEntry = null;
 1983  
 
 1984  
             //this iterates through frames through for a particular frameId
 1985  
             private Iterator<TagField> fieldsIt;
 1986  
 
 1987  
             private void changeIt()
 1988  
             {
 1989  264
                 if (!it.hasNext())
 1990  
                 {
 1991  44
                     return;
 1992  
                 }
 1993  
 
 1994  221
                 while(it.hasNext())
 1995  
                 {
 1996  220
                     Map.Entry<String, Object> e = it.next();
 1997  220
                     latestEntry=itHasNext.next();
 1998  220
                     if (e.getValue() instanceof List)
 1999  
                     {
 2000  22
                         List<TagField> l = (List<TagField>) e.getValue();
 2001  
                         //If list is empty (which it shouldn't be) we skip over this entry
 2002  22
                         if(l.size()==0)
 2003  
                         {
 2004  1
                             continue;
 2005  
                         }
 2006  
                         else
 2007  
                         {
 2008  21
                             fieldsIt = l.iterator();
 2009  21
                             break;
 2010  
                         }
 2011  
                     }
 2012  
                     else
 2013  
                     {
 2014  
                         //TODO must be a better way
 2015  198
                         List<TagField> l = new ArrayList<TagField>();
 2016  198
                         l.add((TagField) e.getValue());
 2017  198
                         fieldsIt = l.iterator();
 2018  198
                         break;
 2019  
                     }
 2020  
                 }
 2021  220
             }
 2022  
 
 2023  
             //TODO assumes if have entry its valid, but what if empty list but very different to check this
 2024  
             //without causing a side effect on next() so leaving for now
 2025  
             public boolean hasNext()
 2026  
             {
 2027  
                 //Check Current frameId, does it contain more values
 2028  3
                 if(fieldsIt!=null)
 2029  
                 {
 2030  1
                     if (fieldsIt.hasNext())
 2031  
                     {
 2032  0
                         return true;
 2033  
                     }
 2034  
                 }
 2035  
 
 2036  
                 //No remaining entries return false
 2037  3
                 if(!itHasNext.hasNext())
 2038  
                 {
 2039  2
                     return false;
 2040  
                 }
 2041  
 
 2042  
                 //Issue #236
 2043  
                 //TODO assumes if have entry its valid, but what if empty list but very different to check this
 2044  
                 //without causing a side effect on next() so leaving for now
 2045  1
                 if(itHasNext.hasNext())
 2046  
                 {
 2047  1
                     return true;
 2048  
                 }
 2049  0
                 return false;
 2050  
             }
 2051  
 
 2052  
             public TagField next()
 2053  
             {
 2054  
                 //Hasn't been initialized yet
 2055  289
                 if (fieldsIt == null)
 2056  
                 {
 2057  46
                     changeIt();
 2058  
                 }
 2059  
 
 2060  289
                 if(fieldsIt!=null)
 2061  
                 {
 2062  
                     //Go to the end of the run
 2063  285
                     if (!fieldsIt.hasNext())
 2064  
                     {
 2065  218
                         changeIt();
 2066  
                     }
 2067  
                 }
 2068  
 
 2069  289
                 if(fieldsIt==null)
 2070  
                 {
 2071  4
                     throw new NoSuchElementException();
 2072  
                 }
 2073  285
                 return fieldsIt.next();
 2074  
             }
 2075  
 
 2076  335
             public void remove()
 2077  
             {
 2078  0
                 fieldsIt.remove();
 2079  0
             }
 2080  
         };
 2081  
     }
 2082  
 
 2083  
     public int getFieldCount()
 2084  
     {
 2085  43
         Iterator<TagField> it = getFields();
 2086  43
         int count = 0;
 2087  
 
 2088  
         //Done this way becuase it.hasNext() incorrectly counts empty list
 2089  
         //whereas it.next() works correctly
 2090  
         try
 2091  
         {
 2092  
             while(true)
 2093  
             {
 2094  285
                 it.next();
 2095  242
                 count++;
 2096  
             }
 2097  
         }
 2098  43
         catch(NoSuchElementException nse)
 2099  
         {
 2100  
             //this is thrown when no more elements
 2101  
         }
 2102  43
         return count;
 2103  
     }
 2104  
 
 2105  
     //TODO is this a special field?
 2106  
     public boolean setEncoding(String enc) throws FieldDataInvalidException
 2107  
     {
 2108  0
         throw new UnsupportedOperationException("Not Implemented Yet");
 2109  
     }
 2110  
 
 2111  
     /**
 2112  
      * Retrieve the first value that exists for this generic key
 2113  
      *
 2114  
      * @param genericKey
 2115  
      * @return
 2116  
      */
 2117  
     public String getFirst(TagFieldKey genericKey) throws KeyNotFoundException
 2118  
     {
 2119  84
         if (genericKey == null)
 2120  
         {
 2121  0
             throw new KeyNotFoundException();
 2122  
         }
 2123  
 
 2124  84
         return doGetFirst(getFrameAndSubIdFromGenericKey(genericKey));
 2125  
     }
 2126  
 
 2127  
 
 2128  
     /**
 2129  
      * Create a new TagField
 2130  
      * <p/>
 2131  
      * Only textual data supported at the moment. The genericKey will be mapped
 2132  
      * to the correct implementation key and return a TagField.
 2133  
      *
 2134  
      * @param genericKey is the generic key
 2135  
      * @param value      to store
 2136  
      * @return
 2137  
      */
 2138  
     public TagField createTagField(TagFieldKey genericKey, String value) throws KeyNotFoundException, FieldDataInvalidException
 2139  
     {
 2140  56
         if (genericKey == null)
 2141  
         {
 2142  0
             throw new KeyNotFoundException();
 2143  
         }
 2144  56
         return doCreateTagField(getFrameAndSubIdFromGenericKey(genericKey), value);
 2145  
     }
 2146  
 
 2147  
     /**
 2148  
      * Create Frame for Id3 Key
 2149  
      * <p/>
 2150  
      * Only textual data supported at the moment, should only be used with frames that
 2151  
      * support a simple string argument.
 2152  
      *
 2153  
      * @param formatKey
 2154  
      * @param value
 2155  
      * @return
 2156  
      * @throws KeyNotFoundException
 2157  
      * @throws FieldDataInvalidException
 2158  
      */
 2159  
     protected TagField doCreateTagField(FrameAndSubId formatKey, String value) throws KeyNotFoundException, FieldDataInvalidException
 2160  
     {
 2161  56
         AbstractID3v2Frame frame = createFrame(formatKey.getFrameId());
 2162  56
         if (frame.getBody() instanceof FrameBodyUFID)
 2163  
         {
 2164  2
             ((FrameBodyUFID) frame.getBody()).setOwner(formatKey.getSubId());
 2165  
             try
 2166  
             {
 2167  2
                 ((FrameBodyUFID) frame.getBody()).setUniqueIdentifier(value.getBytes("ISO-8859-1"));
 2168  
             }
 2169  0
             catch (UnsupportedEncodingException uee)
 2170  
             {
 2171  
                 //This will never happen because we are using a charset supported on all platforms
 2172  
                 //but just in case
 2173  0
                 throw new RuntimeException("When encoding UFID charset ISO-8859-1 was deemed unsupported");
 2174  2
             }
 2175  
 
 2176  
         }
 2177  54
         else if (frame.getBody() instanceof FrameBodyTXXX)
 2178  
         {
 2179  11
             ((FrameBodyTXXX) frame.getBody()).setDescription(formatKey.getSubId());
 2180  11
             ((FrameBodyTXXX) frame.getBody()).setText(value);
 2181  
         }
 2182  43
         else if (frame.getBody() instanceof FrameBodyWXXX)
 2183  
         {
 2184  15
             ((FrameBodyWXXX) frame.getBody()).setDescription(formatKey.getSubId());
 2185  15
             ((FrameBodyWXXX) frame.getBody()).setUrlLink(value);
 2186  
         }
 2187  28
         else if (frame.getBody() instanceof FrameBodyCOMM)
 2188  
         {
 2189  3
             ((FrameBodyCOMM) frame.getBody()).setText(value);
 2190  
         }
 2191  25
         else if (frame.getBody() instanceof FrameBodyUSLT)
 2192  
         {
 2193  2
             ((FrameBodyUSLT) frame.getBody()).setDescription("");
 2194  2
             ((FrameBodyUSLT) frame.getBody()).setLyric(value);
 2195  
         }
 2196  23
         else if (frame.getBody() instanceof FrameBodyWOAR)
 2197  
         {
 2198  3
             ((FrameBodyWOAR) frame.getBody()).setUrlLink(value);           
 2199  
         }
 2200  
 
 2201  20
         else if (frame.getBody() instanceof AbstractFrameBodyTextInfo)
 2202  
         {
 2203  17
             ((AbstractFrameBodyTextInfo) frame.getBody()).setText(value);
 2204  
         }
 2205  3
         else if ((frame.getBody() instanceof FrameBodyAPIC) || (frame.getBody() instanceof FrameBodyPIC))
 2206  
         {
 2207  3
             throw new UnsupportedOperationException("Please use createArtwork() instead for creating artwork");
 2208  
         }
 2209  
         else
 2210  
         {
 2211  0
             throw new FieldDataInvalidException("Field with key of:" + formatKey.getFrameId() + ":does not accept cannot parse data:" + value);
 2212  
         }
 2213  53
         return frame;
 2214  
     }
 2215  
 
 2216  
 
 2217  
     /**
 2218  
      * @param formatKey
 2219  
      * @return
 2220  
      * @throws KeyNotFoundException
 2221  
      */
 2222  
     protected String doGetFirst(FrameAndSubId formatKey) throws KeyNotFoundException
 2223  
     {
 2224  
         //Simple 1 to 1 mapping
 2225  120
         if (formatKey.getSubId() == null)
 2226  
         {
 2227  96
             return getFirst(formatKey.getFrameId());
 2228  
         }
 2229  
         else
 2230  
         {
 2231  
             //Get list of frames that this uses
 2232  24
             List<TagField> list = get(formatKey.getFrameId());
 2233  24
             ListIterator<TagField> li = list.listIterator();
 2234  39
             while (li.hasNext())
 2235  
             {
 2236  39
                 AbstractTagFrameBody next = ((AbstractID3v2Frame) li.next()).getBody();
 2237  39
                 if (next instanceof FrameBodyTXXX)
 2238  
                 {
 2239  39
                     if (((FrameBodyTXXX) next).getDescription().equals(formatKey.getSubId()))
 2240  
                     {
 2241  24
                         return ((FrameBodyTXXX) next).getText();
 2242  
                     }
 2243  
                 }
 2244  0
                 else if (next instanceof FrameBodyWXXX)
 2245  
                 {
 2246  0
                     if (((FrameBodyWXXX) next).getDescription().equals(formatKey.getSubId()))
 2247  
                     {
 2248  0
                         return ((FrameBodyWXXX) next).getUrlLink();
 2249  
                     }
 2250  
                 }
 2251  0
                 else if (next instanceof FrameBodyUFID)
 2252  
                 {
 2253  0
                     if (!((FrameBodyUFID) next).getUniqueIdentifier().equals(formatKey.getSubId()))
 2254  
                     {
 2255  0
                         return new String(((FrameBodyUFID) next).getUniqueIdentifier());
 2256  
                     }
 2257  
                 }
 2258  
                 else
 2259  
                 {
 2260  0
                     throw new RuntimeException("Need to implement get(TagFieldKey genericKey) for:" + next.getClass());
 2261  
                 }
 2262  15
             }
 2263  0
             return "";
 2264  
         }
 2265  
     }
 2266  
 
 2267  
     /**
 2268  
      * Create a link to artwork, this is not recommended because the link may be broken if the mp3 or image
 2269  
      * file is moved
 2270  
      *
 2271  
      * @param url specifies the link, it could be a local file or could be a full url
 2272  
      * @return
 2273  
      */
 2274  
     public TagField createLinkedArtworkField(String url)
 2275  
     {
 2276  1
         AbstractID3v2Frame frame = createFrame(getFrameAndSubIdFromGenericKey(TagFieldKey.COVER_ART).getFrameId());
 2277  1
         if (frame.getBody() instanceof FrameBodyAPIC)
 2278  
         {
 2279  1
             FrameBodyAPIC body = (FrameBodyAPIC) frame.getBody();
 2280  1
             body.setObjectValue(DataTypes.OBJ_PICTURE_DATA, Utils.getDefaultBytes(url, TextEncoding.CHARSET_ISO_8859_1));
 2281  1
             body.setObjectValue(DataTypes.OBJ_PICTURE_TYPE, PictureTypes.DEFAULT_ID);
 2282  1
             body.setObjectValue(DataTypes.OBJ_MIME_TYPE, FrameBodyAPIC.IMAGE_IS_URL);
 2283  1
             body.setObjectValue(DataTypes.OBJ_DESCRIPTION, "");
 2284  1
         }
 2285  0
         else if (frame.getBody() instanceof FrameBodyPIC)
 2286  
         {
 2287  0
             FrameBodyPIC body = (FrameBodyPIC) frame.getBody();
 2288  0
             body.setObjectValue(DataTypes.OBJ_PICTURE_DATA, Utils.getDefaultBytes(url, TextEncoding.CHARSET_ISO_8859_1));
 2289  0
             body.setObjectValue(DataTypes.OBJ_PICTURE_TYPE, PictureTypes.DEFAULT_ID);
 2290  0
             body.setObjectValue(DataTypes.OBJ_IMAGE_FORMAT, FrameBodyAPIC.IMAGE_IS_URL);
 2291  0
             body.setObjectValue(DataTypes.OBJ_DESCRIPTION, "");
 2292  
         }
 2293  1
         return frame;
 2294  
     }
 2295  
 
 2296  
 
 2297  
 
 2298  
     /**
 2299  
      * Delete fields with this generic key
 2300  
      *
 2301  
      * @param genericKey
 2302  
      */
 2303  
     public void deleteTagField(TagFieldKey genericKey) throws KeyNotFoundException
 2304  
     {
 2305  28
        if (genericKey == null)
 2306  
         {
 2307  0
             throw new KeyNotFoundException();
 2308  
         }
 2309  28
         FrameAndSubId formatKey = getFrameAndSubIdFromGenericKey(genericKey);
 2310  28
         doDeleteTagField(formatKey);
 2311  28
     }
 2312  
 
 2313  
     /**
 2314  
      * Internal delete method
 2315  
      *
 2316  
      * @param formatKey
 2317  
      * @throws KeyNotFoundException
 2318  
      */
 2319  
     protected void doDeleteTagField(FrameAndSubId formatKey) throws KeyNotFoundException
 2320  
     {
 2321  
         //Simple 1 to 1 mapping
 2322  28
         if (formatKey.getSubId() == null)
 2323  
         {
 2324  10
             removeFrame(formatKey.getFrameId());
 2325  
         }
 2326  
         else
 2327  
         {
 2328  
             //Get list of frames that this uses
 2329  18
             List<TagField> list = get(formatKey.getFrameId());
 2330  18
             ListIterator<TagField> li = list.listIterator();
 2331  72
             while (li.hasNext())
 2332  
             {
 2333  54
                 AbstractTagFrameBody next = ((AbstractID3v2Frame) li.next()).getBody();
 2334  54
                 if (next instanceof FrameBodyTXXX)
 2335  
                 {
 2336  9
                     if (((FrameBodyTXXX) next).getDescription().equals(formatKey.getSubId()))
 2337  
                     {
 2338  3
                         li.remove();
 2339  
                     }
 2340  
                 }
 2341  45
                 else if (next instanceof FrameBodyWXXX)
 2342  
                 {
 2343  45
                     if (((FrameBodyWXXX) next).getDescription().equals(formatKey.getSubId()))
 2344  
                     {
 2345  15
                         li.remove();
 2346  
                     }
 2347  
                 }
 2348  0
                 else if (next instanceof FrameBodyUFID)
 2349  
                 {
 2350  0
                     if (((FrameBodyUFID) next).getUniqueIdentifier().equals(formatKey.getSubId()))
 2351  
                     {
 2352  0
                         li.remove();
 2353  
                     }
 2354  
                 }
 2355  
                 else
 2356  
                 {
 2357  0
                     throw new RuntimeException("Need to implement get(TagFieldKey genericKey) for:" + next.getClass());
 2358  
                 }
 2359  54
             }            
 2360  
         }
 2361  28
     }
 2362  
 
 2363  
     protected abstract FrameAndSubId getFrameAndSubIdFromGenericKey(TagFieldKey genericKey);
 2364  
 
 2365  
     /**
 2366  
      * Get field(s) for this key
 2367  
      *
 2368  
      * @param genericKey
 2369  
      * @return
 2370  
      * @throws KeyNotFoundException
 2371  
      */
 2372  
     public List<TagField> get(TagFieldKey genericKey) throws KeyNotFoundException
 2373  
     {
 2374  108
         if (genericKey == null)
 2375  
         {
 2376  0
             throw new KeyNotFoundException();
 2377  
         }
 2378  
 
 2379  108
         FrameAndSubId formatKey = getFrameAndSubIdFromGenericKey(genericKey);
 2380  
 
 2381  
         //Get list of frames that this uses, as we are going to remove entries we dont want take a copy
 2382  108
         List<TagField> list = get(formatKey.getFrameId());
 2383  108
         List<TagField> filteredList = new ArrayList<TagField>();
 2384  108
         String subFieldId = formatKey.getSubId();
 2385  108
         String frameid = formatKey.getFrameId();
 2386  
 
 2387  
         //... do we need to refine the list further i.e we only want TXXX frames that relate to the particular
 2388  
         //key that was passed as a parameter
 2389  108
         if (subFieldId != null)
 2390  
         {
 2391  67
             for (TagField tagfield : list)
 2392  
             {
 2393  123
                 AbstractTagFrameBody next = ((AbstractID3v2Frame) tagfield).getBody();
 2394  123
                 if (next instanceof FrameBodyTXXX)
 2395  
                 {
 2396  48
                     if (((FrameBodyTXXX) next).getDescription().equals(formatKey.getSubId()))
 2397  
                     {
 2398  21
                         filteredList.add(tagfield);
 2399  
                     }
 2400  
                 }
 2401  75
                 else if (next instanceof FrameBodyWXXX)
 2402  
                 {
 2403  75
                     if (((FrameBodyWXXX) next).getDescription().equals(formatKey.getSubId()))
 2404  
                     {
 2405  15
                         filteredList.add(tagfield);
 2406  
                     }
 2407  
                 }
 2408  0
                 else if (next instanceof FrameBodyUFID)
 2409  
                 {
 2410  0
                     if (((FrameBodyUFID) next).getUniqueIdentifier().equals(formatKey.getSubId()))
 2411  
                     {
 2412  0
                         filteredList.add(tagfield);
 2413  
                     }
 2414  
                 }
 2415  
                 else
 2416  
                 {
 2417  0
                     throw new RuntimeException("Need to implement get(TagFieldKey genericKey) for:" + next.getClass());
 2418  
                 }
 2419  123
             }
 2420  67
             return filteredList;
 2421  
         }
 2422  
         else
 2423  
         {
 2424  41
             return list;
 2425  
         }
 2426  
     }
 2427  
 
 2428  
     /**
 2429  
      * This class had to be created to minimize the duplicate code in concrete subclasses
 2430  
      * of this class. It is required in some cases when using the Fieldkey enums because enums
 2431  
      * cant be subclassed. We want to use enums instead of regular classes because they are
 2432  
      * much easier for endusers to  to use.
 2433  
      */
 2434  
     class FrameAndSubId
 2435  
     {
 2436  
         private String frameId;
 2437  
         private String subId;
 2438  
 
 2439  
         public FrameAndSubId(String frameId, String subId)
 2440  322
         {
 2441  322
             this.frameId = frameId;
 2442  322
             this.subId = subId;
 2443  322
         }
 2444  
 
 2445  
         public String getFrameId()
 2446  
         {
 2447  430
             return frameId;
 2448  
         }
 2449  
 
 2450  
         public String getSubId()
 2451  
         {
 2452  500
             return subId;
 2453  
         }
 2454  
     }
 2455  
 
 2456  
     public Artwork getFirstArtwork()
 2457  
     {
 2458  3
         List<Artwork> artwork = getArtworkList();
 2459  3
         if(artwork.size()>0)
 2460  
         {
 2461  3
             return artwork.get(0);
 2462  
         }
 2463  0
         return null;
 2464  
     }
 2465  
 
 2466  
     /**
 2467  
       * Create field and then set within tag itself
 2468  
       *
 2469  
       * @param artwork
 2470  
       * @throws FieldDataInvalidException
 2471  
       */
 2472  
      public void createAndSetArtworkField(Artwork artwork) throws FieldDataInvalidException
 2473  
      {
 2474  3
          this.set(createArtworkField(artwork));
 2475  3
      }
 2476  
 }