1#include "CreateJavaOutputStreamAdaptor.h"
2#include "SkData.h"
3#include "SkRefCnt.h"
4#include "SkStream.h"
5#include "SkTypes.h"
6#include "Utils.h"
7
8#include <JNIHelp.h>
9#include <memory>
10
11static jmethodID    gInputStream_readMethodID;
12static jmethodID    gInputStream_skipMethodID;
13
14/**
15 *  Wrapper for a Java InputStream.
16 */
17class JavaInputStreamAdaptor : public SkStream {
18public:
19    JavaInputStreamAdaptor(JNIEnv* env, jobject js, jbyteArray ar)
20        : fEnv(env), fJavaInputStream(js), fJavaByteArray(ar) {
21        SkASSERT(ar);
22        fCapacity = env->GetArrayLength(ar);
23        SkASSERT(fCapacity > 0);
24        fBytesRead = 0;
25        fIsAtEnd = false;
26    }
27
28    virtual size_t read(void* buffer, size_t size) {
29        if (NULL == buffer) {
30            if (0 == size) {
31                return 0;
32            } else {
33                /*  InputStream.skip(n) can return <=0 but still not be at EOF
34                    If we see that value, we need to call read(), which will
35                    block if waiting for more data, or return -1 at EOF
36                 */
37                size_t amountSkipped = 0;
38                do {
39                    size_t amount = this->doSkip(size - amountSkipped);
40                    if (0 == amount) {
41                        char tmp;
42                        amount = this->doRead(&tmp, 1);
43                        if (0 == amount) {
44                            // if read returned 0, we're at EOF
45                            fIsAtEnd = true;
46                            break;
47                        }
48                    }
49                    amountSkipped += amount;
50                } while (amountSkipped < size);
51                return amountSkipped;
52            }
53        }
54        return this->doRead(buffer, size);
55    }
56
57    virtual bool isAtEnd() const {
58        return fIsAtEnd;
59    }
60
61private:
62    size_t doRead(void* buffer, size_t size) {
63        JNIEnv* env = fEnv;
64        size_t bytesRead = 0;
65        // read the bytes
66        do {
67            jint requested = 0;
68            if (size > static_cast<size_t>(fCapacity)) {
69                requested = fCapacity;
70            } else {
71                // This is safe because requested is clamped to (jint)
72                // fCapacity.
73                requested = static_cast<jint>(size);
74            }
75
76            jint n = env->CallIntMethod(fJavaInputStream,
77                                        gInputStream_readMethodID, fJavaByteArray, 0, requested);
78            if (env->ExceptionCheck()) {
79                env->ExceptionDescribe();
80                env->ExceptionClear();
81                SkDebugf("---- read threw an exception\n");
82                // Consider the stream to be at the end, since there was an error.
83                fIsAtEnd = true;
84                return 0;
85            }
86
87            if (n < 0) { // n == 0 should not be possible, see InputStream read() specifications.
88                fIsAtEnd = true;
89                break;  // eof
90            }
91
92            env->GetByteArrayRegion(fJavaByteArray, 0, n,
93                                    reinterpret_cast<jbyte*>(buffer));
94            if (env->ExceptionCheck()) {
95                env->ExceptionDescribe();
96                env->ExceptionClear();
97                SkDebugf("---- read:GetByteArrayRegion threw an exception\n");
98                // The error was not with the stream itself, but consider it to be at the
99                // end, since we do not have a way to recover.
100                fIsAtEnd = true;
101                return 0;
102            }
103
104            buffer = (void*)((char*)buffer + n);
105            bytesRead += n;
106            size -= n;
107            fBytesRead += n;
108        } while (size != 0);
109
110        return bytesRead;
111    }
112
113    size_t doSkip(size_t size) {
114        JNIEnv* env = fEnv;
115
116        jlong skipped = env->CallLongMethod(fJavaInputStream,
117                                            gInputStream_skipMethodID, (jlong)size);
118        if (env->ExceptionCheck()) {
119            env->ExceptionDescribe();
120            env->ExceptionClear();
121            SkDebugf("------- skip threw an exception\n");
122            return 0;
123        }
124        if (skipped < 0) {
125            skipped = 0;
126        }
127
128        return (size_t)skipped;
129    }
130
131    JNIEnv*     fEnv;
132    jobject     fJavaInputStream;   // the caller owns this object
133    jbyteArray  fJavaByteArray;     // the caller owns this object
134    jint        fCapacity;
135    size_t      fBytesRead;
136    bool        fIsAtEnd;
137};
138
139SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,
140                                       jbyteArray storage) {
141    return new JavaInputStreamAdaptor(env, stream, storage);
142}
143
144
145static SkMemoryStream* adaptor_to_mem_stream(SkStream* stream) {
146    SkASSERT(stream != NULL);
147    size_t bufferSize = 4096;
148    size_t streamLen = 0;
149    size_t len;
150    char* data = (char*)sk_malloc_throw(bufferSize);
151
152    while ((len = stream->read(data + streamLen,
153                               bufferSize - streamLen)) != 0) {
154        streamLen += len;
155        if (streamLen == bufferSize) {
156            bufferSize *= 2;
157            data = (char*)sk_realloc_throw(data, bufferSize);
158        }
159    }
160    data = (char*)sk_realloc_throw(data, streamLen);
161
162    SkMemoryStream* streamMem = new SkMemoryStream();
163    streamMem->setMemoryOwned(data, streamLen);
164    return streamMem;
165}
166
167SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream,
168                                        jbyteArray storage) {
169    std::unique_ptr<SkStream> adaptor(CreateJavaInputStreamAdaptor(env, stream, storage));
170    if (NULL == adaptor.get()) {
171        return NULL;
172    }
173    return adaptor_to_mem_stream(adaptor.get());
174}
175
176///////////////////////////////////////////////////////////////////////////////
177
178static jmethodID    gOutputStream_writeMethodID;
179static jmethodID    gOutputStream_flushMethodID;
180
181class SkJavaOutputStream : public SkWStream {
182public:
183    SkJavaOutputStream(JNIEnv* env, jobject stream, jbyteArray storage)
184        : fEnv(env), fJavaOutputStream(stream), fJavaByteArray(storage), fBytesWritten(0) {
185        fCapacity = env->GetArrayLength(storage);
186    }
187
188    virtual size_t bytesWritten() const {
189        return fBytesWritten;
190    }
191
192    virtual bool write(const void* buffer, size_t size) {
193        JNIEnv* env = fEnv;
194        jbyteArray storage = fJavaByteArray;
195
196        while (size > 0) {
197            jint requested = 0;
198            if (size > static_cast<size_t>(fCapacity)) {
199                requested = fCapacity;
200            } else {
201                // This is safe because requested is clamped to (jint)
202                // fCapacity.
203                requested = static_cast<jint>(size);
204            }
205
206            env->SetByteArrayRegion(storage, 0, requested,
207                                    reinterpret_cast<const jbyte*>(buffer));
208            if (env->ExceptionCheck()) {
209                env->ExceptionDescribe();
210                env->ExceptionClear();
211                SkDebugf("--- write:SetByteArrayElements threw an exception\n");
212                return false;
213            }
214
215            fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_writeMethodID,
216                                 storage, 0, requested);
217            if (env->ExceptionCheck()) {
218                env->ExceptionDescribe();
219                env->ExceptionClear();
220                SkDebugf("------- write threw an exception\n");
221                return false;
222            }
223
224            buffer = (void*)((char*)buffer + requested);
225            size -= requested;
226            fBytesWritten += requested;
227        }
228        return true;
229    }
230
231    virtual void flush() {
232        fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_flushMethodID);
233    }
234
235private:
236    JNIEnv*     fEnv;
237    jobject     fJavaOutputStream;  // the caller owns this object
238    jbyteArray  fJavaByteArray;     // the caller owns this object
239    jint        fCapacity;
240    size_t      fBytesWritten;
241};
242
243SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream,
244                                         jbyteArray storage) {
245    static bool gInited;
246
247    if (!gInited) {
248
249        gInited = true;
250    }
251
252    return new SkJavaOutputStream(env, stream, storage);
253}
254
255static jclass findClassCheck(JNIEnv* env, const char classname[]) {
256    jclass clazz = env->FindClass(classname);
257    SkASSERT(!env->ExceptionCheck());
258    return clazz;
259}
260
261static jmethodID getMethodIDCheck(JNIEnv* env, jclass clazz,
262                                  const char methodname[], const char type[]) {
263    jmethodID id = env->GetMethodID(clazz, methodname, type);
264    SkASSERT(!env->ExceptionCheck());
265    return id;
266}
267
268int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env) {
269    jclass inputStream_Clazz = findClassCheck(env, "java/io/InputStream");
270    gInputStream_readMethodID = getMethodIDCheck(env, inputStream_Clazz, "read", "([BII)I");
271    gInputStream_skipMethodID = getMethodIDCheck(env, inputStream_Clazz, "skip", "(J)J");
272
273    jclass outputStream_Clazz = findClassCheck(env, "java/io/OutputStream");
274    gOutputStream_writeMethodID = getMethodIDCheck(env, outputStream_Clazz, "write", "([BII)V");
275    gOutputStream_flushMethodID = getMethodIDCheck(env, outputStream_Clazz, "flush", "()V");
276
277    return 0;
278}
279