FileDocCategorySizeDatePackage
AbstractID3v2Tag.javaAPI DocJaudiotagger 2.0.491386Wed Dec 07 11:08:26 GMT 2011org.jaudiotagger.tag.id3

AbstractID3v2Tag

public abstract class AbstractID3v2Tag extends AbstractID3Tag implements Tag
This is the abstract base class for all ID3v2 tags.
author
: Paul Taylor
author
: Eric Farng
version
$Id: AbstractID3v2Tag.java 1011 2011-12-07 11:08:21Z paultaylor $

Fields Summary
protected static final String
TYPE_HEADER
protected static final String
TYPE_BODY
protected static final byte[]
TAG_ID
public static final int
TAG_HEADER_LENGTH
protected static final int
FIELD_TAGID_LENGTH
protected static final int
FIELD_TAG_MAJOR_VERSION_LENGTH
protected static final int
FIELD_TAG_MINOR_VERSION_LENGTH
protected static final int
FIELD_TAG_FLAG_LENGTH
protected static final int
FIELD_TAG_SIZE_LENGTH
protected static final int
FIELD_TAGID_POS
protected static final int
FIELD_TAG_MAJOR_VERSION_POS
protected static final int
FIELD_TAG_MINOR_VERSION_POS
protected static final int
FIELD_TAG_FLAG_POS
protected static final int
FIELD_TAG_SIZE_POS
protected static final int
TAG_SIZE_INCREMENT
private static final long
MAXIMUM_WRITABLE_CHUNK_SIZE
public HashMap
frameMap
Map of all frames for this tag
public HashMap
encryptedFrameMap
Map of all encrypted frames, these cannot be unencrypted by jaudiotagger
protected static final String
TYPE_DUPLICATEFRAMEID
Holds the ids of invalid duplicate frames
protected String
duplicateFrameId
protected static final String
TYPE_DUPLICATEBYTES
Holds count the number of bytes used up by invalid duplicate frames
protected int
duplicateBytes
protected static final String
TYPE_EMPTYFRAMEBYTES
Holds count the number bytes used up by empty frames
protected int
emptyFrameBytes
protected static final String
TYPE_FILEREADSIZE
Holds the size of the tag as reported by the tag header
protected int
fileReadSize
protected static final String
TYPE_INVALIDFRAMES
Holds count of invalid frames, (frames that could not be read)
protected int
invalidFrames
Constructors Summary
public AbstractID3v2Tag()
Empty Constructor

    
protected AbstractID3v2Tag(AbstractID3v2Tag copyObject)
This constructor is used when a tag is created as a duplicate of another tag of the same type and version.

param
copyObject

    
Methods Summary
public voidaddField(FieldKey genericKey, java.lang.String value)

        TagField tagfield = createField(genericKey, value);
        addField(tagfield);
    
public voidaddField(TagField field)
Add new field

There is a special handling if adding another text field of the same type, in this case the value will be appended to the existing field, separated by the null character.

param
field
throws
FieldDataInvalidException

        if (field == null)
        {
            return;
        }

        if (!(field instanceof AbstractID3v2Frame))
        {
            throw new FieldDataInvalidException("Field " + field + " is not of type AbstractID3v2Frame");
        }

        AbstractID3v2Frame frame = (AbstractID3v2Frame) field;

        Object o = frameMap.get(field.getId());

        //No frame of this type
        if (o == null)
        {
            frameMap.put(field.getId(), field);
        }
        //There are already frames of this type
        else if (o instanceof List)
        {
            List<TagField> list = (List<TagField>) o;
            addNewFrameOrAddField(list, frameMap, null, frame);
        }
        //One frame exists, we are adding another so may need to convert to list
        else
        {
            AbstractID3v2Frame existingFrame = (AbstractID3v2Frame) o;
            List<TagField> list = new ArrayList<TagField>();
            addNewFrameOrAddField(list, frameMap, existingFrame, frame);
        }
    
public voidaddField(org.jaudiotagger.tag.images.Artwork artwork)
Create field and then set within tag itself

param
artwork
throws
FieldDataInvalidException

        this.addField(createField(artwork));
    
protected abstract voidaddFrame(AbstractID3v2Frame frame)

private voidaddNewFrameOrAddField(java.util.List list, java.util.HashMap frameMap, AbstractID3v2Frame existingFrame, AbstractID3v2Frame frame)
Handles adding of a new field that's shares a frame with other fields, so modifies the existing frame rather than creating an ew frame for these special cases

param
list
param
frameMap
param
existingFrame
param
frame

        /**
         * If the frame is a TextInformation (but not the TXXX) frame then we just add an extra string to the existing frame
         * otherwise we create a new frame
         */
        if (frame.getBody() instanceof AbstractFrameBodyTextInfo && !(frame.getBody() instanceof FrameBodyTXXX))
        {
            AbstractFrameBodyTextInfo frameBody = (AbstractFrameBodyTextInfo) frame.getBody();
            AbstractFrameBodyTextInfo existingFrameBody = (AbstractFrameBodyTextInfo) existingFrame.getBody();
            existingFrameBody.addTextValue(frameBody.getText());
        }
        else if (frame.getBody() instanceof FrameBodyIPLS)
        {
            FrameBodyIPLS frameBody = (FrameBodyIPLS) frame.getBody();
            FrameBodyIPLS existingFrameBody = (FrameBodyIPLS) existingFrame.getBody();
            existingFrameBody.addPair(frameBody.getText());
        }
        else if (frame.getBody() instanceof FrameBodyTIPL)
        {
            FrameBodyTIPL frameBody = (FrameBodyTIPL) frame.getBody();
            FrameBodyTIPL existingFrameBody = (FrameBodyTIPL) existingFrame.getBody();
            existingFrameBody.addPair(frameBody.getText());
        }
        else
        {
            if (list.size() == 0)
            {
                list.add(existingFrame);
                list.add(frame);
                frameMap.put(frame.getId(), list);
            }
            else
            {
                list.add(frame);
            }
        }
    
public voidadjustPadding(java.io.File file, int paddingSize, long audioStart)
Adjust the length of the padding at the beginning of the MP3 file, this is only called when there is currently not enough space before the start of the audio to write the tag.

A new file will be created with enough size to fit the ID3v2 tag. The old file will be deleted, and the new file renamed.

