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