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