param
paddingSize This is total size required to store tag before audio
param
audioStart
param
file The file to adjust the padding length of
throws
FileNotFoundException if the file exists but is a directory rather than a regular file or cannot be opened for any other reason
throws
IOException on any I/O error

        logger.finer("Need to move audio file to accommodate tag");
        FileChannel fcIn = null;
        FileChannel fcOut;

        //Create buffer holds the necessary padding
        ByteBuffer paddingBuffer = ByteBuffer.wrap(new byte[paddingSize]);

        //Create Temporary File and write channel, make sure it is locked        
        File paddedFile;

        try
        {
            paddedFile = File.createTempFile(Utils.getBaseFilenameForTempFile(file), ".new", file.getParentFile());
            logger.finest("Created temp file:" + paddedFile.getName() + " for " + file.getName());
        }
        //Vista:Can occur if have Write permission on folder this file would be created in Denied
        catch (IOException ioe)
        {
            logger.log(Level.SEVERE, ioe.getMessage(), ioe);
            if (ioe.getMessage().equals(FileSystemMessage.ACCESS_IS_DENIED.getMsg()))
            {
                logger.severe(ErrorMessage.GENERAL_WRITE_FAILED_TO_CREATE_TEMPORARY_FILE_IN_FOLDER.getMsg(file.getName(), file.getParentFile().getPath()));
                throw new UnableToCreateFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_CREATE_TEMPORARY_FILE_IN_FOLDER.getMsg(file.getName(), file.getParentFile().getPath()));
            }
            else
            {
                logger.severe(ErrorMessage.GENERAL_WRITE_FAILED_TO_CREATE_TEMPORARY_FILE_IN_FOLDER.getMsg(file.getName(), file.getParentFile().getPath()));
                throw new UnableToCreateFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_CREATE_TEMPORARY_FILE_IN_FOLDER.getMsg(file.getName(), file.getParentFile().getPath()));
            }
        }

        try
        {
            fcOut = new FileOutputStream(paddedFile).getChannel();
        }
        //Vista:Can occur if have special permission Create Folder/Append Data denied
        catch (FileNotFoundException ioe)
        {
            logger.log(Level.SEVERE, ioe.getMessage(), ioe);
            logger.severe(ErrorMessage.GENERAL_WRITE_FAILED_TO_MODIFY_TEMPORARY_FILE_IN_FOLDER.getMsg(file.getName(), file.getParentFile().getPath()));
            throw new UnableToModifyFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_MODIFY_TEMPORARY_FILE_IN_FOLDER.getMsg(file.getName(), file.getParentFile().getPath()));
        }

        try
        {
            //Create read channel from original file
            //TODO lock so cant be modified by anything else whilst reading from it ?
            fcIn = new FileInputStream(file).getChannel();

            //Write padding to new file (this is where the tag will be written to later)
            long written = fcOut.write(paddingBuffer);

            //Write rest of file starting from audio
            logger.finer("Copying:" + (file.length() - audioStart) + "bytes");

            //If the amount to be copied is very large we split into 10MB lumps to try and avoid
            //out of memory errors
            long audiolength = file.length() - audioStart;
            if (audiolength <= MAXIMUM_WRITABLE_CHUNK_SIZE)
            {
                fcIn.position(audioStart);
                long written2 = fcOut.transferFrom(fcIn, paddingSize, audiolength);
                logger.finer("Written padding:" + written + " Data:" + written2);
                if (written2 != audiolength)
                {
                    throw new RuntimeException(ErrorMessage.MP3_UNABLE_TO_ADJUST_PADDING.getMsg(audiolength, written2));
                }
            }
            else
            {
                long noOfChunks = audiolength / MAXIMUM_WRITABLE_CHUNK_SIZE;
                long lastChunkSize = audiolength % MAXIMUM_WRITABLE_CHUNK_SIZE;
                long written2 = 0;
                for (int i = 0; i < noOfChunks; i++)
                {
                    written2 += fcIn.transferTo(audioStart + (i * MAXIMUM_WRITABLE_CHUNK_SIZE), MAXIMUM_WRITABLE_CHUNK_SIZE, fcOut);
                }
                written2 += fcIn.transferTo(audioStart + (noOfChunks * MAXIMUM_WRITABLE_CHUNK_SIZE), lastChunkSize, fcOut);
                logger.finer("Written padding:" + written + " Data:" + written2);
                if (written2 != audiolength)
                {
                    throw new RuntimeException(ErrorMessage.MP3_UNABLE_TO_ADJUST_PADDING.getMsg(audiolength, written2));
                }
            }

            //Store original modification time
            long lastModified = file.lastModified();

            //Close Channels and locks
            if (fcIn != null)
            {
                if (fcIn.isOpen())
                {
                    fcIn.close();
                }
            }

            if (fcOut != null)
            {
                if (fcOut.isOpen())
                {
                    fcOut.close();
                }
            }

            //Replace file with paddedFile
            replaceFile(file, paddedFile);

            //Update modification time
            //TODO is this the right file ?
            paddedFile.setLastModified(lastModified);
        }
        catch(UnableToRenameFileException ure)
        {
            paddedFile.delete();
            throw ure;
        }
        finally
        {
            try
            {
                //Whatever happens ensure all locks and channels are closed/released
                if (fcIn != null)
                {
                    if (fcIn.isOpen())
                    {
                        fcIn.close();
                    }
                }

                if (fcOut != null)
                {
                    if (fcOut.isOpen())
                    {
                        fcOut.close();
                    }
                }
            }
            catch (Exception e)
            {
                logger.log(Level.WARNING, "Problem closing channels and locks:" + e.getMessage(), e);
            }
        }
    
protected intcalculateTagSize(int tagSize, int audioStart)
This method determines the total tag size taking into account where the audio file starts, the size of the tagging data and user options for defining how tags should shrink or grow.

param
tagSize
param
audioStart
return

        /** We can fit in the tag so no adjustments required */
        if (tagSize <= audioStart)
        {
            return audioStart;
        }
        /** There is not enough room as we need to move the audio file we might
         *  as well increase it more than neccessary for future changes
         */
        return tagSize + TAG_SIZE_INCREMENT;
    
protected voidcopyFrameIntoMap(java.lang.String id, AbstractID3v2Frame newFrame)


        if (frameMap.containsKey(newFrame.getIdentifier()))
        {
            Object o = frameMap.get(newFrame.getIdentifier());
            if (o instanceof AbstractID3v2Frame)
            {
                List<AbstractID3v2Frame> list = new ArrayList<AbstractID3v2Frame>();
                list.add((AbstractID3v2Frame) o);
                list.add(newFrame);
                frameMap.put(newFrame.getIdentifier(), list);
            }
            else
            {
                List<AbstractID3v2Frame> list = (List) o;
                list.add(newFrame);
            }
        }
        else
        {
            frameMap.put(newFrame.getIdentifier(), newFrame);
        }
    
protected voidcopyFrames(org.jaudiotagger.tag.id3.AbstractID3v2Tag copyObject)
Copy frames from another tag,

param
copyObject

        frameMap = new LinkedHashMap();
        encryptedFrameMap = new LinkedHashMap();
        //Copy Frames that are a valid 2.4 type

        for (Object o1 : copyObject.frameMap.keySet())
        {
            String id = (String) o1;
            Object o = copyObject.frameMap.get(id);
            //SingleFrames
            if (o instanceof AbstractID3v2Frame)
            {
                addFrame((AbstractID3v2Frame) o);
            }
            //MultiFrames
            else if (o instanceof ArrayList)
            {
                for (AbstractID3v2Frame frame : (ArrayList<AbstractID3v2Frame>) o)
                {
                    addFrame(frame);
                }
            }
        }
    
protected voidcopyPrimitives(org.jaudiotagger.tag.id3.AbstractID3v2Tag copyObject)
Copy primitives apply to all tags

param
copyObject

        logger.config("Copying Primitives");
        //Primitives type variables common to all IDv2 Tags
        this.duplicateFrameId = copyObject.duplicateFrameId;
        this.duplicateBytes = copyObject.duplicateBytes;
        this.emptyFrameBytes = copyObject.emptyFrameBytes;
        this.fileReadSize = copyObject.fileReadSize;
        this.invalidFrames = copyObject.invalidFrames;
    
public TagFieldcreateField(FieldKey genericKey, java.lang.String value)
Create a new TagField

Only textual data supported at the moment. The genericKey will be mapped to the correct implementation key and return a TagField.

param
genericKey is the generic key
param
value to store
return

        if (genericKey == null)
        {
            throw new KeyNotFoundException();
        }

        FrameAndSubId formatKey = getFrameAndSubIdFromGenericKey(genericKey);
        if (genericKey == FieldKey.TRACK)
        {
            AbstractID3v2Frame frame = createFrame(formatKey.getFrameId());
            FrameBodyTRCK framebody = (FrameBodyTRCK) frame.getBody();
            framebody.setTrackNo(Integer.parseInt(value));
            return frame;
        }
        else if (genericKey == FieldKey.TRACK_TOTAL)
        {
            AbstractID3v2Frame frame = createFrame(formatKey.getFrameId());
            FrameBodyTRCK framebody = (FrameBodyTRCK) frame.getBody();
            framebody.setTrackTotal(Integer.parseInt(value));
            return frame;
        }
        else if (genericKey == FieldKey.DISC_NO)
        {
            AbstractID3v2Frame frame = createFrame(formatKey.getFrameId());
            FrameBodyTPOS framebody = (FrameBodyTPOS) frame.getBody();
            framebody.setDiscNo(Integer.parseInt(value));
            return frame;
        }
        else if (genericKey == FieldKey.DISC_TOTAL)
        {
            AbstractID3v2Frame frame = createFrame(formatKey.getFrameId());
            FrameBodyTPOS framebody = (FrameBodyTPOS) frame.getBody();
            framebody.setDiscTotal(Integer.parseInt(value));
            return frame;
        }
        else
        {
            return doCreateTagField(formatKey, value);
        }
    
public abstract AbstractID3v2FramecreateFrame(java.lang.String id)
Create Frame of correct ID3 version with the specified id

param
id
return

public TagFieldcreateLinkedArtworkField(java.lang.String url)
Create a link to artwork, this is not recommended because the link may be broken if the mp3 or image file is moved

param
url specifies the link, it could be a local file or could be a full url
return

        AbstractID3v2Frame frame = createFrame(getFrameAndSubIdFromGenericKey(FieldKey.COVER_ART).getFrameId());
        if (frame.getBody() instanceof FrameBodyAPIC)
        {
            FrameBodyAPIC body = (FrameBodyAPIC) frame.getBody();
            body.setObjectValue(DataTypes.OBJ_PICTURE_DATA, Utils.getDefaultBytes(url, TextEncoding.CHARSET_ISO_8859_1));
            body.setObjectValue(DataTypes.OBJ_PICTURE_TYPE, PictureTypes.DEFAULT_ID);
            body.setObjectValue(DataTypes.OBJ_MIME_TYPE, FrameBodyAPIC.IMAGE_IS_URL);
            body.setObjectValue(DataTypes.OBJ_DESCRIPTION, "");
        }
        else if (frame.getBody() instanceof FrameBodyPIC)
        {
            FrameBodyPIC body = (FrameBodyPIC) frame.getBody();
            body.setObjectValue(DataTypes.OBJ_PICTURE_DATA, Utils.getDefaultBytes(url, TextEncoding.CHARSET_ISO_8859_1));
            body.setObjectValue(DataTypes.OBJ_PICTURE_TYPE, PictureTypes.DEFAULT_ID);
            body.setObjectValue(DataTypes.OBJ_IMAGE_FORMAT, FrameBodyAPIC.IMAGE_IS_URL);
            body.setObjectValue(DataTypes.OBJ_DESCRIPTION, "");
        }
        return frame;
    
