Coverage Report - org.jaudiotagger.audio.asf.io.AsfExtHeaderModifier
 
Classes in this File Line Coverage Branch Coverage Complexity
AsfExtHeaderModifier
90%
45/50
43%
13/30
0
 
 1  
 package org.jaudiotagger.audio.asf.io;
 2  
 
 3  
 import org.jaudiotagger.audio.asf.data.GUID;
 4  
 import org.jaudiotagger.audio.asf.util.Utils;
 5  
 
 6  
 import java.io.ByteArrayOutputStream;
 7  
 import java.io.IOException;
 8  
 import java.io.InputStream;
 9  
 import java.io.OutputStream;
 10  
 import java.math.BigInteger;
 11  
 import java.util.ArrayList;
 12  
 import java.util.HashSet;
 13  
 import java.util.List;
 14  
 import java.util.Set;
 15  
 
 16  
 /**
 17  
  * This modifier manipulates an ASF header extension object. 
 18  
  * 
 19  
  * @author Christian Laireiter
 20  
  */
 21  1
 public class AsfExtHeaderModifier implements ChunkModifier
 22  
 {
 23  
 
 24  
     /**
 25  
      * List of modifiers which are to be applied to contained chunks.
 26  
      */
 27  
     private final List<ChunkModifier> modifierList;
 28  
 
 29  
     /**
 30  
      * Creates an instance.<br>
 31  
      * 
 32  
      * @param modifiers modifiers to apply.
 33  
      */
 34  
     public AsfExtHeaderModifier(List<ChunkModifier> modifiers)
 35  4
     {
 36  4
         assert modifiers != null;
 37  4
         this.modifierList = new ArrayList<ChunkModifier>(modifiers);
 38  4
     }
 39  
 
 40  
     /**
 41  
      * Simply copies a chunk from <code>source</code> to <code>destination</code>.<br>
 42  
      * The method assumes, that the GUID has already been read and will write the provided one to
 43  
      * the destination.<br> 
 44  
      * The chunk length however will be read and used to determine the amount of bytes to copy.  
 45  
      * 
 46  
      * @param guid GUID of the current CHUNK.
 47  
      * @param source source of an ASF chunk, which is to be located at the chunk length field.
 48  
      * @param destination the destination to copy the chunk to.
 49  
      * @throws IOException on I/O errors.
 50  
      */
 51  
     private void copyChunk(GUID guid, InputStream source, OutputStream destination) throws IOException
 52  
     {
 53  20
         long chunkSize = Utils.readUINT64(source);
 54  20
         destination.write(guid.getBytes());
 55  20
         Utils.writeUINT64(chunkSize, destination);
 56  20
         Utils.copy(source, destination, chunkSize - 24);
 57  20
     }
 58  
 
 59  
     /**
 60  
      * {@inheritDoc}
 61  
      */
 62  
     public boolean isApplicable(GUID guid)
 63  
     {
 64  4
         return GUID.GUID_HEADER_EXTENSION.equals(guid);
 65  
     }
 66  
 
 67  
     /**
 68  
      * {@inheritDoc}
 69  
      */
 70  
     public ModificationResult modify(GUID guid, InputStream source, OutputStream destination) throws IOException
 71  
     {
 72  4
         assert GUID.GUID_HEADER_EXTENSION.equals(guid);
 73  
 
 74  4
         long difference = 0;
 75  4
         final List<ChunkModifier> modders = new ArrayList<ChunkModifier>(this.modifierList);
 76  4
         final Set<GUID> occuredGuids = new HashSet<GUID>();
 77  4
         occuredGuids.add(guid);
 78  
 
 79  4
         BigInteger chunkLen = Utils.readBig64(source);
 80  4
         final GUID reserved1 = Utils.readGUID(source);
 81  4
         final int reserved2 = Utils.readUINT16(source);
 82  4
         final long dataSize = Utils.readUINT32(source);
 83  
 
 84  4
         assert dataSize == 0 || dataSize >= 24;
 85  4
         assert chunkLen.subtract(BigInteger.valueOf(46)).longValue() == dataSize;
 86  
 
 87  
         /*
 88  
          * Stream buffer for the chunk list
 89  
          */
 90  4
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
 91  
         /*
 92  
          * Stream which counts read bytes. Dirty but quick way of implementing this.
 93  
          */
 94  4
         final CountingInputStream cis = new CountingInputStream(source);
 95  
 
 96  24
         while (cis.getReadCount() < dataSize)
 97  
         {
 98  
             // read GUID
 99  20
             GUID curr = Utils.readGUID(cis);
 100  20
             boolean handled = false;
 101  40
             for (int i = 0; i < modders.size() && !handled; i++)
 102  
             {
 103  20
                 if (modders.get(i).isApplicable(curr))
 104  
                 {
 105  0
                     final ModificationResult modRes = modders.get(i).modify(curr, cis, bos);
 106  0
                     difference += modRes.getByteDifference();
 107  0
                     occuredGuids.addAll(modRes.getOccuredGUIDs());
 108  0
                     modders.remove(i);
 109  0
                     handled = true;
 110  
                 }
 111  
             }
 112  20
             if (!handled)
 113  
             {
 114  20
                 occuredGuids.add(curr);
 115  20
                 copyChunk(curr, cis, bos);
 116  
             }
 117  20
         }
 118  
         // Now apply the left modifiers. 
 119  4
         for (ChunkModifier curr : modders)
 120  
         {
 121  
             // chunks, which were not in the source file, will be added to the destination
 122  4
             final ModificationResult result = curr.modify(null, null, bos);
 123  4
             difference += result.getByteDifference();
 124  4
             occuredGuids.addAll(result.getOccuredGUIDs());
 125  4
         }
 126  4
         destination.write(GUID.GUID_HEADER_EXTENSION.getBytes());
 127  4
         Utils.writeUINT64(chunkLen.add(BigInteger.valueOf(difference)).longValue(), destination);
 128  4
         destination.write(reserved1.getBytes());
 129  4
         Utils.writeUINT16(reserved2, destination);
 130  4
         Utils.writeUINT32(dataSize + difference, destination);
 131  4
         destination.write(bos.toByteArray());
 132  4
         return new ModificationResult(0, difference, occuredGuids);
 133  
     }
 134  
 
 135  
 }