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