public voidcreateStructure()

        createStructureHeader();
        createStructureBody();
    
public voidcreateStructureBody()

        MP3File.getStructureFormatter().openHeadingElement(TYPE_BODY, "");

        AbstractID3v2Frame frame;
        for (Object o : frameMap.values())
        {
            if (o instanceof AbstractID3v2Frame)
            {
                frame = (AbstractID3v2Frame) o;
                frame.createStructure();
            }
            else
            {
                ArrayList<AbstractID3v2Frame> multiFrames = (ArrayList<AbstractID3v2Frame>) o;
                for (ListIterator<AbstractID3v2Frame> li = multiFrames.listIterator(); li.hasNext();)
                {
                    frame = li.next();
                    frame.createStructure();
                }
            }
        }
        MP3File.getStructureFormatter().closeHeadingElement(TYPE_BODY);
    
public voidcreateStructureHeader()

        MP3File.getStructureFormatter().addElement(TYPE_DUPLICATEBYTES, this.duplicateBytes);
        MP3File.getStructureFormatter().addElement(TYPE_DUPLICATEFRAMEID, this.duplicateFrameId);
        MP3File.getStructureFormatter().addElement(TYPE_EMPTYFRAMEBYTES, this.emptyFrameBytes);
        MP3File.getStructureFormatter().addElement(TYPE_FILEREADSIZE, this.fileReadSize);
        MP3File.getStructureFormatter().addElement(TYPE_INVALIDFRAMES, this.invalidFrames);
    
public voiddelete(java.io.RandomAccessFile file)
Delete Tag

param
file to delete the tag from
throws
IOException if problem accessing the file

        // this works by just erasing the "ID3" tag at the beginning
        // of the file
        byte[] buffer = new byte[FIELD_TAGID_LENGTH];
        //Read into Byte Buffer
        final FileChannel fc = file.getChannel();
        fc.position();
        ByteBuffer byteBuffer = ByteBuffer.allocate(TAG_HEADER_LENGTH);
        fc.read(byteBuffer, 0);
        byteBuffer.flip();
        if (seek(byteBuffer))
        {
            file.seek(0L);
            file.write(buffer);
        }
    
public voiddeleteArtworkField()
Delete all instance of artwork Field

throws
KeyNotFoundException

        this.deleteField(FieldKey.COVER_ART);
    
public voiddeleteField(FieldKey genericKey)
Delete fields with this generic key

param
genericKey

        if (genericKey == null)
        {
            throw new KeyNotFoundException();
        }
        FrameAndSubId formatKey = getFrameAndSubIdFromGenericKey(genericKey);
        doDeleteTagField(formatKey);
    
protected TagFielddoCreateTagField(org.jaudiotagger.tag.id3.AbstractID3v2Tag$FrameAndSubId formatKey, java.lang.String value)
Create Frame for Id3 Key

Only textual data supported at the moment, should only be used with frames that support a simple string argument.

param
formatKey
param
value
return
throws
KeyNotFoundException
throws
FieldDataInvalidException

        AbstractID3v2Frame frame = createFrame(formatKey.getFrameId());
        if (frame.getBody() instanceof FrameBodyUFID)
        {
            ((FrameBodyUFID) frame.getBody()).setOwner(formatKey.getSubId());
            try
            {
                ((FrameBodyUFID) frame.getBody()).setUniqueIdentifier(value.getBytes("ISO-8859-1"));
            }
            catch (UnsupportedEncodingException uee)
            {
                //This will never happen because we are using a charset supported on all platforms
                //but just in case
                throw new RuntimeException("When encoding UFID charset ISO-8859-1 was deemed unsupported");
            }
        }
        else if (frame.getBody() instanceof FrameBodyTXXX)
        {
            ((FrameBodyTXXX) frame.getBody()).setDescription(formatKey.getSubId());
            ((FrameBodyTXXX) frame.getBody()).setText(value);
        }
        else if (frame.getBody() instanceof FrameBodyWXXX)
        {
            ((FrameBodyWXXX) frame.getBody()).setDescription(formatKey.getSubId());
            ((FrameBodyWXXX) frame.getBody()).setUrlLink(value);
        }
        else if (frame.getBody() instanceof FrameBodyCOMM)
        {
            //Set description if set
            if (formatKey.getSubId() != null)
            {
                ((FrameBodyCOMM) frame.getBody()).setDescription(formatKey.getSubId());
                //Special Handling for Media Monkey Compatability
                if (((FrameBodyCOMM) frame.getBody()).isMediaMonkeyFrame())
                {
                    ((FrameBodyCOMM) frame.getBody()).setLanguage(Languages.MEDIA_MONKEY_ID);
                }
            }
            ((FrameBodyCOMM) frame.getBody()).setText(value);
        }
        else if (frame.getBody() instanceof FrameBodyUSLT)
        {
            ((FrameBodyUSLT) frame.getBody()).setDescription("");
            ((FrameBodyUSLT) frame.getBody()).setLyric(value);
        }
        else if (frame.getBody() instanceof FrameBodyWOAR)
        {
            ((FrameBodyWOAR) frame.getBody()).setUrlLink(value);
        }
        else if (frame.getBody() instanceof AbstractFrameBodyTextInfo)
        {
            ((AbstractFrameBodyTextInfo) frame.getBody()).setText(value);
        }
        else if (frame.getBody() instanceof FrameBodyPOPM)
        {
            ((FrameBodyPOPM) frame.getBody()).parseString(value);
        }
        else if (frame.getBody() instanceof FrameBodyIPLS)
        {
            PairedTextEncodedStringNullTerminated.ValuePairs pair = new PairedTextEncodedStringNullTerminated.ValuePairs();
            pair.add(formatKey.getSubId(), value);
            frame.getBody().setObjectValue(DataTypes.OBJ_TEXT, pair);
        }
        else if (frame.getBody() instanceof FrameBodyTIPL)
        {
            PairedTextEncodedStringNullTerminated.ValuePairs pair = new PairedTextEncodedStringNullTerminated.ValuePairs();
            pair.add(formatKey.getSubId(), value);
            frame.getBody().setObjectValue(DataTypes.OBJ_TEXT, pair);
        }
        else if ((frame.getBody() instanceof FrameBodyAPIC) || (frame.getBody() instanceof FrameBodyPIC))
        {
            throw new UnsupportedOperationException(ErrorMessage.ARTWORK_CANNOT_BE_CREATED_WITH_THIS_METHOD.getMsg());
        }
        else
        {
            throw new FieldDataInvalidException("Field with key of:" + formatKey.getFrameId() + ":does not accept cannot parse data:" + value);
        }
        return frame;
    
protected voiddoDeleteTagField(org.jaudiotagger.tag.id3.AbstractID3v2Tag$FrameAndSubId formatKey)
Internal delete method

param
formatKey
throws
KeyNotFoundException

        //Simple 1 to 1 mapping
        if (formatKey.getSubId() == null)
        {
            removeFrame(formatKey.getFrameId());
        }
        else
        {
            //Get list of frames that this uses
            List<TagField> list = getFields(formatKey.getFrameId());
            ListIterator<TagField> li = list.listIterator();
            while (li.hasNext())
            {
                AbstractTagFrameBody next = ((AbstractID3v2Frame) li.next()).getBody();
                if (next instanceof FrameBodyTXXX)
                {
                    if (((FrameBodyTXXX) next).getDescription().equals(formatKey.getSubId()))
                    {
                        if(list.size()==1)
                        {
                            removeFrame(formatKey.getFrameId());
                        }
                        else
                        {
                            li.remove();
                        }
                    }
                }
                else if (next instanceof FrameBodyWXXX)
                {
                    if (((FrameBodyWXXX) next).getDescription().equals(formatKey.getSubId()))
                    {
                        if(list.size()==1)
                        {
                            removeFrame(formatKey.getFrameId());
                        }
                        else
                        {
                            li.remove();
                        }
                    }
                }
                else if (next instanceof FrameBodyUFID)
                {
                    if (Arrays.equals(((FrameBodyUFID) next).getUniqueIdentifier(), formatKey.getSubId().getBytes()))
                    {
                        if(list.size()==1)
                        {
                            removeFrame(formatKey.getFrameId());
                        }
                        else
                        {
                            li.remove();
                        }
                    }
                }
                //A single TIPL frame is used for multiple fields, so we just delete the matching pair rather than
                //deleting the frame itself unless now empty
                else if (next instanceof FrameBodyTIPL)
                {
                    PairedTextEncodedStringNullTerminated.ValuePairs pairs = ((FrameBodyTIPL) next).getPairing();
                    ListIterator<Pair> pairIterator = pairs.getMapping().listIterator();
                    while(pairIterator.hasNext())
                    {
                        Pair nextPair =  pairIterator.next();
                        if(nextPair.getKey().equals(formatKey.getSubId()))
                        {
                            pairIterator.remove();
                        }
                    }
                    if(pairs.getMapping().size()==0)
                    {
                        removeFrame(formatKey.getFrameId());
                    }
                }
                //A single IPLS frame is used for multiple fields, so we just delete the matching pair rather than
                //deleting the frame itself unless now empty 
                else if (next instanceof FrameBodyIPLS)
                {
                    PairedTextEncodedStringNullTerminated.ValuePairs pairs = ((FrameBodyIPLS) next).getPairing();
                    ListIterator<Pair> pairIterator = pairs.getMapping().listIterator();
                    while(pairIterator.hasNext())
                    {
                        Pair nextPair =  pairIterator.next();
                        if(nextPair.getKey().equals(formatKey.getSubId()))
                        {
                            pairIterator.remove();
                        }
                    }

                    if(pairs.getMapping().size()==0)
                    {
                        removeFrame(formatKey.getFrameId());
                    }
                }
                else
                {
                    throw new RuntimeException("Need to implement getFields(FieldKey genericKey) for:" + next.getClass());
                }
            }
        }
    
