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