aboutsummaryrefslogtreecommitdiff
path: root/library/src/main/java/com/bumptech/glide/util/ExceptionCatchingInputStream.java
blob: 344a2894f94fa4c0492b32c7666e25cc422628bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package com.bumptech.glide.util;

import com.bumptech.glide.load.resource.bitmap.RecyclableBufferedInputStream;

import java.io.IOException;
import java.io.InputStream;
import java.util.Queue;

/**
 * An {@link java.io.InputStream} that catches {@link java.io.IOException}s during read and skip calls and stores them
 * so they can later be handled or thrown. This class is a workaround for a framework issue where exceptions during
 * reads while decoding bitmaps in {@link android.graphics.BitmapFactory} can return partially decoded bitmaps.
 *
 * See https://github.com/bumptech/glide/issues/126.
 */
public class ExceptionCatchingInputStream extends InputStream {

    private static final Queue<ExceptionCatchingInputStream> QUEUE = Util.createQueue(0);

    private RecyclableBufferedInputStream wrapped;
    private IOException exception;

    public static ExceptionCatchingInputStream obtain(RecyclableBufferedInputStream toWrap) {
        ExceptionCatchingInputStream result;
        synchronized (QUEUE) {
            result = QUEUE.poll();
        }
        if (result == null) {
            result = new ExceptionCatchingInputStream();
        }
        result.setInputStream(toWrap);
        return result;
    }

    // Exposed for testing.
    static void clearQueue() {
        while (!QUEUE.isEmpty()) {
            QUEUE.remove();
        }
    }

    ExceptionCatchingInputStream() {
        // Do nothing.
    }

    void setInputStream(RecyclableBufferedInputStream toWrap) {
        wrapped = toWrap;
    }

    @Override
    public int available() throws IOException {
        return wrapped.available();
    }

    @Override
    public void close() throws IOException {
        wrapped.close();
    }

    @Override
    public void mark(int readlimit) {
        wrapped.mark(readlimit);
    }

    @Override
    public boolean markSupported() {
        return wrapped.markSupported();
    }

    @Override
    public int read(byte[] buffer) throws IOException {
        int read;
        try {
            read = wrapped.read(buffer);
        } catch (IOException e) {
            exception = e;
            read = -1;
        }
        return read;
    }

    @Override
    public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
        int read;
        try {
            read = wrapped.read(buffer, byteOffset, byteCount);
        } catch (IOException e) {
            exception = e;
            read = -1;
        }
        return read;
    }

    @Override
    public synchronized void reset() throws IOException {
        wrapped.reset();
    }

    @Override
    public long skip(long byteCount) throws IOException {
        long skipped;
        try {
            skipped = wrapped.skip(byteCount);
        } catch (IOException e) {
            exception = e;
            skipped = 0;
        }
        return skipped;
    }

    @Override
    public int read() throws IOException {
        int result;
        try {
            result = wrapped.read();
        } catch (IOException e) {
            exception = e;
            result = -1;
        }
        return result;
    }

    public void fixMarkLimit() {
        wrapped.fixMarkLimit();
    }

    public IOException getException() {
        return exception;
    }

    public void release() {
        exception = null;
        wrapped = null;
        synchronized (QUEUE) {
            QUEUE.offer(this);
        }
    }
}