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