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