Merge "Rewrite scoped arrays with template and introduced nullable one." am: 3204e23c06
am: 2446e5dfef

Change-Id: Ie9b3a6882975af0ef844c245a9ed984071c335a6
diff --git a/header_only_include/nativehelper/scoped_primitive_array.h b/header_only_include/nativehelper/scoped_primitive_array.h
index d6840c2..17f18a3 100644
--- a/header_only_include/nativehelper/scoped_primitive_array.h
+++ b/header_only_include/nativehelper/scoped_primitive_array.h
@@ -17,6 +17,10 @@
 #ifndef SCOPED_PRIMITIVE_ARRAY_H_
 #define SCOPED_PRIMITIVE_ARRAY_H_
 
+#include <type_traits>
+
+#include <sys/types.h>  // For ssize_t
+
 #include "jni.h"
 #include "nativehelper_utils.h"
 
@@ -26,122 +30,184 @@
 #define POINTER_TYPE(T) T*  /* NOLINT */
 #endif
 
-#ifdef REFERENCE_TYPE
-#error REFERENCE_TYPE is defined.
-#else
-#define REFERENCE_TYPE(T) T&  /* NOLINT */
-#endif
+template<typename JType> struct ScopedPrimitiveArrayTraits {};
 
-// ScopedBooleanArrayRO, ScopedByteArrayRO, ScopedCharArrayRO, ScopedDoubleArrayRO,
-// ScopedFloatArrayRO, ScopedIntArrayRO, ScopedLongArrayRO, and ScopedShortArrayRO provide
-// convenient read-only access to Java arrays from JNI code. This is cheaper than read-write
-// access and should be used by default.
-#define INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(PRIMITIVE_TYPE, NAME) \
-    class Scoped ## NAME ## ArrayRO { \
-    public: \
-        explicit Scoped ## NAME ## ArrayRO(JNIEnv* env) \
-        : mEnv(env), mJavaArray(NULL), mRawArray(NULL), mSize(0) {} \
-        Scoped ## NAME ## ArrayRO(JNIEnv* env, PRIMITIVE_TYPE ## Array javaArray) \
-        : mEnv(env) { \
-            if (javaArray == NULL) { \
-                mJavaArray = NULL; \
-                mSize = 0; \
-                mRawArray = NULL; \
-                jniThrowNullPointerException(mEnv, NULL); \
-            } else { \
-                reset(javaArray); \
-            } \
-        } \
-        ~Scoped ## NAME ## ArrayRO() { \
-            if (mRawArray != NULL && mRawArray != mBuffer) { \
-                mEnv->Release ## NAME ## ArrayElements(mJavaArray, mRawArray, JNI_ABORT); \
-            } \
-        } \
-        void reset(PRIMITIVE_TYPE ## Array javaArray) { \
-            mJavaArray = javaArray; \
-            mSize = mEnv->GetArrayLength(mJavaArray); \
-            if (mSize <= buffer_size) { \
-                mEnv->Get ## NAME ## ArrayRegion(mJavaArray, 0, mSize, mBuffer); \
-                mRawArray = mBuffer; \
-            } else { \
-                mRawArray = mEnv->Get ## NAME ## ArrayElements(mJavaArray, NULL); \
-            } \
-        } \
-        const PRIMITIVE_TYPE* get() const { return mRawArray; } \
-        PRIMITIVE_TYPE ## Array getJavaArray() const { return mJavaArray; } \
-        const PRIMITIVE_TYPE& operator[](size_t n) const { return mRawArray[n]; } \
-        size_t size() const { return mSize; } \
-    private: \
-        static const jsize buffer_size = 1024; \
-        JNIEnv* const mEnv; \
-        PRIMITIVE_TYPE ## Array mJavaArray; \
-        POINTER_TYPE(PRIMITIVE_TYPE) mRawArray; \
-        jsize mSize; \
-        PRIMITIVE_TYPE mBuffer[buffer_size]; \
-        DISALLOW_COPY_AND_ASSIGN(Scoped ## NAME ## ArrayRO); \
-    }
+#define ARRAY_TRAITS(ARRAY_TYPE, JTYPE, NAME)                                            \
+template<> struct ScopedPrimitiveArrayTraits<JTYPE> {                                    \
+public:                                                                                  \
+    static inline void getArrayRegion(JNIEnv* env, ARRAY_TYPE array, size_t start,       \
+                                      size_t len, POINTER_TYPE(JTYPE) out) {             \
+        env->Get ## NAME ## ArrayRegion(array, start, len, out);                         \
+    }                                                                                    \
+                                                                                         \
+    static inline POINTER_TYPE(JTYPE) getArrayElements(JNIEnv* env, ARRAY_TYPE array) {  \
+        return env->Get ## NAME ## ArrayElements(array, nullptr);                        \
+    }                                                                                    \
+                                                                                         \
+    static inline void releaseArrayElements(JNIEnv* env, ARRAY_TYPE array,               \
+                                            POINTER_TYPE(JTYPE) buffer, jint mode) {     \
+        env->Release ## NAME ## ArrayElements(array, buffer, mode);                      \
+    }                                                                                    \
+    static inline size_t getArrayLength(JNIEnv* env, ARRAY_TYPE array) {                 \
+        return env->GetArrayLength(array);                                               \
+    }                                                                                    \
+    static inline void fatalError(JNIEnv* env, const char*msg) {                         \
+        env->FatalError(msg);                                                            \
+    }                                                                                    \
+    using ArrayType = ARRAY_TYPE;                                                        \
+};                                                                                       \
 
-INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jboolean, Boolean);
-INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jbyte, Byte);
-INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jchar, Char);
-INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jdouble, Double);
-INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jfloat, Float);
-INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jint, Int);
-INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jlong, Long);
-INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jshort, Short);
+ARRAY_TRAITS(jbooleanArray, jboolean, Boolean)
+ARRAY_TRAITS(jbyteArray, jbyte, Byte)
+ARRAY_TRAITS(jcharArray, jchar, Char)
+ARRAY_TRAITS(jdoubleArray, jdouble, Double)
+ARRAY_TRAITS(jfloatArray, jfloat, Float)
+ARRAY_TRAITS(jintArray, jint, Int)
+ARRAY_TRAITS(jlongArray, jlong, Long)
+ARRAY_TRAITS(jshortArray, jshort, Short)
 
-#undef INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO
-
-// ScopedBooleanArrayRW, ScopedByteArrayRW, ScopedCharArrayRW, ScopedDoubleArrayRW,
-// ScopedFloatArrayRW, ScopedIntArrayRW, ScopedLongArrayRW, and ScopedShortArrayRW provide
-// convenient read-write access to Java arrays from JNI code. These are more expensive,
-// since they entail a copy back onto the Java heap, and should only be used when necessary.
-#define INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(PRIMITIVE_TYPE, NAME) \
-    class Scoped ## NAME ## ArrayRW { \
-    public: \
-        explicit Scoped ## NAME ## ArrayRW(JNIEnv* env) \
-        : mEnv(env), mJavaArray(NULL), mRawArray(NULL) {} \
-        Scoped ## NAME ## ArrayRW(JNIEnv* env, PRIMITIVE_TYPE ## Array javaArray) \
-        : mEnv(env), mJavaArray(javaArray), mRawArray(NULL) { \
-            if (mJavaArray == NULL) { \
-                jniThrowNullPointerException(mEnv, NULL); \
-            } else { \
-                mRawArray = mEnv->Get ## NAME ## ArrayElements(mJavaArray, NULL); \
-            } \
-        } \
-        ~Scoped ## NAME ## ArrayRW() { \
-            if (mRawArray) { \
-                mEnv->Release ## NAME ## ArrayElements(mJavaArray, mRawArray, 0); \
-            } \
-        } \
-        void reset(PRIMITIVE_TYPE ## Array javaArray) { \
-            mJavaArray = javaArray; \
-            mRawArray = mEnv->Get ## NAME ## ArrayElements(mJavaArray, NULL); \
-        } \
-        const PRIMITIVE_TYPE* get() const { return mRawArray; } \
-        PRIMITIVE_TYPE ## Array getJavaArray() const { return mJavaArray; } \
-        const PRIMITIVE_TYPE& operator[](size_t n) const { return mRawArray[n]; } \
-        POINTER_TYPE(PRIMITIVE_TYPE) get() { return mRawArray; }  \
-        REFERENCE_TYPE(PRIMITIVE_TYPE) operator[](size_t n) { return mRawArray[n]; } \
-        size_t size() const { return mEnv->GetArrayLength(mJavaArray); } \
-    private: \
-        JNIEnv* const mEnv; \
-        PRIMITIVE_TYPE ## Array mJavaArray; \
-        POINTER_TYPE(PRIMITIVE_TYPE) mRawArray; \
-        DISALLOW_COPY_AND_ASSIGN(Scoped ## NAME ## ArrayRW); \
-    }
-
-INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jboolean, Boolean);
-INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jbyte, Byte);
-INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jchar, Char);
-INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jdouble, Double);
-INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jfloat, Float);
-INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jint, Int);
-INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jlong, Long);
-INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jshort, Short);
-
-#undef INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW
+#undef ARRAY_TRAITS
 #undef POINTER_TYPE
