chromium/third_party/jni_zero/test/java/src/org/jni_zero/SampleForTests.java

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.jni_zero;

import android.graphics.Rect;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * This class serves as a reference test for the bindings generator, and as example documentation
 * for how to use the jni generator.
 *
 * <p>The C++ counter-part is sample_for_tests.cc.
 *
 * <p>jni_zero/BUILD.gn has a sample_jni_apk target that uses these files to create a test app that
 * exercises some basic JNI when the app is opened.
 *
 * <p>All comments are informational only, and are ignored by the jni generator.
 *
 * <p>This JNINamespace annotation indicates that all native methods should be generated inside this
 * namespace, including the native class that this object binds to.
 */
@JNINamespace("jni_zero::tests")
class SampleForTests {
    // Classes can store their C++ pointer counterpart as an int that is normally initialized by
    // calling out a SampleForTestsJni.get().init() function. Replace "CPPClass" with your
    // particular class name!
    long mNativeCPPObject;

    // You can define methods and attributes on the java class just like any other.
    // Methods without the @CalledByNative annotation won't be exposed to JNI.
    public SampleForTests() {}

    public void startExample() {
        // Calls C++ Init(...) method and holds a pointer to the C++ class.
        mNativeCPPObject = SampleForTestsJni.get().init(this, "myParam", new byte[0], null, null);
    }

    public void doStuff() {
        // This will call CPPClass::Method() using nativePtr as a pointer to the object. This must
        // be done to:
        // * avoid leaks.
        // * using finalizers are not allowed to destroy the cpp class.
        SampleForTestsJni.get().method(mNativeCPPObject, this, new String[] {"test"});
    }

    // Just a comment to ensure we aren't reading comments:
    // private native void thisShouldNotExist();

    public void finishExample() {
        // We're done, so let's destroy nativePtr object.
        SampleForTestsJni.get().destroy(mNativeCPPObject, this, new byte[0]);
    }

    // ---------------------------------------------------------------------------------------------
    // The following methods demonstrate exporting Java methods for invocation from C++ code.
    // Java functions are mapping into C global functions by prefixing the method name with
    // "Java_<Class>_"
    // This is triggered by the @CalledByNative annotation; the methods may be named as you wish.

    // Exported to C++ as:
    // Java_SampleForTests_javaMethod(JNIEnv* env, jobject caller, jint foo, jint bar)
    // Typically the C++ code would have obtained the jobject via the Init() call described above.
    @CalledByNative
    public int javaMethod(int jcaller, int ret) {
        return 0;
    }

    // Exported to C++ as Java_SampleForTests_staticJavaMethod(JNIEnv* env)
    // Note no jobject argument, as it is static.
    @CalledByNative
    public static boolean staticJavaMethod() throws Exception {
        return true;
    }

    // We do not want to include androidx annotations but this is basically how
    // the real annotation looks like.
    @interface AnnotationWithNamedNonStringParam {
        int otherwise() default PRIVATE;

        int PRIVATE = 2;
    }

    @CalledByNative
    @AnnotationWithNamedNonStringParam(otherwise = AnnotationWithNamedNonStringParam.PRIVATE)
    public boolean methodWithAnnotationParamAssignment() {
        return false;
    }

    // No prefix, so this method is package private. It will still be exported.
    @CalledByNative
    void packagePrivateJavaMethod() {}

    // Method signature with generics in params.
    @CalledByNative
    public void methodWithGenericParams(
            Map<String, Map<String, String>> env, LinkedList<Integer> bar) {}

    // Constructors will be exported to C++ as:
    // Java_SampleForTests_Constructor(JNIEnv* env, jint foo, jint bar)
    @CalledByNative
    public SampleForTests(int foo, int bar) {}

    // Tests @JniType for @CalledByNative methods.
    @CalledByNative
    @JniType("std::string")
    public String getFirstString(
            @JniType("std::vector<const char*>") String[] array,
            @JniType("const char*") String finalArg) {
        return array[0];
    }

    // Note the "Unchecked" suffix. By default, @CalledByNative will always generate bindings that
    // call CheckException(). With "@CalledByNativeUnchecked", the client C++ code is responsible to
    // call ClearException() and act as appropriate.
    // See more details at the "@CalledByNativeUnchecked" annotation.
    @CalledByNativeUnchecked
    void methodThatThrowsException() throws Exception {}

    // The generator is not confused by inline comments:
    // @CalledByNative void thisShouldNotAppearInTheOutput();
    // @CalledByNativeUnchecked public static void neitherShouldThis(int foo);

