CreateJavaOutputStreamAdaptor.cpp revision 2bb39d7a43fdb28ecdafd65ea4b89dc05c1ad7be
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                return 0;
82            }
83
84            if (n < 0) { // n == 0 should not be possible, see InputStream read() specifications.
85                fIsAtEnd = true;
86                break;  // eof
87            }
88
89            env->GetByteArrayRegion(fJavaByteArray, 0, n,
90                                    reinterpret_cast<jbyte*>(buffer));
91            if (env->ExceptionCheck()) {
92                env->ExceptionDescribe();
93                env->ExceptionClear();
94                SkDebugf("---- read:GetByteArrayRegion threw an exception\n");
95                return 0;
96            }
97
98            buffer = (void*)((char*)buffer + n);
99            bytesRead += n;
100            size -= n;
101            fBytesRead += n;
102        } while (size != 0);
103
104        return bytesRead;
105    }
106
107    size_t doSkip(size_t size) {
108        JNIEnv* env = fEnv;
109
110        jlong skipped = env->CallLongMethod(fJavaInputStream,
111                                            gInputStream_skipMethodID, (jlong)size);
112        if (env->ExceptionCheck()) {
113            env->ExceptionDescribe();
114            env->ExceptionClear();
115            SkDebugf("------- skip threw an exception\n");
116            return 0;
117        }
118        if (skipped < 0) {
119            skipped = 0;
120        }
121
122        return (size_t)skipped;
123    }
124
125    JNIEnv*     fEnv;
126    jobject     fJavaInputStream;   // the caller owns this object
127    jbyteArray  fJavaByteArray;     // the caller owns this object
128    jint        fCapacity;
129    size_t      fBytesRead;
130    bool        fIsAtEnd;
131};
132
133SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,
134                                       jbyteArray storage) {
135    return new JavaInputStreamAdaptor(env, stream, storage);
136}
137
138
139static SkMemoryStream* adaptor_to_mem_stream(SkStream* stream) {
140    SkASSERT(stream != NULL);
141    size_t bufferSize = 4096;
142    size_t streamLen = 0;
143    size_t len;
144    char* data = (char*)sk_malloc_throw(bufferSize);
145
146    while ((len = stream->read(data + streamLen,
147                               bufferSize - streamLen)) != 0) {
148        streamLen += len;
149        if (streamLen == bufferSize) {
150            bufferSize *= 2;
151            data = (char*)sk_realloc_throw(data, bufferSize);
152        }
153    }
154    data = (char*)sk_realloc_throw(data, streamLen);
155
156    SkMemoryStream* streamMem = new SkMemoryStream();
157    streamMem->setMemoryOwned(data, streamLen);
158    return streamMem;
159}
160
161SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream,
162                                        jbyteArray storage) {
163    SkAutoTUnref<SkStream> adaptor(CreateJavaInputStreamAdaptor(env, stream, storage));
164    if (NULL == adaptor.get()) {
165        return NULL;
166    }
167    return adaptor_to_mem_stream(adaptor.get());
168}
169
170///////////////////////////////////////////////////////////////////////////////
171
172static jmethodID    gOutputStream_writeMethodID;
173static jmethodID    gOutputStream_flushMethodID;
174
175class SkJavaOutputStream : public SkWStream {
176public:
177    SkJavaOutputStream(JNIEnv* env, jobject stream, jbyteArray storage)
178        : fEnv(env), fJavaOutputStream(stream), fJavaByteArray(storage) {
179        fCapacity = env->GetArrayLength(storage);
180    }
181
182    virtual bool write(const void* buffer, size_t size) {
183        JNIEnv* env = fEnv;
184        jbyteArray storage = fJavaByteArray;
185
186        while (size > 0) {
187            jint requested = 0;
188            if (size > static_cast<size_t>(fCapacity)) {
189                requested = fCapacity;
190            } else {
191                // This is safe because requested is clamped to (jint)
192                // fCapacity.
193                requested = static_cast<jint>(size);
194            }
195
196            env->SetByteArrayRegion(storage, 0, requested,
197                                    reinterpret_cast<const jbyte*>(buffer));
198            if (env->ExceptionCheck()) {
199                env->ExceptionDescribe();
200                env->ExceptionClear();
201                SkDebugf("--- write:SetByteArrayElements threw an exception\n");
202                return false;
203            }
204
205            fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_writeMethodID,
206                                 storage, 0, requested);
207            if (env->ExceptionCheck()) {
208                env->ExceptionDescribe();
209                env->ExceptionClear();
210                SkDebugf("------- write threw an exception\n");
211                return false;
212            }
213
214            buffer = (void*)((char*)buffer + requested);
215            size -= requested;
216        }
217        return true;
218    }
219
220    virtual void flush() {
221        fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_flushMethodID);
222    }
223
224private:
225    JNIEnv*     fEnv;
226    jobject     fJavaOutputStream;  // the caller owns this object
227    jbyteArray  fJavaByteArray;     // the caller owns this object
228    jint        fCapacity;
229};
230
231SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream,
232                                         jbyteArray storage) {
233    static bool gInited;
234
235    if (!gInited) {
236
237        gInited = true;
238    }
239
240    return new SkJavaOutputStream(env, stream, storage);
241}
242
243static jclass findClassCheck(JNIEnv* env, const char classname[]) {
244    jclass clazz = env->FindClass(classname);
245    SkASSERT(!env->ExceptionCheck());
246    return clazz;
247}
248
249static jmethodID getMethodIDCheck(JNIEnv* env, jclass clazz,
250                                  const char methodname[], const char type[]) {
251    jmethodID id = env->GetMethodID(clazz, methodname, type);
252    SkASSERT(!env->ExceptionCheck());
253    return id;
254}
255
256int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env) {
257    jclass inputStream_Clazz = findClassCheck(env, "java/io/InputStream");
258    gInputStream_readMethodID = getMethodIDCheck(env, inputStream_Clazz, "read", "([BII)I");
259    gInputStream_skipMethodID = getMethodIDCheck(env, inputStream_Clazz, "skip", "(J)J");
260
261    jclass outputStream_Clazz = findClassCheck(env, "java/io/OutputStream");
262    gOutputStream_writeMethodID = getMethodIDCheck(env, outputStream_Clazz, "write", "([BII)V");
263    gOutputStream_flushMethodID = getMethodIDCheck(env, outputStream_Clazz, "flush", "()V");
264
265    return 0;
266}
267