-#undef REFERENCE_TYPE
+
+template<typename JType, bool kNullable>
+class ScopedArrayRO {
+public:
+    using Traits = ScopedPrimitiveArrayTraits<JType>;
+    using ArrayType = typename Traits::ArrayType;
+    using const_iterator = const JType*;
+
+    // Provides read-only access to Java array from JNI code.
+    // env must not be nullptr.
+    // If kNullable is false, this aborts if javaArray is nullptr.
+    ScopedArrayRO(JNIEnv* env, ArrayType javaArray) : mEnv(env), mJavaArray(javaArray) {
+        if (mJavaArray == nullptr) {
+            mSize = -1;
+            mRawArray = nullptr;
+            if (!kNullable) {
+                Traits::fatalError(mEnv, "javaArray is null");
+            }
+        } else {
+            mSize = Traits::getArrayLength(mEnv, mJavaArray);
+            if (mSize <= BUFFER_SIZE) {
+                Traits::getArrayRegion(mEnv, mJavaArray, 0, mSize, mBuffer);
+                mRawArray = mBuffer;
+            } else {
+                mRawArray = Traits::getArrayElements(mEnv, mJavaArray);
+            }
+        }
+    }
+
+    ~ScopedArrayRO() {
+        if (mRawArray != nullptr && mRawArray != mBuffer) {
+            Traits::releaseArrayElements(mEnv, mJavaArray, mRawArray, JNI_ABORT);
+        }
+    }
+
+    const JType* get() const { return mRawArray; }
+    ArrayType getJavaArray() const { return mJavaArray; }
+    const JType& operator[](size_t n) const { return mRawArray[n]; }
+    const_iterator begin() const { return get(); }
+    const_iterator end() const {
+        return (kNullable && mRawArray == nullptr) ? get() : get() + mSize;
+    }
+
+    using SizeT = typename std::conditional<kNullable, ssize_t, size_t>::type;
+    // In case of nonnull array, the return type is size_t.
+    // In case of nullable array, the return type is ssize_t. Then, will return -1 if this is
+    // constructed with null array.
+    SizeT size() const { return mSize; }
+
+private:
+    // 1024 since there is stack frame size limitation (4096 bytes).
+    constexpr static jsize BUFFER_SIZE = 1024 / sizeof(JType);
+
+    JNIEnv* const mEnv;
+    ArrayType mJavaArray;
+    JType* mRawArray;
+    SizeT mSize;
+
+    // Speed-up JNI array access for small arrays, see I703d7346de732199be1feadbead021c6647a554a
+    // for more details.
+    JType mBuffer[BUFFER_SIZE];
+
+    DISALLOW_COPY_AND_ASSIGN(ScopedArrayRO);
+};
+
+// Scoped***ArrayRO provide convenient read-only access to Java array from JNI code.
+// This is cheaper than read-write access and should be used by default.
+// These abort if nullptr is passed.
+using ScopedBooleanArrayRO = ScopedArrayRO<jboolean, false>;
+using ScopedByteArrayRO = ScopedArrayRO<jbyte, false>;
+using ScopedCharArrayRO = ScopedArrayRO<jchar, false>;
+using ScopedDoubleArrayRO = ScopedArrayRO<jdouble, false>;
+using ScopedFloatArrayRO = ScopedArrayRO<jfloat, false>;
+using ScopedIntArrayRO = ScopedArrayRO<jint, false>;
+using ScopedLongArrayRO = ScopedArrayRO<jlong, false>;
+using ScopedShortArrayRO = ScopedArrayRO<jshort, false>;
+
+// ScopedNullable***ArrayRO also provide convenient read-only access to Java array from JNI code.
+// These accept nullptr. In that case, get() returns nullptr and size() returns -1.
+using ScopedNullableBooleanArrayRO = ScopedArrayRO<jboolean, true>;
+using ScopedNullableByteArrayRO = ScopedArrayRO<jbyte, true>;
+using ScopedNullableCharArrayRO = ScopedArrayRO<jchar, true>;
+using ScopedNullableDoubleArrayRO = ScopedArrayRO<jdouble, true>;
+using ScopedNullableFloatArrayRO = ScopedArrayRO<jfloat, true>;
+using ScopedNullableIntArrayRO = ScopedArrayRO<jint, true>;
+using ScopedNullableLongArrayRO = ScopedArrayRO<jlong, true>;
+using ScopedNullableShortArrayRO = ScopedArrayRO<jshort, true>;
+
+template<typename JType>
+class ScopedArrayRW {
+public:
+    using Traits = ScopedPrimitiveArrayTraits<JType>;
+    using ArrayType = typename Traits::ArrayType;
+    using const_iterator = const JType*;
+    using iterator = JType*;
+
+    ScopedArrayRW(JNIEnv* env, ArrayType javaArray) : mEnv(env), mJavaArray(javaArray) {
+        if (mJavaArray == nullptr) {
+            Traits::fatalError(mEnv, "javaArray is null");
+        } else {
+            mSize = Traits::getArrayLength(mEnv, mJavaArray);
+            mRawArray = Traits::getArrayElements(mEnv, mJavaArray);
+        }
+    }
+    ~ScopedArrayRW() {
+        if (mRawArray != nullptr) {
+            Traits::releaseArrayElements(mEnv, mJavaArray, mRawArray, 0);
+        }
+    }
+
+    const JType* get() const { return mRawArray; }
+    ArrayType getJavaArray() const { return mJavaArray; }
+    const JType& operator[](size_t n) const { return mRawArray[n]; }
+    const_iterator cbegin() const { return get(); }
+    const_iterator cend() const { return get() + mSize; }
+    JType* get() { return mRawArray; }
+    JType& operator[](size_t n) { return mRawArray[n]; }
+    iterator begin() { return get(); }
+    iterator end() { return get() + mSize; }
+    size_t size() const { return mSize; }
+
+private:
+    JNIEnv* const mEnv;
+    ArrayType mJavaArray;
+    JType* mRawArray;
+    jsize mSize;
+    DISALLOW_COPY_AND_ASSIGN(ScopedArrayRW);
+};
+
+// Scoped***ArrayRW provide convenient read-write access to Java arrays from JNI code.
+// These are more expensive, since they entail a copy back onto the Java heap, and should only be
+// used when necessary.
+// These abort if nullptr is passed.
+using ScopedBooleanArrayRW = ScopedArrayRW<jboolean>;
+using ScopedByteArrayRW = ScopedArrayRW<jbyte>;
+using ScopedCharArrayRW = ScopedArrayRW<jchar>;
+using ScopedDoubleArrayRW = ScopedArrayRW<jdouble>;
+using ScopedFloatArrayRW = ScopedArrayRW<jfloat>;
+using ScopedIntArrayRW = ScopedArrayRW<jint>;
+using ScopedLongArrayRW = ScopedArrayRW<jlong>;
+using ScopedShortArrayRW = ScopedArrayRW<jshort>;
 
 #endif  // SCOPED_PRIMITIVE_ARRAY_H_
