Coverage Report - org.jaudiotagger.audio.asf.io.ChunkContainerReader
 
Classes in this File Line Coverage Branch Coverage Complexity
ChunkContainerReader
89%
42/47
67%
20/30
0
 
 1  
 package org.jaudiotagger.audio.asf.io;
 2  
 
 3  
 import org.jaudiotagger.audio.asf.data.Chunk;
 4  
 import org.jaudiotagger.audio.asf.data.ChunkContainer;
 5  
 import org.jaudiotagger.audio.asf.data.GUID;
 6  
 import org.jaudiotagger.audio.asf.util.Utils;
 7  
 
 8  
 import java.io.IOException;
 9  
 import java.io.InputStream;
 10  
 import java.math.BigInteger;
 11  
 import java.util.HashMap;
 12  
 import java.util.HashSet;
 13  
 import java.util.List;
 14  
 import java.util.logging.Logger;
 15  
 
 16  
 
 17  
 /**
 18  
  * This class represents a reader implementation, which is able to read ASF objects (chunks) which
 19  
  * store other objects (chunks) within them.<br>
 20  
  * 
 21  
  * @author Christian Laireiter
 22  
  * @param <ChunkType> The {@link ChunkContainer} instance, the implementation will create.
 23  
  */
 24  122
 abstract class ChunkContainerReader<ChunkType extends ChunkContainer> implements ChunkReader
 25  
 {
 26  
 
 27  
     /**
 28  
      * Within this range, a {@link ChunkReader} should be aware if it fails.
 29  
      */
 30  
     public final static int READ_LIMIT = 8192;
 31  
 
 32  
     /**
 33  
      * If <code>true</code> each chunk type will only be read once.<br>
 34  
      */
 35  
     protected final boolean eachChunkOnce;
 36  
 
 37  
     /**
 38  
      * If <code>true</code> due to a {@linkplain #register(Class) registered} 
 39  
      * chunk reader, all {@link InputStream} objects passed to {@link #read(GUID, InputStream, long)} must
 40  
      * support mark/reset.
 41  
      */
 42  294
     protected boolean hasFailingReaders = false;
 43  
 
 44  
     /**
 45  
      * Logger
 46  
      */
 47  294
     protected Logger logger = Logger.getLogger("org.jaudiotabgger.audio"); //$NON-NLS-1$
 48  
 
 49  
     /**
 50  
      * Registers GUIDs to their reader classes.<br>
 51  
      */
 52  294
     protected final HashMap<GUID, ChunkReader> readerMap = new HashMap<GUID, ChunkReader>();
 53  
 
 54  
     /**
 55  
      * Creates a reader instance, which only utilizes the given list of chunk
 56  
      * readers.<br>
 57  
      * 
 58  
      * @param toRegister
 59  
      *            List of {@link ChunkReader} class instances, which are to be
 60  
      *            utilized by the instance.
 61  
      * @param readChunkOnce
 62  
      *            if <code>true</code>, each chunk type (identified by chunk
 63  
      *            GUID) will handled only once, if a reader is available, other
 64  
      *            chunks will be discarded.
 65  
      */
 66  
     protected ChunkContainerReader(List<Class<? extends ChunkReader>> toRegister, boolean readChunkOnce)
 67  294
     {
 68  294
         this.eachChunkOnce = readChunkOnce;
 69  294
         for (Class<? extends ChunkReader> curr : toRegister)
 70  
         {
 71  882
             register(curr);
 72  
         }
 73  294
     }
 74  
 
 75  
     /**
 76  
      * {@inheritDoc}
 77  
      */
 78  
     public boolean canFail()
 79  
     {
 80  80
         return false;
 81  
     }
 82  
 
 83  
     /**
 84  
      * Checks for the constraints of this class.
 85  
      * @param stream stream to test.
 86  
      * @throws IllegalArgumentException If stream does not meet the requirements.
 87  
      */
 88  
     protected void checkStream(InputStream stream) throws IllegalArgumentException
 89  
     {
 90  160
         if (this.hasFailingReaders && !stream.markSupported())
 91  
         {
 92  0
             throw new IllegalArgumentException("Stream has to support mark/reset.");
 93  
         }
 94  160
     }
 95  
 
 96  
     /**
 97  
      * This method is called by {@link #read(GUID, InputStream, long)} in order to create the
 98  
      * resulting object. Implementations of this class should now return a new instance
 99  
      * of their implementation specific result <b>AND</b> all data should be read, until the list
 100  
      * of chunks starts. (The {@link ChunkContainer#getChunckEnd()} must return a sane result, too)<br>
 101  
      * 
 102  
      * @param streamPosition position of the stream, the chunk starts. 
 103  
      * @param chunkLength the length of the chunk (from chunk header)
 104  
      * @param stream to read the implementation specific information.
 105  
      * @return instance of the implementations result.
 106  
      * @throws IOException On I/O Errors and Invalid data.
 107  
      */
 108  
     abstract protected ChunkType createContainer(long streamPosition, BigInteger chunkLength, InputStream stream) throws IOException;
 109  
 
 110  
     /**
 111  
      * Gets a configured {@linkplain ChunkReader reader} instance for ASF objects (chunks) with the specified <code>guid</code>.
 112  
      * @param guid GUID which identifies the chunk to be read.
 113  
      * @return an appropriate reader implementation, <code>null</code> if not {@linkplain #register(Class) registered}.
 114  
      */
 115  
     protected ChunkReader getReader(GUID guid)
 116  
     {
 117  650
         return readerMap.get(guid);
 118  
     }
 119  
 
 120  
     /**
 121  
      * Tests whether {@link #getReader(GUID)} won't return <code>null</code>.<br>
 122  
      * @param guid GUID which identifies the chunk to be read.
 123  
      * @return <code>true</code> if a reader is available.
 124  
      */
 125  
     protected boolean isReaderAvailable(GUID guid)
 126  
     {
 127  1140
         return this.readerMap.containsKey(guid);
 128  
     }
 129  
 
 130  
     /**
 131  
      * This Method implements the reading of a chunk container.<br>
 132  
      * 
 133  
      * @param guid GUID of the currently read container.
 134  
      * @param stream
 135  
      *            Stream which contains the chunk container.
 136  
      * @param chunkStart
 137  
      *            The start of the chunk container from stream start.<br>
 138  
      *            For direct file streams one can assume <code>0</code> here.
 139  
      * @return <code>null</code> if no valid data found, else a Wrapper
 140  
      *         containing all supported data.
 141  
      * @throws IOException
 142  
      *             Read errors.
 143  
      * @throws IllegalArgumentException If one used {@link ChunkReader} could 
 144  
      *  {@linkplain ChunkReader#canFail() fail} and the stream source
 145  
      *   doesn't support mark/reset.
 146  
      */
 147  
     public ChunkType read(final GUID guid, final InputStream stream, final long chunkStart) throws IOException, IllegalArgumentException
 148  
     {
 149  160
         checkStream(stream);
 150  160
         final CountingInputStream cis = new CountingInputStream(stream);
 151  160
         if (!getApplyingId().equals(guid))
 152  
         {
 153  0
             throw new IllegalArgumentException("provided GUID is not supported by this reader.");
 154  
         }
 155  
         // For Know the file pointer pointed to an ASF header chunk.
 156  160
         final BigInteger chunkLen = Utils.readBig64(cis);
 157  
         /*
 158  
          * now read implementation specific information until the chunk 
 159  
          * collection starts and create the resulting object. 
 160  
          */
 161  160
         ChunkType result = createContainer(chunkStart, chunkLen, cis);
 162  
         // 16 bytes have already been for providing the GUID
 163  160
         long currentPosition = chunkStart + cis.getReadCount() + 16;
 164  
 
 165  160
         HashSet<GUID> alreadyRead = new HashSet<GUID>();
 166  
         /*
 167  
          * Now reading header of chuncks.
 168  
          */
 169  1067
         while (currentPosition < result.getChunckEnd())
 170  
         {
 171  907
             GUID currentGUID = Utils.readGUID(cis);
 172  907
             boolean skip = this.eachChunkOnce && (!isReaderAvailable(currentGUID) || !alreadyRead.add(currentGUID));
 173  
             Chunk chunk;
 174  
             /*
 175  
              * If one reader tells it could fail (new method), then check
 176  
              * the input stream for mark/reset. And use it if failed.
 177  
              */
 178  907
             if (!skip && isReaderAvailable(currentGUID))
 179  
             {
 180  325
                 final ChunkReader reader = getReader(currentGUID);
 181  325
                 if (reader.canFail())
 182  
                 {
 183  55
                     cis.mark(READ_LIMIT);
 184  
                 }
 185  325
                 chunk = getReader(currentGUID).read(currentGUID, cis, currentPosition);
 186  325
             }
 187  
             else
 188  
             {
 189  582
                 chunk = new ChunkHeaderReader().read(currentGUID, cis, currentPosition);
 190  
             }
 191  907
             if (chunk != null)
 192  
             {
 193  907
                 if (!skip)
 194  
                 {
 195  325
                     result.addChunk(chunk);
 196  
                 }
 197  907
                 currentPosition = chunk.getChunckEnd();
 198  
                 // Always take into account, that 16 bytes have been read prior to calling this method
 199  907
                 assert cis.getReadCount() + chunkStart + 16 == currentPosition;
 200  
             }
 201  
             else
 202  
             {
 203  
                 /*
 204  
                  * Reader failed
 205  
                  */
 206  0
                 cis.reset();
 207  
             }
 208  907
         }
 209  
 
 210  160
         return result;
 211  
     }
 212  
 
 213  
     /**
 214  
      * Registers the given reader.<br>
 215  
      * @param <T> The actual reader implementation.
 216  
      * 
 217  
      * @param toRegister chunk reader which is to be registered.
 218  
      */
 219  
     private <T extends ChunkReader> void register(Class<T> toRegister)
 220  
     {
 221  
         try
 222  
         {
 223  882
             T reader = toRegister.newInstance();
 224  882
             this.readerMap.put(reader.getApplyingId(), reader);
 225  0
         } catch (Exception e)
 226  
         {
 227  0
             logger.severe(e.getMessage());
 228  882
         }
 229  
 
 230  882
     }
 231  
 
 232  
 }