package org.jaudiotagger.audio.asf.io; import org.jaudiotagger.audio.asf.data.GUID; import org.jaudiotagger.audio.asf.util.Utils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * This modifier manipulates an ASF header extension object. * * @author Christian Laireiter */ public class AsfExtHeaderModifier implements ChunkModifier { /** * List of modifiers which are to be applied to contained chunks. */ private final List<ChunkModifier> modifierList; /** * Creates an instance.<br> * * @param modifiers * modifiers to apply. */ public AsfExtHeaderModifier(final List<ChunkModifier> modifiers) { assert modifiers != null; this.modifierList = new ArrayList<ChunkModifier>(modifiers); } /** * Simply copies a chunk from <code>source</code> to * <code>destination</code>.<br> * The method assumes, that the GUID has already been read and will write * the provided one to the destination.<br> * The chunk length however will be read and used to determine the amount of * bytes to copy. * * @param guid * GUID of the current CHUNK. * @param source * source of an ASF chunk, which is to be located at the chunk * length field. * @param destination * the destination to copy the chunk to. * @throws IOException * on I/O errors. */ private void copyChunk(final GUID guid, final InputStream source, final OutputStream destination) throws IOException { final long chunkSize = Utils.readUINT64(source); destination.write(guid.getBytes()); Utils.writeUINT64(chunkSize, destination); Utils.copy(source, destination, chunkSize - 24); } /** * {@inheritDoc} */ public boolean isApplicable(final GUID guid) { return GUID.GUID_HEADER_EXTENSION.equals(guid); } /** * {@inheritDoc} */ public ModificationResult modify(final GUID guid, final InputStream source, final OutputStream destination) throws IOException { assert GUID.GUID_HEADER_EXTENSION.equals(guid); long difference = 0; final List<ChunkModifier> modders = new ArrayList<ChunkModifier>( this.modifierList); final Set<GUID> occuredGuids = new HashSet<GUID>(); occuredGuids.add(guid); final BigInteger chunkLen = Utils.readBig64(source); final GUID reserved1 = Utils.readGUID(source); final int reserved2 = Utils.readUINT16(source); final long dataSize = Utils.readUINT32(source); assert dataSize == 0 || dataSize >= 24; assert chunkLen.subtract(BigInteger.valueOf(46)).longValue() == dataSize; /* * Stream buffer for the chunk list */ final ByteArrayOutputStream bos = new ByteArrayOutputStream(); /* * Stream which counts read bytes. Dirty but quick way of implementing * this. */ final CountingInputStream cis = new CountingInputStream(source); while (cis.getReadCount() < dataSize) { // read GUID final GUID curr = Utils.readGUID(cis); boolean handled = false; for (int i = 0; i < modders.size() && !handled; i++) { if (modders.get(i).isApplicable(curr)) { final ModificationResult modRes = modders.get(i).modify( curr, cis, bos); difference += modRes.getByteDifference(); occuredGuids.addAll(modRes.getOccuredGUIDs()); modders.remove(i); handled = true; } } if (!handled) { occuredGuids.add(curr); copyChunk(curr, cis, bos); } } // Now apply the left modifiers. for (final ChunkModifier curr : modders) { // chunks, which were not in the source file, will be added to the // destination final ModificationResult result = curr.modify(null, null, bos); difference += result.getByteDifference(); occuredGuids.addAll(result.getOccuredGUIDs()); } destination.write(GUID.GUID_HEADER_EXTENSION.getBytes()); Utils.writeUINT64(chunkLen.add(BigInteger.valueOf(difference)) .longValue(), destination); destination.write(reserved1.getBytes()); Utils.writeUINT16(reserved2, destination); Utils.writeUINT32(dataSize + difference, destination); destination.write(bos.toByteArray()); return new ModificationResult(0, difference, occuredGuids); } }