1 package com.bumptech.glide.util;
2 
3 import com.bumptech.glide.load.resource.bitmap.RecyclableBufferedInputStream;
4 
5 import java.io.IOException;
6 import java.io.InputStream;
7 import 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  */
16 public 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 
obtain(RecyclableBufferedInputStream toWrap)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.
clearQueue()36     static void clearQueue() {
37         while (!QUEUE.isEmpty()) {
38             QUEUE.remove();
39         }
40     }
41 
ExceptionCatchingInputStream()42     ExceptionCatchingInputStream() {
43         // Do nothing.
44     }
45 
setInputStream(RecyclableBufferedInputStream toWrap)46     void setInputStream(RecyclableBufferedInputStream toWrap) {
47         wrapped = toWrap;
48     }
49 
50     @Override
available()51     public int available() throws IOException {
52         return wrapped.available();
53     }
54 
55     @Override
close()56     public void close() throws IOException {
57         wrapped.close();
58     }
59 
60     @Override
mark(int readlimit)61     public void mark(int readlimit) {
62         wrapped.mark(readlimit);
63     }
64 
65     @Override
markSupported()66     public boolean markSupported() {
67         return wrapped.markSupported();
68     }
69 
70     @Override
read(byte[] buffer)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
read(byte[] buffer, int byteOffset, int byteCount)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
reset()95     public synchronized void reset() throws IOException {
96         wrapped.reset();
97     }
98 
99     @Override
skip(long byteCount)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
read()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 
fixMarkLimit()123     public void fixMarkLimit() {
124         wrapped.fixMarkLimit();
125     }
126 
getException()127     public IOException getException() {
128         return exception;
129     }
130 
release()131     public void release() {
132         exception = null;
133         wrapped = null;
134         synchronized (QUEUE) {
135             QUEUE.offer(this);
136         }
137     }
138 }
139