diff --git a/tests/Android.bp b/tests/Android.bp
index e6cbf5c..d30b4d2 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -4,7 +4,10 @@
     name: "JniInvocation_test",
     test_suites: ["device-tests"],
     host_supported: true,
-    srcs: ["JniInvocation_test.cpp"],
+    srcs: [
+        "JniInvocation_test.cpp",
+        "scoped_primitive_array_test.cpp"
+    ],
     cflags: ["-Wall", "-Werror"],
     shared_libs: ["libnativehelper"],
 }
diff --git a/tests/scoped_primitive_array_test.cpp b/tests/scoped_primitive_array_test.cpp
new file mode 100644
index 0000000..cba258c
--- /dev/null
+++ b/tests/scoped_primitive_array_test.cpp
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <nativehelper/scoped_primitive_array.h>
+
+#include <gtest/gtest.h>
+
+struct TestType { char dummy[1]; };
+using jTestTypeArray = void*;
+
+const jTestTypeArray LARGE_ARRAY = reinterpret_cast<jTestTypeArray>(0x1);
+const jTestTypeArray SMALL_ARRAY = reinterpret_cast<jTestTypeArray>(0x2);
+
+constexpr size_t LARGE_ARRAY_SIZE = 8192;
+constexpr size_t SMALL_ARRAY_SIZE = 32;
+
+struct TestContext {
+    TestType* dummyPtr;
+
+    int getArrayElementsCallCount = 0;
+    int releaseArrayElementsCallCount = 0;
+    bool aborted = false;
+    bool elementsUpdated = false;
+
+    void resetCallCount() {
+        getArrayElementsCallCount = 0;
+        releaseArrayElementsCallCount = 0;
+        aborted = false;
+        elementsUpdated = false;
+    }
+
+    bool memoryUpdated() const {
+        return releaseArrayElementsCallCount > 0 && elementsUpdated;
+    }
+};
+
+// Mock implementation of the ScopedPrimitiveArrayTraits.
+// JNIEnv is abused for passing TestContext.
+template<> struct ScopedPrimitiveArrayTraits<TestType> {
+public:
+    static inline void getArrayRegion(JNIEnv*, jTestTypeArray, size_t, size_t, TestType*) {}
+
+    static inline TestType* getArrayElements(JNIEnv* env, jTestTypeArray) {
+        TestContext* ctx = reinterpret_cast<TestContext*>(env);
+        ctx->getArrayElementsCallCount++;
+        return ctx->dummyPtr;
+    }
+
+    static inline void releaseArrayElements(JNIEnv* env, jTestTypeArray, TestType* buffer,
+                                            jint mode) {
+        TestContext* ctx = reinterpret_cast<TestContext*>(env);
+        if (ctx->dummyPtr == buffer) {
+            ctx->releaseArrayElementsCallCount++;
+        }
+        ctx->elementsUpdated = (mode != JNI_ABORT);
+    }
+
+    static inline size_t getArrayLength(JNIEnv*, jTestTypeArray array) {
+        return array == LARGE_ARRAY ? LARGE_ARRAY_SIZE : SMALL_ARRAY_SIZE;
+    }
+
+    static inline void fatalError(JNIEnv* env, const char*) {
+        reinterpret_cast<TestContext*>(env)->aborted = true;
+    }
+
+    using ArrayType = jTestTypeArray;
+};
+
+TEST(ScopedPrimitiveArrayTest, testNonNullArray) {
+    std::unique_ptr<TestType[]> dummyTestType = std::make_unique<TestType[]>(LARGE_ARRAY_SIZE);
+
+    TestContext context;
+    context.dummyPtr = dummyTestType.get();
+
+    JNIEnv* env = reinterpret_cast<JNIEnv*>(&context);
+    {
+        context.resetCallCount();
+        {
+            ScopedArrayRO<TestType, false /* non null */> array(env, SMALL_ARRAY);
+            EXPECT_NE(nullptr, array.get());
+            EXPECT_EQ(SMALL_ARRAY, array.getJavaArray());
+            EXPECT_NE(nullptr, array.begin());
+            EXPECT_NE(nullptr, array.end());
+            EXPECT_EQ(array.end(), array.begin() + SMALL_ARRAY_SIZE);
+            EXPECT_EQ(SMALL_ARRAY_SIZE, array.size());
+        }
+        EXPECT_EQ(context.getArrayElementsCallCount, context.releaseArrayElementsCallCount);
+        EXPECT_FALSE(context.memoryUpdated());
+        EXPECT_FALSE(context.aborted);
+    }
+    {
+        context.resetCallCount();
+        {
+            ScopedArrayRO<TestType, false /* non null */> array(env, LARGE_ARRAY);
+
+            EXPECT_EQ(context.dummyPtr, array.get());
+            EXPECT_EQ(LARGE_ARRAY, array.getJavaArray());
+            EXPECT_EQ(context.dummyPtr, array.begin());
+            EXPECT_EQ(context.dummyPtr + LARGE_ARRAY_SIZE, array.end());
+            EXPECT_EQ(LARGE_ARRAY_SIZE, array.size());
+        }
+        EXPECT_EQ(context.getArrayElementsCallCount, context.releaseArrayElementsCallCount);
+        EXPECT_FALSE(context.memoryUpdated());
+        EXPECT_FALSE(context.aborted);
+    }
+    {
+        context.resetCallCount();
+        {
+            ScopedArrayRO<TestType, false /* non null */> array(env, nullptr);
+            EXPECT_TRUE(context.aborted);
+        }
+    }
+}
+
+TEST(ScopedPrimitiveArrayTest, testNullableArray) {
+    std::unique_ptr<TestType[]> dummyTestType = std::make_unique<TestType[]>(LARGE_ARRAY_SIZE);
+
+    TestContext context;
+    context.dummyPtr = dummyTestType.get();
+
+    JNIEnv* env = reinterpret_cast<JNIEnv*>(&context);
+    {
+        context.resetCallCount();
+        {
+            ScopedArrayRO<TestType, true /* nullable */> array(env, SMALL_ARRAY);
+            EXPECT_NE(nullptr, array.get());
+            EXPECT_EQ(SMALL_ARRAY, array.getJavaArray());
+            EXPECT_NE(nullptr, array.begin());
+            EXPECT_NE(nullptr, array.end());
+            EXPECT_EQ(array.end(), array.begin() + SMALL_ARRAY_SIZE);
+            EXPECT_EQ(SMALL_ARRAY_SIZE, (size_t) array.size());
+        }
+        EXPECT_EQ(context.getArrayElementsCallCount, context.releaseArrayElementsCallCount);
+        EXPECT_FALSE(context.memoryUpdated());
+        EXPECT_FALSE(context.aborted);
+    }
+    {
+        context.resetCallCount();
+        {
+            ScopedArrayRO<TestType, true /* nullable */> array(env, LARGE_ARRAY);
+            EXPECT_EQ(context.dummyPtr, array.get());
+            EXPECT_EQ(LARGE_ARRAY, array.getJavaArray());
+            EXPECT_EQ(context.dummyPtr, array.begin());
+            EXPECT_EQ(context.dummyPtr + LARGE_ARRAY_SIZE, array.end());
+            EXPECT_EQ(LARGE_ARRAY_SIZE, (size_t) array.size());
+        }
+        EXPECT_EQ(context.getArrayElementsCallCount, context.releaseArrayElementsCallCount);
+        EXPECT_FALSE(context.memoryUpdated());
+        EXPECT_FALSE(context.aborted);
+    }
+    {
+        context.resetCallCount();
+        {
+            ScopedArrayRO<TestType, true /* nullable*/> array(env, nullptr);
+            EXPECT_EQ(nullptr, array.get());
+            EXPECT_EQ(nullptr, array.getJavaArray());
+            EXPECT_EQ(nullptr, array.begin());
+            EXPECT_EQ(nullptr, array.end());
+            EXPECT_EQ(-1, array.size());
+        }
+        EXPECT_EQ(context.getArrayElementsCallCount, context.releaseArrayElementsCallCount);
+        EXPECT_FALSE(context.memoryUpdated());
+        EXPECT_FALSE(context.aborted);
+    }
+}
+
+TEST(ScopedPrimitiveArrayTest, testArrayRW) {
+    std::unique_ptr<TestType[]> dummyTestType = std::make_unique<TestType[]>(LARGE_ARRAY_SIZE);
+
+    TestContext context;
+    context.dummyPtr = dummyTestType.get();
+
+    JNIEnv* env = reinterpret_cast<JNIEnv*>(&context);
+    {
+        context.resetCallCount();
+        {
+            ScopedArrayRW<TestType> array(env, SMALL_ARRAY);
+            EXPECT_NE(nullptr, array.get());
+            EXPECT_EQ(SMALL_ARRAY, array.getJavaArray());
+            EXPECT_NE(nullptr, array.begin());
+            EXPECT_NE(nullptr, array.end());
+            EXPECT_EQ(array.end(), array.begin() + SMALL_ARRAY_SIZE);
+            EXPECT_EQ(SMALL_ARRAY_SIZE, (size_t) array.size());
+        }
+        EXPECT_EQ(context.getArrayElementsCallCount, context.releaseArrayElementsCallCount);
+        EXPECT_TRUE(context.memoryUpdated());
+        EXPECT_FALSE(context.aborted);
+    }
+    {
+        context.resetCallCount();
+        {
+            ScopedArrayRW<TestType> array(env, LARGE_ARRAY);
+            EXPECT_EQ(context.dummyPtr, array.get());
+            EXPECT_EQ(LARGE_ARRAY, array.getJavaArray());
+            EXPECT_EQ(context.dummyPtr, array.begin());
+            EXPECT_EQ(context.dummyPtr + LARGE_ARRAY_SIZE, array.end());
+            EXPECT_EQ(LARGE_ARRAY_SIZE, (size_t) array.size());
+        }
+        EXPECT_EQ(context.getArrayElementsCallCount, context.releaseArrayElementsCallCount);
+        EXPECT_TRUE(context.memoryUpdated());
+        EXPECT_FALSE(context.aborted);
+    }
+    {
+        context.resetCallCount();
+        {
+            ScopedArrayRW<TestType> array(env, nullptr);
+            EXPECT_TRUE(context.aborted);
+        }
+    }
+}