    /**
     * The generator is not confused by block comments:
     * @CalledByNative void thisShouldNotAppearInTheOutputEither();
     * @CalledByNativeUnchecked public static void andDefinitelyNotThis(int foo);
     */

    // String constants that look like comments don't confuse the generator:
    private String mArrgh = "*/*";

    private @interface SomeAnnotation {}
    private @interface AnotherAnnotation {}

    // The generator is not confused by @Annotated parameters.
    @CalledByNative
    @JniType("std::vector<int32_t>")
    int[] jniTypesAndAnnotations(
            @SomeAnnotation @JniType("MyEnum") int foo,
            final @SomeAnnotation @JniType("std::vector<int32_t>") int[] bar,
            @SomeAnnotation final int baz,
            @SomeAnnotation @JniType("long") final @AnotherAnnotation long bat) {
        return bar;
    }

    @CalledByNative
    @JniType("std::vector")
    static Collection<SampleForTests> listTest1(
            @JniType("std::vector<std::string>") List<String> items) {
        return Collections.emptyList();
    }

    @CalledByNative
    static @JniType("std::map<std::string, std::string>") Map<String, String> mapTest1(
            @JniType("std::map<std::string, std::string>") Map<String, String> arg0) {
        return arg0;
    }

    // ---------------------------------------------------------------------------------------------
    // Java fields which are accessed from C++ code only must be annotated with @AccessedByNative to
    // prevent them being eliminated when unreferenced code is stripped.
    @AccessedByNative private int mJavaField;

    // This "struct" will be created by the native side using |createInnerStructA|,
    // and used by the java-side somehow.
    // Note that |@CalledByNative| has to contain the inner class name.
    static class InnerStructA {
        private final long mLong;
        private final int mInt;
        private final String mString;

        private InnerStructA(long l, int i, String s) {
            mLong = l;
            mInt = i;
            mString = s;
        }

        @CalledByNative("InnerStructA")
        private static InnerStructA create(long l, int i, String s) {
            return new InnerStructA(l, i, s);
        }
    }

    private List<InnerStructA> mListInnerStructA = new ArrayList<InnerStructA>();

    @CalledByNative
    private void addStructA(InnerStructA a) {
        // Called by the native side to append another element.
        mListInnerStructA.add(a);
    }

    @CalledByNative
    private void iterateAndDoSomething() {
        Iterator<InnerStructA> it = mListInnerStructA.iterator();
        while (it.hasNext()) {
            InnerStructA element = it.next();
            // Now, do something with element.
        }
        // Done, clear the list.
        mListInnerStructA.clear();
    }

    // This "struct" will be created by the java side passed to native, which
    // will use its getters.
    // Note that |@CalledByNative| has to contain the inner class name.
    static class InnerStructB {
        private final long mKey;
        private final String mValue;

        private InnerStructB(long k, String v) {
            mKey = k;
            mValue = v;
        }

        @CalledByNative("InnerStructB")
        private long getKey() {
            return mKey;
        }

        @CalledByNative("InnerStructB")
        private String getValue() {
            return mValue;
        }
    }

    List<InnerStructB> mListInnerStructB = new ArrayList<InnerStructB>();

    void iterateAndDoSomethingWithMap() {
        Iterator<InnerStructB> it = mListInnerStructB.iterator();
        while (it.hasNext()) {
            InnerStructB element = it.next();
            // Now, do something with element.
            SampleForTestsJni.get().addStructB(mNativeCPPObject, this, element);
        }
        SampleForTestsJni.get().iterateAndDoSomethingWithStructB(mNativeCPPObject, this);
    }
    interface InnerInterface {}
    enum InnerEnum {}

    @CalledByNative
    static InnerInterface getInnerInterface() {
        return null;
    }

    @CalledByNative
    static InnerEnum getInnerEnum() {
        return null;
    }

    // Test overloads (causes names to be mangled).
    @CalledByNative
    static InnerEnum getInnerEnum(int a) {
        return null;
    }

    // Test jclass and jthrowable, as well as generics.
    @CalledByNative
    private Class<Map<String, String>> getClass(Class<Map<String, String>> arg0) {
        return null;
    }
    @CalledByNative
    private Throwable getThrowable(Throwable arg0) {
        return null;
    }

    @CalledByNative
    static @JniType("std::vector<bool>") boolean[] primitiveArrays(
            @JniType("std::vector<uint8_t>") byte[] b,
            @JniType("std::vector<uint16_t>") char[] c,
            @JniType("std::vector<int16_t>") short[] s,
            @JniType("std::vector<int32_t>") int[] i,
            @JniType("std::vector<int64_t>") long[] l,
            @JniType("std::vector<float>") float[] f,
            @JniType("std::vector<double>") double[] d) {
        return null;
    }