protected java.lang.StringdoGetValueAtIndex(org.jaudiotagger.tag.id3.AbstractID3v2Tag$FrameAndSubId formatKey, int index)

param
formatKey
param
index
return
throws
KeyNotFoundException

        //Simple 1 to 1 mapping
        if (formatKey.getSubId() == null)
        {
            List<TagField> list = getFields(formatKey.getFrameId());
            if (list.size() > index)
            {
                return getTextValueForFrame((AbstractID3v2Frame) list.get(index));
            }
        }
        else
        {
            //Get list of frames that this uses
            List<TagField> list = getFields(formatKey.getFrameId());
            ListIterator<TagField> li = list.listIterator();
            List<String> listOfMatches = new ArrayList<String>();
            while (li.hasNext())
            {
                AbstractTagFrameBody next = ((AbstractID3v2Frame) li.next()).getBody();

                if (next instanceof FrameBodyTXXX)
                {
                    if (((FrameBodyTXXX) next).getDescription().equals(formatKey.getSubId()))
                    {
                        listOfMatches.add(((FrameBodyTXXX) next).getText());
                    }
                }
                else if (next instanceof FrameBodyWXXX)
                {
                    if (((FrameBodyWXXX) next).getDescription().equals(formatKey.getSubId()))
                    {
                        listOfMatches.add(((FrameBodyWXXX) next).getUrlLink());
                    }
                }
                else if (next instanceof FrameBodyCOMM)
                {
                    if (((FrameBodyCOMM) next).getDescription().equals(formatKey.getSubId()))
                    {
                        listOfMatches.add(((FrameBodyCOMM) next).getText());
                    }
                }
                else if (next instanceof FrameBodyUFID)
                {
                    if (Arrays.equals(((FrameBodyUFID) next).getUniqueIdentifier(), formatKey.getSubId().getBytes()))
                    {
                        listOfMatches.add(new String(((FrameBodyUFID) next).getUniqueIdentifier()));
                    }
                }
                else if (next instanceof FrameBodyIPLS)
                {
                    for (Pair entry : ((FrameBodyIPLS) next).getPairing().getMapping())
                    {
                        if (entry.getKey().equals(formatKey.getSubId()))
                        {
                            listOfMatches.add(entry.getValue());
                        }
                    }
                }
                else if (next instanceof FrameBodyTIPL)
                {
                    for (Pair entry : ((FrameBodyTIPL) next).getPairing().getMapping())
                    {
                        if (entry.getKey().equals(formatKey.getSubId()))
                        {
                            listOfMatches.add(entry.getValue());
                        }
                    }
                }
                else
                {
                    throw new RuntimeException("Need to implement getFields(FieldKey genericKey) for:" + next.getClass());
                }
            }
            if (listOfMatches.size() > index)
            {
                return listOfMatches.get(index);
            }
            else
            {
                return "";
            }
        }
        return "";
    
public booleanequals(java.lang.Object obj)
Is this tag equivalent to another

param
obj to test for equivalence
return
true if they are equivalent

        if (!(obj instanceof AbstractID3v2Tag))
        {
            return false;
        }
        AbstractID3v2Tag object = (AbstractID3v2Tag) obj;
        return this.frameMap.equals(object.frameMap) && super.equals(obj);
    
public intgetDuplicateBytes()
Returns the number of bytes which come from duplicate frames

return
the number of bytes which come from duplicate frames

        return duplicateBytes;
    
public java.lang.StringgetDuplicateFrameId()
Return the string which holds the ids of all duplicate frames.

return
the string which holds the ids of all duplicate frames.

        return duplicateFrameId;
    
public intgetEmptyFrameBytes()
Returns the number of bytes which come from empty frames

return
the number of bytes which come from empty frames

        return emptyFrameBytes;
    
public java.lang.ObjectgetEncryptedFrame(java.lang.String identifier)
Return any encrypted frames with this identifier

For single frames return the frame in this tag with given identifier if it exists, if multiple frames exist with the same identifier it will return a list containing all the frames with this identifier

param
identifier
return

        return encryptedFrameMap.get(identifier);
    
public intgetFieldCount()
Count number of frames/fields in this tag

return

        Iterator<TagField> it = getFields();
        int count = 0;

        //Done this way because it.hasNext() incorrectly counts empty list
        //whereas it.next() works correctly
        try
        {
            while (true)
            {
                TagField next = it.next();
                count++;
            }
        }
        catch (NoSuchElementException nse)
        {
            //this is thrown when no more elements
        }
        return count;
    
public intgetFieldCountIncludingSubValues()
Return count of fields, this considers a text frame with two null separated values as two fields, if you want a count of frames @see getFrameCount

return
count of fields

        Iterator<TagField> it = getFields();
        int count = 0;

        //Done this way because it.hasNext() incorrectly counts empty list
        //whereas it.next() works correctly
        try
        {
            while (true)
            {
                TagField next = it.next();
                if (next instanceof AbstractID3v2Frame)
                {
                    AbstractID3v2Frame frame = (AbstractID3v2Frame) next;
                    if ((frame.getBody() instanceof AbstractFrameBodyTextInfo) && !(frame.getBody() instanceof FrameBodyTXXX))
                    {
                        AbstractFrameBodyTextInfo frameBody = (AbstractFrameBodyTextInfo) frame.getBody();
                        count += frameBody.getNumberOfValues();
                        continue;
                    }
                }
                count++;
            }
        }
        catch (NoSuchElementException nse)
        {
            //this is thrown when no more elements
        }
        return count;
    
public java.util.ListgetFields(java.lang.String id)
Retrieve the values that exists for this id3 frame id

        Object o = getFrame(id);
        if (o == null)
        {
            return new ArrayList<TagField>();
        }
        else if (o instanceof List)
        {
            //TODO should return copy
            return (List<TagField>) o;
        }
        else if (o instanceof AbstractID3v2Frame)
        {
            List<TagField> list = new ArrayList<TagField>();
            list.add((TagField) o);
            return list;
        }
        else
        {
            throw new RuntimeException("Found entry in frameMap that was not a frame or a list:" + o);
        }
    
public java.util.IteratorgetFields()

return
iterator of all fields, multiple values for the same Id (e.g multiple TXXX frames) count as separate fields

        //Iterator of each different frameId in this tag
        final Iterator<Map.Entry<String, Object>> it = this.frameMap.entrySet().iterator();

        //Iterator used by hasNext() so doesn't effect next()
        final Iterator<Map.Entry<String, Object>> itHasNext = this.frameMap.entrySet().iterator();


        return new Iterator<TagField>()
        {
            Map.Entry<String, Object> latestEntry = null;

            //this iterates through frames through for a particular frameId
            private Iterator<TagField> fieldsIt;

            private void changeIt()
            {
                if (!it.hasNext())
                {
                    return;
                }

                while (it.hasNext())
                {
                    Map.Entry<String, Object> e = it.next();
                    latestEntry = itHasNext.next();
                    if (e.getValue() instanceof List)
                    {
                        List<TagField> l = (List<TagField>) e.getValue();
                        //If list is empty (which it shouldn't be) we skip over this entry
                        if (l.size() == 0)
                        {
                            continue;
                        }
                        else
                        {
                            fieldsIt = l.iterator();
                            break;
                        }
                    }
                    else
                    {
                        //TODO must be a better way
                        List<TagField> l = new ArrayList<TagField>();
                        l.add((TagField) e.getValue());
                        fieldsIt = l.iterator();
                        break;
                    }
                }
            }

            //TODO assumes if have entry its valid, but what if empty list but very different to check this
            //without causing a side effect on next() so leaving for now
            public boolean hasNext()
            {
                //Check Current frameId, does it contain more values
                if (fieldsIt != null)
                {
                    if (fieldsIt.hasNext())
                    {
                        return true;
                    }
                }

                //No remaining entries return false
                if (!itHasNext.hasNext())
                {
                    return false;
                }

                //Issue #236
                //TODO assumes if have entry its valid, but what if empty list but very different to check this
                //without causing a side effect on next() so leaving for now
                return itHasNext.hasNext();
            }

            public TagField next()
            {
                //Hasn't been initialized yet
                if (fieldsIt == null)
                {
                    changeIt();
                }

                if (fieldsIt != null)
                {
                    //Go to the end of the run
                    if (!fieldsIt.hasNext())
                    {
                        changeIt();
                    }
                }

                if (fieldsIt == null)
                {
                    throw new NoSuchElementException();
                }
                return fieldsIt.next();
            }

            public void remove()
            {
                fieldsIt.remove();
            }
        };
    
