CreateJavaOutputStreamAdaptor.cpp revision ca32021b43f326af7d3f4ae041f8db297f98a518
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* WrapJavaInputStream(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
193static SkMemoryStream* adaptor_to_mem_stream(SkStream* adaptor) {
194    SkASSERT(adaptor != NULL);
195    SkDynamicMemoryWStream wStream;
196    const int bufferSize = 256 * 1024; // 256 KB, same as ViewStateSerializer.
197    uint8_t buffer[bufferSize];
198    do {
199        size_t bytesRead = adaptor->read(buffer, bufferSize);
200        wStream.write(buffer, bytesRead);
201    } while (!adaptor->isAtEnd());
202    SkAutoTUnref<SkData> data(wStream.copyToData());
203    return new SkMemoryStream(data.get());
204}
205
206SkMemoryStream* CopyJavaInputStream(JNIEnv* env, jobject stream,
207                                    jbyteArray storage) {
208    SkAutoTUnref<SkStream> adaptor(WrapJavaInputStream(env, stream, storage));
209    if (NULL == adaptor.get()) {
210        return NULL;
211    }
212    return adaptor_to_mem_stream(adaptor.get());
213}
214
215/**
216 *  Wrapper for a Java InputStream which is rewindable and
217 *  has a length.
218 */
219class RewindableJavaStream : public SkStreamRewindable {
220public:
221    // RewindableJavaStream takes ownership of adaptor.
222    RewindableJavaStream(JavaInputStreamAdaptor* adaptor, size_t length)
223        : fAdaptor(adaptor)
224        , fLength(length) {
225        SkASSERT(fAdaptor != NULL);
226    }
227
228    virtual ~RewindableJavaStream() {
229        fAdaptor->unref();
230    }
231
232    virtual bool rewind() {
233        return fAdaptor->doRewind();
234    }
235
236    virtual size_t read(void* buffer, size_t size) {
237        return fAdaptor->read(buffer, size);
238    }
239
240    virtual bool isAtEnd() const {
241        return fAdaptor->isAtEnd();
242    }
243
244    virtual size_t getLength() const {
245        return fLength;
246    }
247
248    virtual bool hasLength() const {
249        return true;
250    }
251
252    virtual SkStreamRewindable* duplicate() const {
253        // Duplicating this stream requires rewinding and
254        // reading, which modify this Stream (and could
255        // fail, leaving this one invalid).
256        SkASSERT(false);
257        return NULL;
258    }
259
260private:
261    JavaInputStreamAdaptor* fAdaptor;
262    const size_t            fLength;
263};
264
265/**
266 *  If jstream is a ByteArrayInputStream, return its remaining length. Otherwise
267 *  return 0.
268 */
269static size_t get_length_from_byte_array_stream(JNIEnv* env, jobject jstream) {
270    static jclass byteArrayInputStream_Clazz;
271    static jfieldID countField;
272    static jfieldID posField;
273
274    byteArrayInputStream_Clazz = env->FindClass("java/io/ByteArrayInputStream");
275    RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz);
276
277    countField = env->GetFieldID(byteArrayInputStream_Clazz, "count", "I");
278    RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz);
279    posField = env->GetFieldID(byteArrayInputStream_Clazz, "pos", "I");
280    RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz);
281
282    if (env->IsInstanceOf(jstream, byteArrayInputStream_Clazz)) {
283        // Return the remaining length, to keep the same behavior of using the rest of the
284        // stream.
285        return env->GetIntField(jstream, countField) - env->GetIntField(jstream, posField);
286    }
287    return 0;
288}
289
290/**
291 *  If jstream is a class that has a length, return it. Otherwise
292 *  return 0.
293 *  Only checks for a set of subclasses.
294 */
295static size_t get_length_if_supported(JNIEnv* env, jobject jstream) {
296    size_t len = get_length_from_byte_array_stream(env, jstream);
297    if (len > 0) {
298        return len;
299    }
300    return 0;
301}
302
303SkStreamRewindable* GetRewindableStream(JNIEnv* env, jobject stream,
304                                        jbyteArray storage) {
305    SkAutoTUnref<SkStream> adaptor(WrapJavaInputStream(env, stream, storage));
306    if (NULL == adaptor.get()) {
307        return NULL;
308    }
309
310    const size_t length = get_length_if_supported(env, stream);
311    if (length > 0 && env->CallBooleanMethod(stream, gInputStream_markSupportedMethodID)) {
312        // Set the readLimit for mark to the end of the stream, so it can
313        // be rewound regardless of how much has been read.
314        env->CallVoidMethod(stream, gInputStream_markMethodID, length);
315        // RewindableJavaStream will unref adaptor when it is destroyed.
316        return new RewindableJavaStream(static_cast<JavaInputStreamAdaptor*>(adaptor.detach()),
317                                        length);
318    }
319
320    return adaptor_to_mem_stream(adaptor.get());
321}
322
323android::AssetStreamAdaptor* CheckForAssetStream(JNIEnv* env, jobject jstream) {
324    static jclass assetInputStream_Clazz;
325    static jmethodID getAssetIntMethodID;
326
327    assetInputStream_Clazz = env->FindClass("android/content/res/AssetManager$AssetInputStream");
328    RETURN_NULL_IF_NULL(assetInputStream_Clazz);
329
330    getAssetIntMethodID = env->GetMethodID(assetInputStream_Clazz, "getAssetInt", "()I");
331    RETURN_NULL_IF_NULL(getAssetIntMethodID);
332
333    if (!env->IsInstanceOf(jstream, assetInputStream_Clazz)) {
334        return NULL;
335    }
336
337    jint jasset = env->CallIntMethod(jstream, getAssetIntMethodID);
338    android::Asset* a = reinterpret_cast<android::Asset*>(jasset);
339    if (NULL == a) {
340        jniThrowNullPointerException(env, "NULL native asset");
341        return NULL;
342    }
343    return new android::AssetStreamAdaptor(a);
344}
345
346///////////////////////////////////////////////////////////////////////////////
347
348static jmethodID    gOutputStream_writeMethodID;
349static jmethodID    gOutputStream_flushMethodID;
350
351class SkJavaOutputStream : public SkWStream {
352public:
353    SkJavaOutputStream(JNIEnv* env, jobject stream, jbyteArray storage)
354        : fEnv(env), fJavaOutputStream(stream), fJavaByteArray(storage) {
355        fCapacity = env->GetArrayLength(storage);
356    }
357
358	virtual bool write(const void* buffer, size_t size) {
359        JNIEnv* env = fEnv;
360        jbyteArray storage = fJavaByteArray;
361
362        while (size > 0) {
363            size_t requested = size;
364            if (requested > fCapacity) {
365                requested = fCapacity;
366            }
367
368            env->SetByteArrayRegion(storage, 0, requested,
369                                    reinterpret_cast<const jbyte*>(buffer));
370            if (env->ExceptionCheck()) {
371                env->ExceptionDescribe();
372                env->ExceptionClear();
373                SkDebugf("--- write:SetByteArrayElements threw an exception\n");
374                return false;
375            }
376
377            fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_writeMethodID,
378                                 storage, 0, requested);
379            if (env->ExceptionCheck()) {
380                env->ExceptionDescribe();
381                env->ExceptionClear();
382                SkDebugf("------- write threw an exception\n");
383                return false;
384            }
385
386            buffer = (void*)((char*)buffer + requested);
387            size -= requested;
388        }
389        return true;
390    }
391
392    virtual void flush() {
393        fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_flushMethodID);
394    }
395
396private:
397    JNIEnv*     fEnv;
398    jobject     fJavaOutputStream;  // the caller owns this object
399    jbyteArray  fJavaByteArray;     // the caller owns this object
400    size_t      fCapacity;
401};
402
403SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream,
404                                         jbyteArray storage) {
405    static bool gInited;
406
407    if (!gInited) {
408        jclass outputStream_Clazz = env->FindClass("java/io/OutputStream");
409        RETURN_NULL_IF_NULL(outputStream_Clazz);
410
411        gOutputStream_writeMethodID = env->GetMethodID(outputStream_Clazz,
412                                                       "write", "([BII)V");
413        RETURN_NULL_IF_NULL(gOutputStream_writeMethodID);
414        gOutputStream_flushMethodID = env->GetMethodID(outputStream_Clazz,
415                                                       "flush", "()V");
416        RETURN_NULL_IF_NULL(gOutputStream_flushMethodID);
417
418        gInited = true;
419    }
420
421    return new SkJavaOutputStream(env, stream, storage);
422}
423