    // ---------------------------------------------------------------------------------------------
    // The following methods demonstrate declaring methods to call into C++ from Java.
    // The generator detects the type and name of the first parameter.
    @NativeMethods
    public interface Natives {
        // This declares a C++ function which the application code must implement:
        // static jint Init(JNIEnv* env, jobject caller);
        // The jobject parameter refers back to this java side object instance.
        // The implementation must return the pointer to the C++ object cast to jint.
        // The caller of this method should store it, and supply it as a the nativeCPPClass param to
        // subsequent native method calls (see the methods below that take an "int native..." as
        // first param).
        long init(
                SampleForTests caller,
                String param,
                @JniType("jni_zero::ByteArrayView") byte[] bytes,
                @JniType("jni_zero::tests::CPPClass*") SampleForTests convertedType,
                @JniType("std::vector") SampleForTests[] nonConvertedArray);

        // This defines a function binding to the associated C++ class member function. The name is
        // derived from |nativeDestroy| and |nativeCPPClass| to arrive at CPPClass::Destroy() (i.e.
        // native prefixes stripped).
        //
        // The |nativeCPPClass| is automatically cast to type CPPClass*, in order to obtain the
        // object on which to invoke the member function. Replace "CPPClass" with your particular
        // class name!
        void destroy(
                long nativeCPPClass,
                SampleForTests caller,
                @JniType("std::vector<uint8_t>") byte[] bytes);

        // This declares a C++ function which the application code must implement:
        // static jdouble GetDoubleFunction(JNIEnv* env, jobject caller);
        // The jobject parameter refers back to this java side object instance.
        double getDoubleFunction(SampleForTests ret);

        // Similar to nativeGetDoubleFunction(), but here the C++ side will receive a jclass rather
        // than jobject param, as the function is declared static.
        float getFloatFunction();

        @JniType("std::vector")
        List<SampleForTests> listTest2(@JniType("std::vector<std::string>") Set<String> items);

        // This function takes a non-POD datatype. We have a list mapping them to their full
        // classpath in jni_generator.py JavaParamToJni. If you require a new datatype, make sure
        // you add to that function.
        void setNonPODDatatype(SampleForTests obj, Rect rect);

        // This declares a C++ function which the application code must implement:
        // static ScopedJavaLocalRef<jobject> GetNonPODDatatype(JNIEnv* env, jobject caller);
        // The jobject parameter refers back to this java side object instance.
        // Note that it returns a ScopedJavaLocalRef<jobject> so that you don' have to worry about
        // deleting the JNI local reference. This is similar with Strings and arrays.
        Object getNonPODDatatype(SampleForTests jcaller);

        // Test jclass and jthrowable, as well as generics.
        Class<Map<String, String>> getClass(Class<Map<String, String>> env);

        Throwable getThrowable(Throwable arg0);

        // Test Map.
        @JniType("std::map<std::string, std::string>")
        Map<String, String> mapTest2(
                @JniType("std::map<std::string, std::string>") Map<String, String> arg0);

        // Test class under the same package
        void classUnderSamePackageTest(SampleUnderSamePackage arg);

        // Similar to nativeDestroy above, this will cast nativeCPPClass into pointer of CPPClass
        // type and call its Method member function. Replace "CPPClass" with your particular class
        // name!
        int method(
                long nativeCPPClass,
                SampleForTests caller,
                @JniType("std::vector<std::string>") String[] strings);

        @JniType("std::vector<bool>")
        boolean[] primitiveArrays(
                @JniType("std::vector<uint8_t>") byte[] b,
                @JniType("std::vector<uint16_t>") char[] c,
                @JniType("std::vector<int16_t>") short[] s,
                @JniType("std::vector<int32_t>") int[] i,
                @JniType("std::vector<int64_t>") long[] l,
                @JniType("std::vector<float>") float[] f,
                @JniType("std::vector<double>") double[] d);

        // Similar to nativeMethod above, but here the C++ fully qualified class name is taken from
        // the annotation rather than parameter name, which can thus be chosen freely.
        @NativeClassQualifiedName("CPPClass::InnerClass")
        double methodOtherP0(long nativePtr, SampleForTests caller);

        // Tests passing a nested class.
        void addStructB(long nativeCPPClass, SampleForTests caller, InnerStructB b);

        void iterateAndDoSomethingWithStructB(long nativeCPPClass, SampleForTests caller);
        String returnAString(long nativeCPPClass, SampleForTests caller);
    }
}