public java.util.ListgetFields(FieldKey genericKey)
Get field(s) for this key

param
genericKey
return
throws
KeyNotFoundException


        if (genericKey == null)
        {
            throw new KeyNotFoundException();
        }

        FrameAndSubId formatKey = getFrameAndSubIdFromGenericKey(genericKey);

        //Get list of frames that this uses, as we are going to remove entries we don't want take a copy
        List<TagField> list = getFields(formatKey.getFrameId());
        List<TagField> filteredList = new ArrayList<TagField>();
        String subFieldId = formatKey.getSubId();

        //... do we need to refine the list further i.e we only want TXXX frames that relate to the particular
        //key that was passed as a parameter
        if (subFieldId != null)
        {
            for (TagField tagfield : list)
            {
                AbstractTagFrameBody next = ((AbstractID3v2Frame) tagfield).getBody();
                if (next instanceof FrameBodyTXXX)
                {
                    if (((FrameBodyTXXX) next).getDescription().equals(formatKey.getSubId()))
                    {
                        filteredList.add(tagfield);
                    }
                }
                else if (next instanceof FrameBodyWXXX)
                {
                    if (((FrameBodyWXXX) next).getDescription().equals(formatKey.getSubId()))
                    {
                        filteredList.add(tagfield);
                    }
                }
                else if (next instanceof FrameBodyCOMM)
                {
                    if (((FrameBodyCOMM) next).getDescription().equals(formatKey.getSubId()))
                    {
                        filteredList.add(tagfield);
                    }
                }
                else if (next instanceof FrameBodyUFID)
                {
                    if (Arrays.equals(((FrameBodyUFID) next).getUniqueIdentifier(), formatKey.getSubId().getBytes()))
                    {
                        filteredList.add(tagfield);
                    }
                }
                else if (next instanceof FrameBodyIPLS)
                {
                    for (Pair entry : ((FrameBodyIPLS) next).getPairing().getMapping())
                    {
                        if (entry.getKey().equals(formatKey.getSubId()))
                        {
                            filteredList.add(tagfield);
                        }
                    }
                }
                else if (next instanceof FrameBodyTIPL)
                {
                    for (Pair entry : ((FrameBodyTIPL) next).getPairing().getMapping())
                    {

                        if (entry.getKey().equals(formatKey.getSubId()))
                        {
                            filteredList.add(tagfield);
                        }
                    }
                }
                else
                {
                    throw new RuntimeException("Need to implement getFields(FieldKey genericKey) for:" + next.getClass());
                }
            }
            return filteredList;
        }
        else
        {
            return list;
        }
    
protected java.nio.channels.FileLockgetFileLockForWriting(java.nio.channels.FileChannel fileChannel, java.lang.String filePath)
Get file lock for writing too file

TODO:this appears to have little effect on Windows Vista

param
fileChannel
param
filePath
return
lock or null if locking is not supported
throws
IOException if unable to get lock because already locked by another program
throws
java.nio.channels.OverlappingFileLockException if already locked by another thread in the same VM, we dont catch this because indicates a programming error

        logger.finest("locking fileChannel for " + filePath);
        FileLock fileLock;
        try
        {
            fileLock = fileChannel.tryLock();
        }
        //Assumes locking is not supported on this platform so just returns null
        catch (IOException exception)
        {
            return null;
        }

        //Couldnt getFields lock because file is already locked by another application
        if (fileLock == null)
        {
            throw new IOException(ErrorMessage.GENERAL_WRITE_FAILED_FILE_LOCKED.getMsg(filePath));
        }
        return fileLock;
    
public intgetFileReadBytes()
Returns the tag size as reported by the tag header

return
the tag size as reported by the tag header

        return fileReadSize;
    
public java.lang.StringgetFirst(java.lang.String identifier)
Retrieve the first value that exists for this identifier

If the value is a String it returns that, otherwise returns a summary of the fields information

param
identifier
return

        AbstractID3v2Frame frame = getFirstField(identifier);
        if (frame == null)
        {
            return "";
        }
        return getTextValueForFrame(frame);
    
public java.lang.StringgetFirst(FieldKey genericKey)
Retrieve the first value that exists for this generic key

param
genericKey
return

        return getValue(genericKey, 0);
    
public org.jaudiotagger.tag.images.ArtworkgetFirstArtwork()

        List<Artwork> artwork = getArtworkList();
        if (artwork.size() > 0)
        {
            return artwork.get(0);
        }
        return null;
    
public TagFieldgetFirstField(FieldKey genericKey)

        List<TagField> fields = getFields(genericKey);
        if (fields.size() > 0)
        {
            return fields.get(0);
        }
        return null;
    
public AbstractID3v2FramegetFirstField(java.lang.String identifier)
Retrieve the first tag field that exists for this identifier

param
identifier
return
tag field or null if doesn't exist

        Object object = getFrame(identifier);
        if (object == null)
        {
            return null;
        }
        if (object instanceof List)
        {
            return ((List<AbstractID3v2Frame>) object).get(0);
        }
        else
        {
            return (AbstractID3v2Frame) object;
        }
    
public java.lang.ObjectgetFrame(java.lang.String identifier)
For single frames return the frame in this tag with given identifier if it exists, if multiple frames exist with the same identifier it will return a list containing all the frames with this identifier

Warning the match is only done against the identifier so if a tag contains a frame with an unsupported body but happens to have an identifier that is valid for another version of the tag it will be returned.

param
identifier is an ID3Frame identifier
return
matching frame, or list of matching frames

        return frameMap.get(identifier);
    
protected abstract org.jaudiotagger.tag.id3.AbstractID3v2Tag$FrameAndSubIdgetFrameAndSubIdFromGenericKey(FieldKey genericKey)

public java.util.IteratorgetFrameOfType(java.lang.String identifier)
Return all frames which start with the identifier, this can be more than one which is useful if trying to retrieve similar frames e.g TIT1,TIT2,TIT3 ... and don't know exactly which ones there are.

Warning the match is only done against the identifier so if a tag contains a frame with an unsupported body but happens to have an identifier that is valid for another version of the tag it will be returned.

param
identifier
return
an iterator of all the frames starting with a particular identifier

        Iterator<String> iterator = frameMap.keySet().iterator();
        HashSet result = new HashSet();
        String key;
        while (iterator.hasNext())
        {
            key = iterator.next();
            if (key.startsWith(identifier))
            {
                result.add(frameMap.get(key));
            }
        }
        return result.iterator();
    
protected abstract ID3FramesgetID3Frames()

public intgetInvalidFrames()
Return byte count of invalid frames

return
byte count of invalid frames

        return invalidFrames;
    
public abstract java.util.ComparatorgetPreferredFrameOrderComparator()

return
comparator used to order frames in preferred order for writing to file so that most important frames are written first.

public intgetSize()
Return tag size based upon the sizes of the tags rather than the physical no of bytes between start of ID3Tag and start of Audio Data.Should be extended by subclasses to include header.

return
size of the tag

        int size = 0;
        Iterator iterator = frameMap.values().iterator();
        AbstractID3v2Frame frame;
        while (iterator.hasNext())
        {
            Object o = iterator.next();
            if (o instanceof AbstractID3v2Frame)
            {
                frame = (AbstractID3v2Frame) o;
                size += frame.getSize();
            }
            else
            {
                ArrayList<AbstractID3v2Frame> multiFrames = (ArrayList<AbstractID3v2Frame>) o;
                for (ListIterator<AbstractID3v2Frame> li = multiFrames.listIterator(); li.hasNext();)
                {
                    frame = li.next();
                    size += frame.getSize();
                }
            }
        }
        return size;
    
public java.lang.StringgetSubValue(FieldKey genericKey, int n, int m)
Retrieve the mth value that exists in the nth frame for this generic key

param
genericKey
param
n the index of the frame
param
m
return

        String wholeValue = getValue(genericKey, n);
        List<String> values = TextEncodedStringSizeTerminated.splitByNullSeperator(wholeValue);
        if (values.size() > m)
        {
            return values.get(m);
        }
        return "";
    
private java.lang.StringgetTextValueForFrame(AbstractID3v2Frame frame)

param
frame
return

        return frame.getBody().getUserFriendlyValue();
    
