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