~ubuntu-branches/ubuntu/utopic/libjaudiotagger-java/utopic

« back to all changes in this revision

Viewing changes to org/jaudiotagger/tag/id3/AbstractID3v2Tag.java

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath, Damien Raude-Morvan, Varun Hiremath
  • Date: 2009-04-01 19:17:56 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20090401191756-bygniim270guy7o1
Tags: 1.0.9-1
[ Damien Raude-Morvan ]
* New upstream release
* debian/watch: Use java.net repository (which contains new releases!)
* debian/control:
  - Build-Depends on default-jdk-builddep
  - Bump Standards-Version to 3.8.1 (no changes needed)
  - Change section to "java"
* debian/rules: use default-java as JAVA_HOME
* debina/orig-tar.{sh|excludes}: strip audio and others binary files from ZIP
* debian/build.xml:
  - compile with "nowarn" to keep build log readable
  - exclude LogFormatter from build (use com.sun classes)
* debian/ant.properties: new source directory is "src" in orig.tar.gz
* Add myself as Uploaders

[ Varun Hiremath ]
* Accept changes made by Damien Raude-Morvan (Closes: #522130)

Show diffs side-by-side

added added

removed removed

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