public static longgetV2TagSizeIfExists(java.io.File file)
Checks to see if the file contains an ID3tag and if so return its size as reported in the tag header and return the size of the tag (including header), if no such tag exists return zero.

param
file
return
the end of the tag in the file or zero if no tag exists.
throws
java.io.IOException

        FileInputStream fis = null;
        FileChannel fc = null;
        ByteBuffer bb = null;
        try
        {
            //Files
            fis = new FileInputStream(file);
            fc = fis.getChannel();

            //Read possible Tag header  Byte Buffer
            bb = ByteBuffer.allocate(TAG_HEADER_LENGTH);
            fc.read(bb);
            bb.flip();
            if (bb.limit() < (TAG_HEADER_LENGTH))
            {
                return 0;
            }
        }
        finally
        {
            if (fc != null)
            {
                fc.close();
            }

            if (fis != null)
            {
                fis.close();
            }
        }

        //ID3 identifier
        byte[] tagIdentifier = new byte[FIELD_TAGID_LENGTH];
        bb.get(tagIdentifier, 0, FIELD_TAGID_LENGTH);
        if (!(Arrays.equals(tagIdentifier, TAG_ID)))
        {
            return 0;
        }

        //Is it valid Major Version
        byte majorVersion = bb.get();
        if ((majorVersion != ID3v22Tag.MAJOR_VERSION) && (majorVersion != ID3v23Tag.MAJOR_VERSION) && (majorVersion != ID3v24Tag.MAJOR_VERSION))
        {
            return 0;
        }

        //Skip Minor Version
        bb.get();

        //Skip Flags
        bb.get();

        //Get size as recorded in frame header
        int frameSize = ID3SyncSafeInteger.bufferToValue(bb);

        //addField header size to frame size
        frameSize += TAG_HEADER_LENGTH;
        return frameSize;
    
public java.lang.StringgetValue(FieldKey genericKey, int index)
Retrieve the value that exists for this generic key and this index

Have to do some special mapping for certain generic keys because they share frame with another generic key.

param
genericKey
return

        if (genericKey == null)
        {
            throw new KeyNotFoundException();
        }

        FrameAndSubId frameAndSubId = getFrameAndSubIdFromGenericKey(genericKey);

        List<TagField> fields = getFields(genericKey);
        if (fields != null && fields.size() > index)
        {
            AbstractID3v2Frame frame = (AbstractID3v2Frame) fields.get(index);
            if (frame != null)
            {
                if (genericKey == FieldKey.TRACK)
                {
                    return String.valueOf(((FrameBodyTRCK) frame.getBody()).getTrackNo());
                }
                else if (genericKey == FieldKey.TRACK_TOTAL)
                {
                    return String.valueOf(((FrameBodyTRCK) frame.getBody()).getTrackTotal());
                }
                else if (genericKey == FieldKey.DISC_NO)
                {
                    return String.valueOf(((FrameBodyTPOS) frame.getBody()).getDiscNo());
                }
                else if (genericKey == FieldKey.DISC_TOTAL)
                {
                    return String.valueOf(((FrameBodyTPOS) frame.getBody()).getDiscTotal());
                }
                else if (genericKey == FieldKey.RATING)
                {
                    return String.valueOf(((FrameBodyPOPM) frame.getBody()).getRating());
                }
                else
                {
                    return doGetValueAtIndex(frameAndSubId, index);
                }
            }
            else
            {
                return "";
            }
        }
        return "";
    
public booleanhasCommonFields()

        return true;
    
public booleanhasField(FieldKey key)
Does this tag contain a field with the specified key

param
key The field id to look for.
return

        return getFirstField(key)!=null;
    
public booleanhasField(java.lang.String id)
Does this tag contain a field with the specified id

see
org.jaudiotagger.tag.Tag#hasField(java.lang.String)

        return hasFrame(id);
    
public booleanhasFrame(java.lang.String identifier)
Return whether tag has frame with this identifier

Warning the match is only done against the identifier so if a tag contains a frame with an unsupported body but happens to have an identifier that is valid for another version of the tag it will return true

param
identifier frameId to lookup
return
true if tag has frame with this identifier

        return frameMap.containsKey(identifier);
    
public booleanhasFrameAndBody(java.lang.String identifier)
Return whether tag has frame with this identifier and a related body. This is required to protect against circumstances whereby a tag contains a frame with an unsupported body but happens to have an identifier that is valid for another version of the tag which it has been converted to

e.g TDRC is an invalid frame in a v23 tag but if somehow a v23tag has been created by another application with a TDRC frame we construct an UnsupportedFrameBody to hold it, then this library constructs a v24 tag, it will contain a frame with id TDRC but it will not have the expected frame body it is not really a TDRC frame.

param
identifier frameId to lookup
return
true if tag has frame with this identifier

        if (hasFrame(identifier))
        {
            Object o = getFrame(identifier);
            if (o instanceof AbstractID3v2Frame)
            {
                return !(((AbstractID3v2Frame) o).getBody() instanceof FrameBodyUnsupported);
            }
            return true;
        }
        return false;
    
public booleanhasFrameOfType(java.lang.String identifier)
Return whether tag has frame starting with this identifier

Warning the match is only done against the identifier so if a tag contains a frame with an unsupported body but happens to have an identifier that is valid for another version of the tag it will return true

param
identifier start of frameId to lookup
return
tag has frame starting with this identifier

        Iterator<String> iterator = frameMap.keySet().iterator();
        String key;
        boolean found = false;
        while (iterator.hasNext() && !found)
        {
            key = iterator.next();
            if (key.startsWith(identifier))
            {
                found = true;
            }
        }
        return found;
    
public booleanisEmpty()
Is this tag empty

see
org.jaudiotagger.tag.Tag#isEmpty()

        return frameMap.size() == 0;
    
private static booleanisID3V2Header(java.io.RandomAccessFile raf)
True if files has a ID3v2 header

param
raf
return
throws
IOException


                     
          
    
        long start = raf.getFilePointer();
        byte[] tagIdentifier = new byte[FIELD_TAGID_LENGTH];
        raf.read(tagIdentifier);
        raf.seek(start);
        if (!(Arrays.equals(tagIdentifier, TAG_ID)))
        {
            return false;
        }
        return true;
    
public static booleanisId3Tag(java.io.RandomAccessFile raf)
Determines if file contain an id3 tag and if so positions the file pointer just after the end of the tag. This method is used by non mp3s (such as .ogg and .flac) to determine if they contain an id3 tag

param
raf
return
throws
IOException

        if(!isID3V2Header(raf))
        {
            return false;
        }
        //So we have a tag
        byte[] tagHeader = new byte[FIELD_TAG_SIZE_LENGTH];
        raf.seek(raf.getFilePointer() + 6);
        raf.read(tagHeader);
        ByteBuffer bb = ByteBuffer.wrap(tagHeader);

        int size = ID3SyncSafeInteger.bufferToValue(bb);
        raf.seek(size + TAG_HEADER_LENGTH);
        return true;
    
public java.util.Iteratoriterator()
Return the frames in the order they were added

return
and iterator of the frmaes/list of multi value frames

        return frameMap.values().iterator();
    
protected voidloadFrameIntoMap(java.lang.String frameId, AbstractID3v2Frame next)
Add frame to the frame map

param
frameId
param
next

        if (next.getBody() instanceof FrameBodyEncrypted)
        {
            loadFrameIntoSpecifiedMap(encryptedFrameMap, frameId, next);
        }
        else
        {
            loadFrameIntoSpecifiedMap(frameMap, frameId, next);
        }
    
protected voidloadFrameIntoSpecifiedMap(java.util.HashMap map, java.lang.String frameId, AbstractID3v2Frame next)
Decides what to with the frame that has just been read from file. If the frame is an allowable duplicate frame and is a duplicate we add all frames into an ArrayList and add the ArrayList to the HashMap. if not allowed to be duplicate we store the number of bytes in the duplicateBytes variable and discard the frame itself.

param
frameId
param
next

        if ((ID3v24Frames.getInstanceOf().isMultipleAllowed(frameId)) || (ID3v23Frames.getInstanceOf().isMultipleAllowed(frameId)) || (ID3v22Frames.getInstanceOf().isMultipleAllowed(frameId)))
        {
            //If a frame already exists of this type
            if (map.containsKey(frameId))
            {
                Object o = map.get(frameId);
                if (o instanceof ArrayList)
                {
                    ArrayList<AbstractID3v2Frame> multiValues = (ArrayList<AbstractID3v2Frame>) o;
                    multiValues.add(next);
                    logger.finer("Adding Multi Frame(1)" + frameId);
                }
                else
                {
                    ArrayList<AbstractID3v2Frame> multiValues = new ArrayList<AbstractID3v2Frame>();
                    multiValues.add((AbstractID3v2Frame) o);
                    multiValues.add(next);
                    map.put(frameId, multiValues);
                    logger.finer("Adding Multi Frame(2)" + frameId);
                }
            }
            else
            {
                logger.finer("Adding Multi FrameList(3)" + frameId);
                map.put(frameId, next);
            }
        }
        //If duplicate frame just stores the name of the frame and the number of bytes the frame contains
        else if (map.containsKey(frameId))
        {
            logger.warning("Ignoring Duplicate Frame" + frameId);
            //If we have multiple duplicate frames in a tag separate them with semicolons
            if (this.duplicateFrameId.length() > 0)
            {
                this.duplicateFrameId += ";";
            }
            this.duplicateFrameId += frameId;
            this.duplicateBytes += ((AbstractID3v2Frame) frameMap.get(frameId)).getSize();
        }
        else
        {
            logger.finer("Adding Frame" + frameId);
            map.put(frameId, next);
        }
    
