1package com.bumptech.glide.util;
2
3import com.bumptech.glide.load.resource.bitmap.RecyclableBufferedInputStream;
4
5import java.io.IOException;
6import java.io.InputStream;
7import java.util.Queue;
8
9/**
10 * An {@link java.io.InputStream} that catches {@link java.io.IOException}s during read and skip calls and stores them
11 * so they can later be handled or thrown. This class is a workaround for a framework issue where exceptions during
12 * reads while decoding bitmaps in {@link android.graphics.BitmapFactory} can return partially decoded bitmaps.
13 *
14 * See https://github.com/bumptech/glide/issues/126.
15 */
16public class ExceptionCatchingInputStream extends InputStream {
17
18    private static final Queue<ExceptionCatchingInputStream> QUEUE = Util.createQueue(0);
19
20    private RecyclableBufferedInputStream wrapped;
21    private IOException exception;
22
23    public static ExceptionCatchingInputStream obtain(RecyclableBufferedInputStream toWrap) {
24        ExceptionCatchingInputStream result;
25        synchronized (QUEUE) {
26            result = QUEUE.poll();
27        }
28        if (result == null) {
29            result = new ExceptionCatchingInputStream();
30        }
31        result.setInputStream(toWrap);
32        return result;
33    }
34
35    // Exposed for testing.
36    static void clearQueue() {
37        while (!QUEUE.isEmpty()) {
38            QUEUE.remove();
39        }
40    }
41
42    ExceptionCatchingInputStream() {
43        // Do nothing.
44    }
45
46    void setInputStream(RecyclableBufferedInputStream toWrap) {
47        wrapped = toWrap;
48    }
49
50    @Override
51    public int available() throws IOException {
52        return wrapped.available();
53    }
54
55    @Override
56    public void close() throws IOException {
57        wrapped.close();
58    }
59
60    @Override
61    public void mark(int readlimit) {
62        wrapped.mark(readlimit);
63    }
64
65    @Override
66    public boolean markSupported() {
67        return wrapped.markSupported();
68    }
69
70    @Override
71    public int read(byte[] buffer) throws IOException {
72        int read;
73        try {
74            read = wrapped.read(buffer);
75        } catch (IOException e) {
76            exception = e;
77            read = -1;
78        }
79        return read;
80    }
81
82    @Override
83    public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
84        int read;
85        try {
86            read = wrapped.read(buffer, byteOffset, byteCount);
87        } catch (IOException e) {
88            exception = e;
89            read = -1;
90        }
91        return read;
92    }
93
94    @Override
95    public synchronized void reset() throws IOException {
96        wrapped.reset();
97    }
98
99    @Override
100    public long skip(long byteCount) throws IOException {
101        long skipped;
102        try {
103            skipped = wrapped.skip(byteCount);
104        } catch (IOException e) {
105            exception = e;
106            skipped = 0;
107        }
108        return skipped;
109    }
110
111    @Override
112    public int read() throws IOException {
113        int result;
114        try {
115            result = wrapped.read();
116        } catch (IOException e) {
117            exception = e;
118            result = -1;
119        }
120        return result;
121    }
122
123    public void fixMarkLimit() {
124        wrapped.fixMarkLimit();
125    }
126
127    public IOException getException() {
128        return exception;
129    }
130
131    public void release() {
132        exception = null;
133        wrapped = null;
134        synchronized (QUEUE) {
135            QUEUE.offer(this);
136        }
137    }
138}
139