| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| ChunkContainerReader |
|
| 0.0;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 | } |