public voidmergeDuplicateFrames(AbstractID3v2Frame newFrame, java.util.List frames)
Add frame taking into account existing frames of the same type

param
newFrame
param
frames

        for (ListIterator<AbstractID3v2Frame> li = frames.listIterator(); li.hasNext();)
        {
            AbstractID3v2Frame nextFrame = li.next();

            if (newFrame.getBody() instanceof FrameBodyTXXX)
            {
                //Value with matching key exists so replace
                if (((FrameBodyTXXX) newFrame.getBody()).getDescription().equals(((FrameBodyTXXX) nextFrame.getBody()).getDescription()))
                {
                    li.set(newFrame);
                    frameMap.put(newFrame.getId(), frames);
                    return;
                }
            }
            else if (newFrame.getBody() instanceof FrameBodyWXXX)
            {
                //Value with matching key exists so replace
                if (((FrameBodyWXXX) newFrame.getBody()).getDescription().equals(((FrameBodyWXXX) nextFrame.getBody()).getDescription()))
                {
                    li.set(newFrame);
                    frameMap.put(newFrame.getId(), frames);
                    return;
                }
            }
            else if (newFrame.getBody() instanceof FrameBodyCOMM)
            {
                if (((FrameBodyCOMM) newFrame.getBody()).getDescription().equals(((FrameBodyCOMM) nextFrame.getBody()).getDescription()))
                {
                    li.set(newFrame);
                    frameMap.put(newFrame.getId(), frames);
                    return;
                }
            }
            else if (newFrame.getBody() instanceof FrameBodyUFID)
            {
                if (((FrameBodyUFID) newFrame.getBody()).getOwner().equals(((FrameBodyUFID) nextFrame.getBody()).getOwner()))
                {
                    li.set(newFrame);
                    frameMap.put(newFrame.getId(), frames);
                    return;
                }
            }
            else if (newFrame.getBody() instanceof FrameBodyUSLT)
            {
                if (((FrameBodyUSLT) newFrame.getBody()).getDescription().equals(((FrameBodyUSLT) nextFrame.getBody()).getDescription()))
                {
                    li.set(newFrame);
                    frameMap.put(newFrame.getId(), frames);
                    return;
                }
            }
            else if (newFrame.getBody() instanceof FrameBodyPOPM)
            {
                if (((FrameBodyPOPM) newFrame.getBody()).getEmailToUser().equals(((FrameBodyPOPM) nextFrame.getBody()).getEmailToUser()))
                {
                    li.set(newFrame);
                    frameMap.put(newFrame.getId(), frames);
                    return;
                }
            }
            //Just grab any additional info from new TRCK Frame and add to the existing one
            else if (newFrame.getBody() instanceof FrameBodyTRCK)
            {
                FrameBodyTRCK newBody = (FrameBodyTRCK) newFrame.getBody();
                FrameBodyTRCK oldBody = (FrameBodyTRCK) nextFrame.getBody();

                if (newBody.getTrackNo() != null && newBody.getTrackNo() > 0)
                {
                    oldBody.setTrackNo(newBody.getTrackNo());
                }

                if (newBody.getTrackTotal() != null && newBody.getTrackTotal() > 0)
                {
                    oldBody.setTrackTotal(newBody.getTrackTotal());
                }
                return;
            }
            //Just grab any additional info from new TPOS Frame and add to the existing one
            else if (newFrame.getBody() instanceof FrameBodyTPOS)
            {
                FrameBodyTPOS newBody = (FrameBodyTPOS) newFrame.getBody();
                FrameBodyTPOS oldBody = (FrameBodyTPOS) nextFrame.getBody();

                Integer newDiscNo = newBody.getDiscNo();
                if ((newDiscNo != null) && (newDiscNo > 0))
                {
                    oldBody.setDiscNo(newDiscNo);
                }

                Integer newDiscTotal = newBody.getDiscTotal();
                if ((newDiscTotal != null) && (newDiscTotal > 0))
                {
                    oldBody.setDiscTotal(newDiscTotal);
                }
                return;
            }
            else if (newFrame.getBody() instanceof FrameBodyIPLS)
            {
                FrameBodyIPLS frameBody         = (FrameBodyIPLS) newFrame.getBody();
                FrameBodyIPLS existingFrameBody = (FrameBodyIPLS) nextFrame.getBody();
                existingFrameBody.addPair(frameBody.getText());
                return;
            }
            else if (newFrame.getBody() instanceof FrameBodyTIPL)
            {
                FrameBodyTIPL frameBody         = (FrameBodyTIPL) newFrame.getBody();
                FrameBodyTIPL existingFrameBody = (FrameBodyTIPL) nextFrame.getBody();
                existingFrameBody.addPair(frameBody.getText());
                return;
            }
        }

        if(!getID3Frames().isMultipleAllowed(newFrame.getId()))
        {
            frameMap.put(newFrame.getId(), newFrame);
        }
        else
        {
            //No match found so addField new one
            frames.add(newFrame);
            frameMap.put(newFrame.getId(), frames);
        }
    
public voidremoveFrame(java.lang.String identifier)
Remove frame(s) with this identifier from tag

param
identifier frameId to look for

        logger.finest("Removing frame with identifier:" + identifier);
        frameMap.remove(identifier);
    
public voidremoveFrameOfType(java.lang.String identifier)
Remove any frames starting with this identifier from tag

param
identifier start of frameId to look for

        //First fine matching keys
        HashSet<String> result = new HashSet<String>();
        for (Object match : frameMap.keySet())
        {
            String key = (String) match;
            if (key.startsWith(identifier))
            {
                result.add(key);
            }
        }
        //Then deleteField outside of loop to prevent concurrent modificatioon eception if there are two keys
        //with the same id
        for (String match : result)
        {
            logger.finest("Removing frame with identifier:" + match + "because starts with:" + identifier);
            frameMap.remove(match);
        }
    
public voidremoveUnsupportedFrames()
Remove all frame(s) which have an unsupported body, in other words remove all frames that are not part of the standard frameSet for this tag

        for (Iterator i = iterator(); i.hasNext();)
        {
            Object o = i.next();
            if (o instanceof AbstractID3v2Frame)
            {
                if (((AbstractID3v2Frame) o).getBody() instanceof FrameBodyUnsupported)
                {
                    logger.finest("Removing frame" + ((AbstractID3v2Frame) o).getIdentifier());
                    i.remove();
                }
            }
        }
    
private voidreplaceFile(java.io.File originalFile, java.io.File newFile)
Replace originalFile with the contents of newFile

Both files must exist in the same folder so that there are no problems with filesystem mount points

param
newFile
param
originalFile
throws
IOException

        boolean renameOriginalResult;
        //Rename Original File to make a backup in case problem with new file
        File originalFileBackup = new File(originalFile.getAbsoluteFile().getParentFile().getPath(), AudioFile.getBaseFilename(originalFile) + ".old");
        //If already exists modify the suffix
        int count = 1;
        while (originalFileBackup.exists())
        {
            originalFileBackup = new File(originalFile.getAbsoluteFile().getParentFile().getPath(), AudioFile.getBaseFilename(originalFile) + ".old" + count);
            count++;
        }

        renameOriginalResult = originalFile.renameTo(originalFileBackup);
        if (!renameOriginalResult)
        {
            logger.warning(ErrorMessage.GENERAL_WRITE_FAILED_TO_RENAME_ORIGINAL_FILE_TO_BACKUP.getMsg(originalFile.getAbsolutePath(), originalFileBackup.getName()));
            newFile.delete();
            throw new UnableToRenameFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_RENAME_ORIGINAL_FILE_TO_BACKUP.getMsg(originalFile.getAbsolutePath(), originalFileBackup.getName()));
        }

        //Rename new Temporary file to the final file
        boolean renameResult = newFile.renameTo(originalFile);
        if (!renameResult)
        {
            //Renamed failed so lets do some checks rename the backup back to the original file
            //New File doesnt exist
            if (!newFile.exists())
            {
                logger.warning(ErrorMessage.GENERAL_WRITE_FAILED_NEW_FILE_DOESNT_EXIST.getMsg(newFile.getAbsolutePath()));
            }

            //Rename the backup back to the original
            renameOriginalResult = originalFileBackup.renameTo(originalFile);
            if (!renameOriginalResult)
            {
                //TODO now if this happens we are left with testfile.old instead of testfile.mp3
                logger.warning(ErrorMessage.GENERAL_WRITE_FAILED_TO_RENAME_ORIGINAL_BACKUP_TO_ORIGINAL.getMsg(originalFileBackup.getAbsolutePath(), originalFile.getName()));
            }


            logger.warning(ErrorMessage.GENERAL_WRITE_FAILED_TO_RENAME_TO_ORIGINAL_FILE.getMsg(originalFile.getAbsolutePath(), newFile.getName()));
            newFile.delete();
            throw new UnableToRenameFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_RENAME_TO_ORIGINAL_FILE.getMsg(originalFile.getAbsolutePath(), newFile.getName()));
        }
        else
        {
            //Rename was okay so we can now deleteField the backup of the original
            boolean deleteResult = originalFileBackup.delete();
            if (!deleteResult)
            {
                //Not a disaster but can't deleteField the backup so make a warning
                logger.warning(ErrorMessage.GENERAL_WRITE_WARNING_UNABLE_TO_DELETE_BACKUP_FILE.getMsg(originalFileBackup.getAbsolutePath()));
            }
        }
    
