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