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