public booleanseek(java.nio.ByteBuffer byteBuffer)
Does a tag of the correct version exist in this file.

param
byteBuffer to search through
return
true if tag exists.

        byteBuffer.rewind();
        logger.config("ByteBuffer pos:" + byteBuffer.position() + ":limit" + byteBuffer.limit() + ":cap" + byteBuffer.capacity());


        byte[] tagIdentifier = new byte[FIELD_TAGID_LENGTH];
        byteBuffer.get(tagIdentifier, 0, FIELD_TAGID_LENGTH);
        if (!(Arrays.equals(tagIdentifier, TAG_ID)))
        {
            return false;
        }
        //Major Version
        if (byteBuffer.get() != getMajorVersion())
        {
            return false;
        }
        //Minor Version
        return byteBuffer.get() == getRevision();
    
public booleansetEncoding(java.lang.String enc)

        throw new UnsupportedOperationException("Not Implemented Yet");
    
public voidsetField(FieldKey genericKey, java.lang.String value)

        TagField tagfield = createField(genericKey, value);
        setField(tagfield);
    
public voidsetField(TagField field)

param
field
throws
FieldDataInvalidException

        if (!(field instanceof AbstractID3v2Frame))
        {
            throw new FieldDataInvalidException("Field " + field + " is not of type AbstractID3v2Frame");
        }

        AbstractID3v2Frame newFrame = (AbstractID3v2Frame) field;

        Object obj = frameMap.get(field.getId());


        //If no frame of this type exist or if multiples are not allowed
        if (obj == null)
        {
            frameMap.put(field.getId(), field);
        }
        //frame of this type already exists
        else if (obj instanceof AbstractID3v2Frame)
        {
            List<AbstractID3v2Frame> frames = new ArrayList<AbstractID3v2Frame>();
            frames.add((AbstractID3v2Frame) obj);
            mergeDuplicateFrames(newFrame, frames);
        }
        //Multiple frames of this type already exist
        else if (obj instanceof List)
        {
            mergeDuplicateFrames(newFrame, (List<AbstractID3v2Frame>) obj);
        }
    
public voidsetField(org.jaudiotagger.tag.images.Artwork artwork)
Create field and then set within tag itself

param
artwork
throws
FieldDataInvalidException

        this.setField(createField(artwork));
    
public voidsetFrame(AbstractID3v2Frame frame)
Add a frame to this tag

param
frame the frame to add

Warning if frame(s) already exists for this identifier that they are overwritten

        frameMap.put(frame.getIdentifier(), frame);
    
public voidsetFrame(java.lang.String identifier, java.util.List multiFrame)
Used for setting multiple frames for a single frame Identifier

Warning if frame(s) already exists for this identifier thay are overwritten

TODO needs to ensure do not add an invalid frame for this tag

param
identifier
param
multiFrame

        logger.finest("Adding " + multiFrame.size() + " frames for " + identifier);
        frameMap.put(identifier, multiFrame);
    
public java.lang.StringtoString()

        final StringBuilder out = new StringBuilder();
        out.append("Tag content:\n");
        final Iterator<TagField> it = getFields();
        while (it.hasNext())
        {
            final TagField field = it.next();
            out.append("\t");
            out.append(field.getId());
            out.append(":");
            out.append(field.toString());
            out.append("\n");
        }

        return out.toString();
    
public voidwrite(java.io.File file, long audioStartByte)
Write tag to file.

param
file
param
audioStartByte
throws
IOException TODO should be abstract

    
public voidwrite(java.io.RandomAccessFile file)
Write tag to file.

param
file
throws
IOException TODO should be abstract

    
public voidwrite(java.nio.channels.WritableByteChannel channel)
Write tag to channel.

param
channel
throws
IOException TODO should be abstract

    
public voidwrite(java.io.OutputStream outputStream)
Write tag to output stream

param
outputStream
throws
IOException

        write(Channels.newChannel(outputStream));
    
protected voidwriteBufferToFile(java.io.File file, java.nio.ByteBuffer headerBuffer, byte[] bodyByteBuffer, int padding, int sizeIncPadding, long audioStartLocation)
Write the data from the buffer to the file

param
file
param
headerBuffer
param
bodyByteBuffer
param
padding
param
sizeIncPadding
param
audioStartLocation
throws
IOException

        FileChannel fc = null;
        FileLock fileLock = null;

        //We need to adjust location of audio file if true
        if (sizeIncPadding > audioStartLocation)
        {
            logger.finest("Adjusting Padding");
            adjustPadding(file, sizeIncPadding, audioStartLocation);
        }

        try
        {
            fc = new RandomAccessFile(file, "rws").getChannel();
            fileLock = getFileLockForWriting(fc, file.getPath());
            fc.write(headerBuffer);
            fc.write(ByteBuffer.wrap(bodyByteBuffer));
            fc.write(ByteBuffer.wrap(new byte[padding]));
        }
        catch (FileNotFoundException fe)
        {
            logger.log(Level.SEVERE, getLoggingFilename() + fe.getMessage(), fe);
            if (fe.getMessage().equals(FileSystemMessage.ACCESS_IS_DENIED.getMsg()))
            {
                logger.severe(ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg(file.getPath()));
                throw new UnableToModifyFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg(file.getPath()));
            }
            else
            {
                logger.severe(ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg(file.getPath()));
                throw new UnableToCreateFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg(file.getPath()));
            }
        }
        catch (IOException ioe)
        {
            logger.log(Level.SEVERE, getLoggingFilename() + ioe.getMessage(), ioe);
            if (ioe.getMessage().equals(FileSystemMessage.ACCESS_IS_DENIED.getMsg()))
            {
                logger.severe(ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg(file.getParentFile().getPath()));
                throw new UnableToModifyFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg(file.getParentFile().getPath()));
            }
            else
            {
                logger.severe(ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg(file.getParentFile().getPath()));
                throw new UnableToCreateFileException(ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg(file.getParentFile().getPath()));
            }
        }
        finally
        {
            if (fc != null)
            {
                if (fileLock != null)
                {
                    fileLock.release();
                }
                fc.close();
            }
        }
    
protected java.io.ByteArrayOutputStreamwriteFramesToBuffer()
Write all the frames to the byteArrayOutputStream

Currently Write all frames, defaults to the order in which they were loaded, newly created frames will be at end of tag.

return
ByteBuffer Contains all the frames written within the tag ready for writing to file
throws
IOException

        ByteArrayOutputStream bodyBuffer = new ByteArrayOutputStream();
        writeFramesToBufferStream(frameMap, bodyBuffer);
        writeFramesToBufferStream(encryptedFrameMap, bodyBuffer);
        return bodyBuffer;
    
private voidwriteFramesToBufferStream(java.util.Map map, java.io.ByteArrayOutputStream bodyBuffer)
Write frames in map to bodyBuffer

param
map
param
bodyBuffer
throws
IOException

        //Sort keys into Preferred Order
        TreeSet<String> sortedWriteOrder = new TreeSet<String>(getPreferredFrameOrderComparator());
        sortedWriteOrder.addAll(map.keySet());

        AbstractID3v2Frame frame;
        for (String id : sortedWriteOrder)
        {
            Object o = map.get(id);
            if (o instanceof AbstractID3v2Frame)
            {
                frame = (AbstractID3v2Frame) o;
                frame.setLoggingFilename(getLoggingFilename());
                frame.write(bodyBuffer);
            }
            else
            {
                List<AbstractID3v2Frame> multiFrames = (List<AbstractID3v2Frame>) o;
                for (AbstractID3v2Frame nextFrame : multiFrames)
                {
                    nextFrame.setLoggingFilename(getLoggingFilename());
                    nextFrame.write(bodyBuffer);
                }
            }
        }