CreateJavaOutputStreamAdaptor.cpp revision c7797525084ba0ea441e394aa0a2ba35d6ff3320
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#include <androidfw/Asset.h>
9
10#define RETURN_NULL_IF_NULL(value) \
11    do { if (!(value)) { SkASSERT(0); return NULL; } } while (false)
12
13#define RETURN_ZERO_IF_NULL(value) \
14    do { if (!(value)) { SkASSERT(0); return 0; } } while (false)
15
16static jmethodID    gInputStream_resetMethodID;
17static jmethodID    gInputStream_markMethodID;
18static jmethodID    gInputStream_markSupportedMethodID;
19static jmethodID    gInputStream_readMethodID;
20static jmethodID    gInputStream_skipMethodID;
21
22class RewindableJavaStream;
23
24/**
25 *  Non-rewindable wrapper for a Java InputStream.
26 */
27class JavaInputStreamAdaptor : public SkStream {
28public:
29    JavaInputStreamAdaptor(JNIEnv* env, jobject js, jbyteArray ar)
30        : fEnv(env), fJavaInputStream(js), fJavaByteArray(ar) {
31        SkASSERT(ar);
32        fCapacity = env->GetArrayLength(ar);
33        SkASSERT(fCapacity > 0);
34        fBytesRead = 0;
35        fIsAtEnd = false;
36    }
37
38    virtual size_t read(void* buffer, size_t size) {
39        JNIEnv* env = fEnv;
40        if (NULL == buffer) {
41            if (0 == size) {
42                return 0;
43            } else {
44                /*  InputStream.skip(n) can return <=0 but still not be at EOF
45                    If we see that value, we need to call read(), which will
46                    block if waiting for more data, or return -1 at EOF
47                 */
48                size_t amountSkipped = 0;
49                do {
50                    size_t amount = this->doSkip(size - amountSkipped);
51                    if (0 == amount) {
52                        char tmp;
53                        amount = this->doRead(&tmp, 1);
54                        if (0 == amount) {
55                            // if read returned 0, we're at EOF
56                            fIsAtEnd = true;
57                            break;
58                        }
59                    }
60                    amountSkipped += amount;
61                } while (amountSkipped < size);
62                return amountSkipped;
63            }
64        }
65        return this->doRead(buffer, size);
66    }
67
68    virtual bool isAtEnd() const {
69        return fIsAtEnd;
70    }
71
72private:
73    // Does not override rewind, since a JavaInputStreamAdaptor's interface
74    // does not support rewinding. RewindableJavaStream, which is a friend,
75    // will be able to call this method to rewind.
76    bool doRewind() {
77        JNIEnv* env = fEnv;
78
79        fBytesRead = 0;
80        fIsAtEnd = false;
81
82        env->CallVoidMethod(fJavaInputStream, gInputStream_resetMethodID);
83        if (env->ExceptionCheck()) {
84            env->ExceptionDescribe();
85            env->ExceptionClear();
86            SkDebugf("------- reset threw an exception\n");
87            return false;
88        }
89        return true;
90    }
91
92    size_t doRead(void* buffer, size_t size) {
93        JNIEnv* env = fEnv;
94        size_t bytesRead = 0;
95        // read the bytes
96        do {
97            size_t requested = size;
98            if (requested > fCapacity)
99                requested = fCapacity;
100
101            jint n = env->CallIntMethod(fJavaInputStream,
102                                        gInputStream_readMethodID, fJavaByteArray, 0, requested);
103            if (env->ExceptionCheck()) {
104                env->ExceptionDescribe();
105                env->ExceptionClear();
106                SkDebugf("---- read threw an exception\n");
107                return 0;
108            }
109
110            if (n < 0) { // n == 0 should not be possible, see InputStream read() specifications.
111                fIsAtEnd = true;
112                break;  // eof
113            }
114
115            env->GetByteArrayRegion(fJavaByteArray, 0, n,
116                                    reinterpret_cast<jbyte*>(buffer));
117            if (env->ExceptionCheck()) {
118                env->ExceptionDescribe();
119                env->ExceptionClear();
120                SkDebugf("---- read:GetByteArrayRegion threw an exception\n");
121                return 0;
122            }
123
124            buffer = (void*)((char*)buffer + n);
125            bytesRead += n;
126            size -= n;
127            fBytesRead += n;
128        } while (size != 0);
129
130        return bytesRead;
131    }
132
133    size_t doSkip(size_t size) {
134        JNIEnv* env = fEnv;
135
136        jlong skipped = env->CallLongMethod(fJavaInputStream,
137                                            gInputStream_skipMethodID, (jlong)size);
138        if (env->ExceptionCheck()) {
139            env->ExceptionDescribe();
140            env->ExceptionClear();
141            SkDebugf("------- skip threw an exception\n");
142            return 0;
143        }
144        if (skipped < 0) {
145            skipped = 0;
146        }
147
148        return (size_t)skipped;
149    }
150
151    JNIEnv*     fEnv;
152    jobject     fJavaInputStream;   // the caller owns this object
153    jbyteArray  fJavaByteArray;     // the caller owns this object
154    size_t      fCapacity;
155    size_t      fBytesRead;
156    bool        fIsAtEnd;
157
158    // Allows access to doRewind and fBytesRead.
159    friend class RewindableJavaStream;
160};
161
162SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,
163                                       jbyteArray storage) {
164    static bool gInited;
165
166    if (!gInited) {
167        jclass inputStream_Clazz = env->FindClass("java/io/InputStream");
168        RETURN_NULL_IF_NULL(inputStream_Clazz);
169
170        gInputStream_resetMethodID      = env->GetMethodID(inputStream_Clazz,
171                                                           "reset", "()V");
172        gInputStream_markMethodID       = env->GetMethodID(inputStream_Clazz,
173                                                           "mark", "(I)V");
174        gInputStream_markSupportedMethodID = env->GetMethodID(inputStream_Clazz,
175                                                              "markSupported", "()Z");
176        gInputStream_readMethodID       = env->GetMethodID(inputStream_Clazz,
177                                                           "read", "([BII)I");
178        gInputStream_skipMethodID       = env->GetMethodID(inputStream_Clazz,
179                                                           "skip", "(J)J");
180
181        RETURN_NULL_IF_NULL(gInputStream_resetMethodID);
182        RETURN_NULL_IF_NULL(gInputStream_markMethodID);
183        RETURN_NULL_IF_NULL(gInputStream_markSupportedMethodID);
184        RETURN_NULL_IF_NULL(gInputStream_readMethodID);
185        RETURN_NULL_IF_NULL(gInputStream_skipMethodID);
186
187        gInited = true;
188    }
189
190    return new JavaInputStreamAdaptor(env, stream, storage);
191}
192
193
194static SkMemoryStream* adaptor_to_mem_stream(SkStream* adaptor) {
195    SkASSERT(adaptor != NULL);
196    SkDynamicMemoryWStream wStream;
197    const int bufferSize = 256 * 1024; // 256 KB, same as ViewStateSerializer.
198    uint8_t buffer[bufferSize];
199    do {
200        size_t bytesRead = adaptor->read(buffer, bufferSize);
201        wStream.write(buffer, bytesRead);
202    } while (!adaptor->isAtEnd());
203    SkAutoTUnref<SkData> data(wStream.copyToData());
204    return new SkMemoryStream(data.get());
205}
206
207SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream,
208                                        jbyteArray storage) {
209    SkAutoTUnref<SkStream> adaptor(CreateJavaInputStreamAdaptor(env, stream, storage));
210    if (NULL == adaptor.get()) {
211        return NULL;
212    }
213    return adaptor_to_mem_stream(adaptor.get());
214}
215
216/**
217 *  Wrapper for a Java InputStream which is rewindable and
218 *  has a length.
219 */
220class RewindableJavaStream : public SkStreamRewindable {
221public:
222    // RewindableJavaStream takes ownership of adaptor.
223    RewindableJavaStream(JavaInputStreamAdaptor* adaptor, size_t length)
224        : fAdaptor(adaptor)
225        , fLength(length) {
226        SkASSERT(fAdaptor != NULL);
227    }
228
229    virtual ~RewindableJavaStream() {
230        fAdaptor->unref();
231    }
232
233    virtual bool rewind() {
234        return fAdaptor->doRewind();
235    }
236
237    virtual size_t read(void* buffer, size_t size) {
238        return fAdaptor->read(buffer, size);
239    }
240
241    virtual bool isAtEnd() const {
242        return fAdaptor->isAtEnd();
243    }
244
245    virtual size_t getLength() const {
246        return fLength;
247    }
248
249    virtual bool hasLength() const {
250        return true;
251    }
252
253    virtual SkStreamRewindable* duplicate() const {
254        // Duplicating this stream requires rewinding and
255        // reading, which modify this Stream (and could
256        // fail, leaving this one invalid).
257        SkASSERT(false);
258        return NULL;
259    }
260
261private:
262    JavaInputStreamAdaptor* fAdaptor;
263    const size_t            fLength;
264};
265
266/**
267 *  If jstream is a ByteArrayInputStream, return its remaining length. Otherwise
268 *  return 0.
269 */
270static size_t get_length_from_byte_array_stream(JNIEnv* env, jobject jstream) {
271    static jclass byteArrayInputStream_Clazz;
272    static jfieldID countField;
273    static jfieldID posField;
274
275    byteArrayInputStream_Clazz = env->FindClass("java/io/ByteArrayInputStream");
276    RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz);
277
278    countField = env->GetFieldID(byteArrayInputStream_Clazz, "count", "I");
279    RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz);
280    posField = env->GetFieldID(byteArrayInputStream_Clazz, "pos", "I");
281    RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz);
282
283    if (env->IsInstanceOf(jstream, byteArrayInputStream_Clazz)) {
284        // Return the remaining length, to keep the same behavior of using the rest of the
285        // stream.
286        return env->GetIntField(jstream, countField) - env->GetIntField(jstream, posField);
287    }
288    return 0;
289}
290
291/**
292 *  If jstream is a class that has a length, return it. Otherwise
293 *  return 0.
294 *  Only checks for a set of subclasses.
295 */
296static size_t get_length_if_supported(JNIEnv* env, jobject jstream) {
297    size_t len = get_length_from_byte_array_stream(env, jstream);
298    if (len > 0) {
299        return len;
300    }
301    return 0;
302}
303
304SkStreamRewindable* GetRewindableStream(JNIEnv* env, jobject stream,
305                                        jbyteArray storage) {
306    SkAutoTUnref<SkStream> adaptor(CreateJavaInputStreamAdaptor(env, stream, storage));
307    if (NULL == adaptor.get()) {
308        return NULL;
309    }
310
311    const size_t length = get_length_if_supported(env, stream);
312    if (length > 0 && env->CallBooleanMethod(stream, gInputStream_markSupportedMethodID)) {
313        // Set the readLimit for mark to the end of the stream, so it can
314        // be rewound regardless of how much has been read.
315        env->CallVoidMethod(stream, gInputStream_markMethodID, length);
316        // RewindableJavaStream will unref adaptor when it is destroyed.
317        return new RewindableJavaStream(static_cast<JavaInputStreamAdaptor*>(adaptor.detach()),
318                                        length);
319    }
320
321    return adaptor_to_mem_stream(adaptor.get());
322}
323
324android::AssetStreamAdaptor* CheckForAssetStream(JNIEnv* env, jobject jstream) {
325    static jclass assetInputStream_Clazz;
326    static jmethodID getAssetIntMethodID;
327
328    assetInputStream_Clazz = env->FindClass("android/content/res/AssetManager$AssetInputStream");
329    RETURN_NULL_IF_NULL(assetInputStream_Clazz);
330
331    getAssetIntMethodID = env->GetMethodID(assetInputStream_Clazz, "getAssetInt", "()I");
332    RETURN_NULL_IF_NULL(getAssetIntMethodID);
333
334    if (!env->IsInstanceOf(jstream, assetInputStream_Clazz)) {
335        return NULL;
336    }
337
338    jint jasset = env->CallIntMethod(jstream, getAssetIntMethodID);
339    android::Asset* a = reinterpret_cast<android::Asset*>(jasset);
340    if (NULL == a) {
341        jniThrowNullPointerException(env, "NULL native asset");
342        return NULL;
343    }
344    return new android::AssetStreamAdaptor(a);
345}
346
347///////////////////////////////////////////////////////////////////////////////
348
349static jmethodID    gOutputStream_writeMethodID;
350static jmethodID    gOutputStream_flushMethodID;
351
352class SkJavaOutputStream : public SkWStream {
353public:
354    SkJavaOutputStream(JNIEnv* env, jobject stream, jbyteArray storage)
355        : fEnv(env), fJavaOutputStream(stream), fJavaByteArray(storage) {
356        fCapacity = env->GetArrayLength(storage);
357    }
358
359	virtual bool write(const void* buffer, size_t size) {
360        JNIEnv* env = fEnv;
361        jbyteArray storage = fJavaByteArray;
362
363        while (size > 0) {
364            size_t requested = size;
365            if (requested > fCapacity) {
366                requested = fCapacity;
367            }
368
369            env->SetByteArrayRegion(storage, 0, requested,
370                                    reinterpret_cast<const jbyte*>(buffer));
371            if (env->ExceptionCheck()) {
372                env->ExceptionDescribe();
373                env->ExceptionClear();
374                SkDebugf("--- write:SetByteArrayElements threw an exception\n");
375                return false;
376            }
377
378            fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_writeMethodID,
379                                 storage, 0, requested);
380            if (env->ExceptionCheck()) {
381                env->ExceptionDescribe();
382                env->ExceptionClear();
383                SkDebugf("------- write threw an exception\n");
384                return false;
385            }
386
387            buffer = (void*)((char*)buffer + requested);
388            size -= requested;
389        }
390        return true;
391    }
392
393    virtual void flush() {
394        fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_flushMethodID);
395    }
396
397private:
398    JNIEnv*     fEnv;
399    jobject     fJavaOutputStream;  // the caller owns this object
400    jbyteArray  fJavaByteArray;     // the caller owns this object
401    size_t      fCapacity;
402};
403
404SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream,
405                                         jbyteArray storage) {
406    static bool gInited;
407
408    if (!gInited) {
409        jclass outputStream_Clazz = env->FindClass("java/io/OutputStream");
410        RETURN_NULL_IF_NULL(outputStream_Clazz);
411
412        gOutputStream_writeMethodID = env->GetMethodID(outputStream_Clazz,
413                                                       "write", "([BII)V");
414        RETURN_NULL_IF_NULL(gOutputStream_writeMethodID);
415        gOutputStream_flushMethodID = env->GetMethodID(outputStream_Clazz,
416                                                       "flush", "()V");
417        RETURN_NULL_IF_NULL(gOutputStream_flushMethodID);
418
419        gInited = true;
420    }
421
422    return new SkJavaOutputStream(env, stream, storage);
423}
424